Skip to content

Commit 1c9eb89

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

File tree

6 files changed

+234
-0
lines changed

6 files changed

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

Sources/_SwiftSyntaxCShims/include/_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)