Skip to content

Commit 6ed9ea2

Browse files
committed
[Macros] Add 'LibraryPluginProvider'
LibraryPluginProvider is a 'PluginProvider' type that can load shared library plugins at runtime.
1 parent d643ebb commit 6ed9ea2

File tree

6 files changed

+235
-0
lines changed

6 files changed

+235
-0
lines changed

Package.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ let package = Package(
124124
dependencies: ["_SwiftSyntaxTestSupport", "SwiftIDEUtils", "SwiftParser", "SwiftSyntax"]
125125
),
126126

127+
// MARK: SwiftLibraryPluginProvider
128+
129+
.target(
130+
name: "SwiftLibraryPluginProvider",
131+
dependencies: ["SwiftSyntaxMacros", "SwiftCompilerPluginMessageHandling"],
132+
exclude: ["CMakeLists.txt"]
133+
),
134+
127135
// MARK: SwiftSyntax
128136

129137
.target(

Sources/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_subdirectory(_SwiftSyntaxCShims)
1010
add_subdirectory(SwiftBasicFormat)
1111
add_subdirectory(SwiftSyntax)
1212
add_subdirectory(SwiftDiagnostics)
13+
add_subdirectory(SwiftLibraryPluginProvider)
1314
add_subdirectory(SwiftParser)
1415
add_subdirectory(SwiftParserDiagnostics)
1516
add_subdirectory(SwiftRefactor)

Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public enum PluginFeature: String {
2525
}
2626

2727
/// A type that provides the actual plugin functions.
28+
///
29+
/// Note that it's an implementation's responsibility to cache the API results as needed.
2830
@_spi(PluginMessage)
2931
public protocol PluginProvider {
3032
/// Resolve macro type by the module name and the type name.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# This source file is part of the Swift.org open source project
2+
#
3+
# Copyright (c) 2024 Apple Inc. and the Swift project authors
4+
# Licensed under Apache License v2.0 with Runtime Library Exception
5+
#
6+
# See http://swift.org/LICENSE.txt for license information
7+
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
8+
9+
add_swift_syntax_library(SwiftLibraryPluginProvider
10+
LibraryPluginProvider.swift
11+
)
12+
13+
target_link_swift_syntax_libraries(SwiftLibraryPluginProvider PUBLIC
14+
SwiftSyntaxMacros
15+
SwiftCompilerPluginMessageHandling
16+
)
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#if swift(>=6.0)
14+
public import SwiftSyntaxMacros
15+
@_spi(PluginMessage) public import SwiftCompilerPluginMessageHandling
16+
// NOTE: Do not use '_'
17+
#if canImport(Darwin)
18+
private import Darwin
19+
#elseif canImport(Glibc)
20+
private import Glibc
21+
#elseif canImport(Musl)
22+
private import Musl
23+
#elseif canImport(WinSDK)
24+
private import WinSDK
25+
#endif
26+
#else
27+
import SwiftSyntaxMacros
28+
@_spi(PluginMessage) import SwiftCompilerPluginMessageHandling
29+
#if canImport(Darwin)
30+
@_implementationOnly import Darwin
31+
#elseif canImport(Glibc)
32+
@_implementationOnly import Glibc
33+
#elseif canImport(Musl)
34+
@_implementationOnly import Musl
35+
#elseif canImport(WinSDK)
36+
@_implementationOnly import WinSDK
37+
#endif
38+
#endif
39+
40+
/// Singleton 'PluginProvider' that can serve shared library plugins.
41+
@_spi(PluginMessage)
42+
public class LibraryPluginProvider: PluginProvider {
43+
struct LoadedLibraryPlugin {
44+
var libraryPath: String
45+
var handle: UnsafeMutableRawPointer
46+
}
47+
48+
struct MacroRef: Hashable {
49+
var moduleName: String
50+
var typeName: String
51+
}
52+
53+
/// Loaded dynamic link library handles associated with the module name.
54+
var loadedLibraryPlugins: [String: LoadedLibraryPlugin] = [:]
55+
56+
/// Resolved macros cache.
57+
var resolvedMacros: [MacroRef: Macro.Type] = [:]
58+
59+
private init() {}
60+
61+
/// Singleton.
62+
@MainActor
63+
public static let shared: LibraryPluginProvider = LibraryPluginProvider()
64+
65+
public var features: [PluginFeature] {
66+
[.loadPluginLibrary]
67+
}
68+
69+
public func loadPluginLibrary(libraryPath: String, moduleName: String) throws {
70+
if let loaded = loadedLibraryPlugins[moduleName] {
71+
guard loaded.libraryPath == libraryPath else {
72+
// NOTE: Should be unreachable. Compiler should not load different
73+
// library for the same module name.
74+
throw LibraryPluginError(
75+
message:
76+
"library plugin for module '\(moduleName)' is already loaded from different path '\(loaded.libraryPath)'"
77+
)
78+
}
79+
return
80+
}
81+
82+
let dlHandle = try _loadLibrary(libraryPath)
83+
84+
loadedLibraryPlugins[moduleName] = LoadedLibraryPlugin(
85+
libraryPath: libraryPath,
86+
handle: dlHandle
87+
)
88+
}
89+
90+
public func resolveMacro(moduleName: String, typeName: String) throws -> SwiftSyntaxMacros.Macro.Type {
91+
let macroRef = MacroRef(moduleName: moduleName, typeName: typeName)
92+
if let resolved = resolvedMacros[macroRef] {
93+
return resolved
94+
}
95+
96+
// Find 'dlopen'ed library for the module name.
97+
guard let plugin = loadedLibraryPlugins[moduleName] else {
98+
// NOTE: Should be unreachable. Compiler should not use this server
99+
// unless the plugin loading succeeded.
100+
throw LibraryPluginError(message: "plugin not loaded for module '\(moduleName)'")
101+
}
102+
103+
// Lookup the type metadata.
104+
guard let type = _findAnyType(moduleName, typeName) else {
105+
throw LibraryPluginError(
106+
message: "type '\(moduleName).\(typeName)' could not be found in library plugin '\(plugin.libraryPath)'"
107+
)
108+
}
109+
110+
// The type must be a 'Macro' type.
111+
guard let macro = type as? Macro.Type else {
112+
throw LibraryPluginError(
113+
message:
114+
"type '\(moduleName).\(typeName)' is not a valid macro implementation type in library plugin '\(plugin.libraryPath)'"
115+
)
116+
}
117+
118+
// Cache the resolved type.
119+
resolvedMacros[macroRef] = macro
120+
return macro
121+
}
122+
}
123+
124+
#if os(Windows)
125+
private func _loadLibrary(_ path: String) throws -> UnsafeMutableRawPointer {
126+
// Create NUL terminated UTF16 path.
127+
let utf16Path = UnsafeMutableBufferPointer<UInt16>.allocate(capacity: path.utf16.count + 1)
128+
defer { utf16Path.deallocate() }
129+
let end = utf16Path.initialize(fromContentsOf: path.utf16)
130+
utf16Path.initializeElement(at: end, to: 0)
131+
132+
if let dlHandle = LoadLibraryW(utf16Path.baseAddress) {
133+
return UnsafeMutableRawPointer(dlHandle)
134+
}
135+
// FIXME: Format the error code to string.
136+
throw LibraryPluginError(message: "loader error: \(GetLastError())")
137+
}
138+
#else
139+
private func _loadLibrary(_ path: String) throws -> UnsafeMutableRawPointer {
140+
if let dlHandle = dlopen(path, RTLD_LAZY | RTLD_LOCAL) {
141+
return dlHandle
142+
}
143+
throw LibraryPluginError(message: "loader error: \(String(cString: dlerror()))")
144+
}
145+
#endif
146+
147+
private func _findAnyType(_ moduleName: String, _ typeName: String) -> Any.Type? {
148+
// Create a mangled name for struct, enum, and class. And use a runtime
149+
// function to find the type. Note that this simple mangling works even if the
150+
// actual symbol name doesn't match with it. i.e. We don't need to perform
151+
// punycode encodings or word substitutions.
152+
// FIXME: This is process global. Can we limit it to a specific .dylib ?
153+
for suffix in [ /*struct*/"V", /*enum*/ "O", /*class*/ "C"] {
154+
let mangled = "\(moduleName.utf8.count)\(moduleName)\(typeName.utf8.count)\(typeName)\(suffix)"
155+
if let type = _typeByName(mangled) {
156+
return type
157+
}
158+
}
159+
return nil
160+
}
161+
162+
private struct LibraryPluginError: Error, CustomStringConvertible {
163+
var description: String
164+
init(message: String) {
165+
self.description = message
166+
}
167+
}
168+
169+
// Compatibility shim for SE-0370
170+
#if swift(<5.8)
171+
extension UnsafeMutableBufferPointer {
172+
private func initialize(
173+
fromContentsOf source: some Collection<Element>
174+
) -> Index {
175+
let count = source.withContiguousStorageIfAvailable {
176+
guard let sourceAddress = $0.baseAddress, !$0.isEmpty else {
177+
return 0
178+
}
179+
precondition(
180+
$0.count <= self.count,
181+
"buffer cannot contain every element from source."
182+
)
183+
baseAddress?.initialize(from: sourceAddress, count: $0.count)
184+
return $0.count
185+
}
186+
if let count {
187+
return startIndex.advanced(by: count)
188+
}
189+
190+
var (iterator, copied) = self.initialize(from: source)
191+
precondition(
192+
iterator.next() == nil,
193+
"buffer cannot contain every element from source."
194+
)
195+
return startIndex.advanced(by: copied)
196+
}
197+
198+
private func initializeElement(at index: Index, to value: Element) {
199+
precondition(startIndex <= index && index < endIndex)
200+
let p = baseAddress!.advanced(by: index)
201+
p.initialize(to: value)
202+
}
203+
}
204+
#endif

Sources/_SwiftSyntaxCShims/include/swiftsyntax/_includes.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525
//===----------------------------------------------------------------------===//
2626

2727
#if defined(_WIN32)
28+
// NOTE: Do NOT include "WinSDK" headers here.
29+
// This is a part of compiler. If we use 'WinSDK' here, the compiler links with
30+
// swiftWinSDK.dll when (re)bulding it, and fails because it's used.
2831
#include <io.h>
32+
2933
#elif defined(__unix__) || defined(__APPLE__)
3034
#include <unistd.h>
3135
#endif

0 commit comments

Comments
 (0)