Skip to content

Commit 8c7baac

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

File tree

6 files changed

+202
-37
lines changed

6 files changed

+202
-37
lines changed

Sources/SwiftCompilerPluginMessageHandling/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
add_swift_syntax_library(SwiftCompilerPluginMessageHandling
1010
CompilerPluginMessageHandler.swift
1111
Diagnostics.swift
12+
LibraryPluginProvider.swift
1213
Macros.swift
1314
PluginMacroExpansionContext.swift
1415
PluginMessageCompatibility.swift
@@ -18,6 +19,7 @@ add_swift_syntax_library(SwiftCompilerPluginMessageHandling
1819
JSON/JSONDecoding.swift
1920
JSON/JSONEncoding.swift
2021
StandardIOMessageConnection.swift
22+
StdlibExtra.swift
2123
)
2224

2325
target_link_swift_syntax_libraries(SwiftCompilerPluginMessageHandling PUBLIC

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.

Sources/SwiftCompilerPluginMessageHandling/JSON/JSONDecoding.swift

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,40 +1314,3 @@ extension JSONDecoding.UnkeyedContainer: UnkeyedDecodingContainer {
13141314
self._currMapIdx = array.startIndex
13151315
}
13161316
}
1317-
1318-
// Compatibility shim for SE-0370
1319-
#if swift(<5.8)
1320-
extension UnsafeMutableBufferPointer {
1321-
fileprivate func initialize(
1322-
fromContentsOf source: some Collection<Element>
1323-
) -> Index {
1324-
let count = source.withContiguousStorageIfAvailable {
1325-
guard let sourceAddress = $0.baseAddress, !$0.isEmpty else {
1326-
return 0
1327-
}
1328-
precondition(
1329-
$0.count <= self.count,
1330-
"buffer cannot contain every element from source."
1331-
)
1332-
baseAddress?.initialize(from: sourceAddress, count: $0.count)
1333-
return $0.count
1334-
}
1335-
if let count {
1336-
return startIndex.advanced(by: count)
1337-
}
1338-
1339-
var (iterator, copied) = self.initialize(from: source)
1340-
precondition(
1341-
iterator.next() == nil,
1342-
"buffer cannot contain every element from source."
1343-
)
1344-
return startIndex.advanced(by: copied)
1345-
}
1346-
1347-
fileprivate func initializeElement(at index: Index, to value: Element) {
1348-
precondition(startIndex <= index && index < endIndex)
1349-
let p = baseAddress!.advanced(by: index)
1350-
p.initialize(to: value)
1351-
}
1352-
}
1353-
#endif
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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+
private import _SwiftSyntaxCShims
16+
#else
17+
import SwiftSyntaxMacros
18+
@_implementationOnly import _SwiftSyntaxCShims
19+
#endif
20+
21+
/// Singleton 'PluginProvider' that can serve shared library plugins.
22+
@_spi(PluginMessage)
23+
public class LibraryPluginProvider: PluginProvider {
24+
struct LoadedLibraryPlugin {
25+
var libraryPath: String
26+
var handle: UnsafeMutableRawPointer
27+
}
28+
29+
struct MacroRef: Hashable {
30+
var moduleName: String
31+
var typeName: String
32+
}
33+
34+
/// Loaded dynamic link library handles associated with the module name.
35+
var loadedLibraryPlugins: [String: LoadedLibraryPlugin] = [:]
36+
37+
/// Resolved macros cache.
38+
var resolvedMacros: [MacroRef: Macro.Type] = [:]
39+
40+
private init() {}
41+
42+
/// Singleton.
43+
@MainActor
44+
public static let shared: LibraryPluginProvider = LibraryPluginProvider()
45+
46+
public var features: [PluginFeature] {
47+
[.loadPluginLibrary]
48+
}
49+
50+
public func loadPluginLibrary(libraryPath: String, moduleName: String) throws {
51+
if let loaded = loadedLibraryPlugins[moduleName] {
52+
guard loaded.libraryPath == libraryPath else {
53+
// NOTE: Should be unreachable. Compiler should not load different
54+
// library for the same module name.
55+
throw LibraryPluginError(
56+
message:
57+
"library plugin for module '\(moduleName)' is already loaded from different path '\(loaded.libraryPath)'"
58+
)
59+
}
60+
return
61+
}
62+
63+
let dlHandle = try _loadLibrary(libraryPath)
64+
65+
loadedLibraryPlugins[moduleName] = LoadedLibraryPlugin(
66+
libraryPath: libraryPath,
67+
handle: dlHandle
68+
)
69+
}
70+
71+
public func resolveMacro(moduleName: String, typeName: String) throws -> SwiftSyntaxMacros.Macro.Type {
72+
let macroRef = MacroRef(moduleName: moduleName, typeName: typeName)
73+
if let resolved = resolvedMacros[macroRef] {
74+
return resolved
75+
}
76+
77+
// Find 'dlopen'ed library for the module name.
78+
guard let plugin = loadedLibraryPlugins[moduleName] else {
79+
// NOTE: Should be unreachable. Compiler should not use this server
80+
// unless the plugin loading succeeded.
81+
throw LibraryPluginError(message: "plugin not loaded for module '\(moduleName)'")
82+
}
83+
84+
// Lookup the type metadata.
85+
guard let type = _findAnyType(moduleName, typeName) else {
86+
throw LibraryPluginError(
87+
message: "type '\(moduleName).\(typeName)' could not be found in library plugin '\(plugin.libraryPath)'"
88+
)
89+
}
90+
91+
// The type must be a 'Macro' type.
92+
guard let macro = type as? Macro.Type else {
93+
throw LibraryPluginError(
94+
message:
95+
"type '\(moduleName).\(typeName)' is not a valid macro implementation type in library plugin '\(plugin.libraryPath)'"
96+
)
97+
}
98+
99+
// Cache the resolved type.
100+
resolvedMacros[macroRef] = macro
101+
return macro
102+
}
103+
}
104+
105+
#if os(Windows)
106+
private func _loadLibrary(_ path: String) throws -> UnsafeMutableRawPointer {
107+
// Create NUL terminated UTF16 path.
108+
let utf16Path = UnsafeMutableBufferPointer<UInt16>.allocate(capacity: path.utf16.count + 1)
109+
defer { utf16Path.deallocate() }
110+
let end = utf16Path.initialize(fromContentsOf: path.utf16)
111+
utf16Path.initializeElement(at: end, to: 0)
112+
113+
if let dlHandle = LoadLibraryW(utf16Path.baseAddress) {
114+
return UnsafeMutableRawPointer(dlHandle)
115+
}
116+
// FIXME: Format the error code to string.
117+
throw LibraryPluginError(message: "loader error: \(GetLastError())")
118+
}
119+
#else
120+
private func _loadLibrary(_ path: String) throws -> UnsafeMutableRawPointer {
121+
if let dlHandle = dlopen(path, RTLD_LAZY | RTLD_LOCAL) {
122+
return dlHandle
123+
}
124+
throw LibraryPluginError(message: "loader error: \(String(cString: dlerror()))")
125+
}
126+
#endif
127+
128+
private func _findAnyType(_ moduleName: String, _ typeName: String) -> Any.Type? {
129+
// Create a mangled name for struct, enum, and class. And use a runtime
130+
// function to find the type. Note that this simple mangling works even if the
131+
// actual symbol name doesn't match with it. i.e. We don't need to perform
132+
// punycode encodings or word substitutions.
133+
// FIXME: This is process global. Can we limit it to a specific .dylib ?
134+
for suffix in [ /*struct*/"V", /*enum*/ "O", /*class*/ "C"] {
135+
let mangled = "\(moduleName.utf8.count)\(moduleName)\(typeName.utf8.count)\(typeName)\(suffix)"
136+
if let type = _typeByName(mangled) {
137+
return type
138+
}
139+
}
140+
return nil
141+
}
142+
143+
private struct LibraryPluginError: Error, CustomStringConvertible {
144+
var description: String
145+
init(message: String) {
146+
self.description = message
147+
}
148+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
// Compatibility shim for SE-0370
14+
#if swift(<5.8)
15+
extension UnsafeMutableBufferPointer {
16+
func initialize(
17+
fromContentsOf source: some Collection<Element>
18+
) -> Index {
19+
let count = source.withContiguousStorageIfAvailable {
20+
guard let sourceAddress = $0.baseAddress, !$0.isEmpty else {
21+
return 0
22+
}
23+
precondition(
24+
$0.count <= self.count,
25+
"buffer cannot contain every element from source."
26+
)
27+
baseAddress?.initialize(from: sourceAddress, count: $0.count)
28+
return $0.count
29+
}
30+
if let count {
31+
return startIndex.advanced(by: count)
32+
}
33+
34+
var (iterator, copied) = self.initialize(from: source)
35+
precondition(
36+
iterator.next() == nil,
37+
"buffer cannot contain every element from source."
38+
)
39+
return startIndex.advanced(by: copied)
40+
}
41+
42+
func initializeElement(at index: Index, to value: Element) {
43+
precondition(startIndex <= index && index < endIndex)
44+
let p = baseAddress!.advanced(by: index)
45+
p.initialize(to: value)
46+
}
47+
}
48+
#endif

Sources/_SwiftSyntaxCShims/include/swiftsyntax/_includes.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626

2727
#if defined(_WIN32)
2828
#include <io.h>
29+
#include <libloaderapi.h>
2930
#elif defined(__unix__) || defined(__APPLE__)
3031
#include <unistd.h>
32+
#include <dlfcn.h>
3133
#endif
3234

3335
#include <stdio.h>

0 commit comments

Comments
 (0)