Skip to content

Commit

Permalink
[Macros] Add 'LibraryPluginProvider'
Browse files Browse the repository at this point in the history
LibraryPluginProvider is a 'PluginProvider' type that can load shared
library plugins at runtime.
  • Loading branch information
rintaro committed May 1, 2024
1 parent 23a868a commit 2a486f7
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 49 deletions.
2 changes: 2 additions & 0 deletions Sources/SwiftCompilerPluginMessageHandling/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
add_swift_syntax_library(SwiftCompilerPluginMessageHandling
CompilerPluginMessageHandler.swift
Diagnostics.swift
LibraryPluginProvider.swift
Macros.swift
PluginMacroExpansionContext.swift
PluginMessageCompatibility.swift
Expand All @@ -18,6 +19,7 @@ add_swift_syntax_library(SwiftCompilerPluginMessageHandling
JSON/JSONDecoding.swift
JSON/JSONEncoding.swift
StandardIOMessageConnection.swift
StdlibExtra.swift
)

target_link_swift_syntax_libraries(SwiftCompilerPluginMessageHandling PUBLIC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public enum PluginFeature: String {
}

/// A type that provides the actual plugin functions.
///
/// Note that it's an implementation's responsibility to cache the API results as needed.
@_spi(PluginMessage)
public protocol PluginProvider {
/// Resolve macro type by the module name and the type name.
Expand Down
49 changes: 0 additions & 49 deletions Sources/SwiftCompilerPluginMessageHandling/JSON/JSONDecoding.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,3 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#if swift(>=6.0)
private import _SwiftSyntaxCShims
#else
Expand Down Expand Up @@ -1314,40 +1302,3 @@ extension JSONDecoding.UnkeyedContainer: UnkeyedDecodingContainer {
self._currMapIdx = array.startIndex
}
}

// Compatibility shim for SE-0370
#if swift(<5.8)
extension UnsafeMutableBufferPointer {
fileprivate func initialize(
fromContentsOf source: some Collection<Element>
) -> Index {
let count = source.withContiguousStorageIfAvailable {
guard let sourceAddress = $0.baseAddress, !$0.isEmpty else {
return 0
}
precondition(
$0.count <= self.count,
"buffer cannot contain every element from source."
)
baseAddress?.initialize(from: sourceAddress, count: $0.count)
return $0.count
}
if let count {
return startIndex.advanced(by: count)
}

var (iterator, copied) = self.initialize(from: source)
precondition(
iterator.next() == nil,
"buffer cannot contain every element from source."
)
return startIndex.advanced(by: copied)
}

fileprivate func initializeElement(at index: Index, to value: Element) {
precondition(startIndex <= index && index < endIndex)
let p = baseAddress!.advanced(by: index)
p.initialize(to: value)
}
}
#endif
148 changes: 148 additions & 0 deletions Sources/SwiftCompilerPluginMessageHandling/LibraryPluginProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#if swift(>=6.0)
public import SwiftSyntaxMacros
private import _SwiftSyntaxCShims
#else
import SwiftSyntaxMacros
@_implementationOnly import _SwiftSyntaxCShims
#endif

/// Singleton 'PluginProvider' that can serve shared library plugins.
@_spi(PluginMessage)
public class LibraryPluginProvider: PluginProvider {
struct LoadedLibraryPlugin {
var libraryPath: String
var handle: UnsafeMutableRawPointer
}

struct MacroRef: Hashable {
var moduleName: String
var typeName: String
}

/// Loaded dynamic link library handles associated with the module name.
var loadedLibraryPlugins: [String: LoadedLibraryPlugin] = [:]

/// Resolved macros cache.
var resolvedMacros: [MacroRef: Macro.Type] = [:]

private init() {}

/// Singleton.
@MainActor
public static let shared: LibraryPluginProvider = LibraryPluginProvider()

public var features: [PluginFeature] {
[.loadPluginLibrary]
}

public func loadPluginLibrary(libraryPath: String, moduleName: String) throws {
if let loaded = loadedLibraryPlugins[moduleName] {
guard loaded.libraryPath == libraryPath else {
// NOTE: Should be unreachable. Compiler should not load different
// library for the same module name.
throw LibraryPluginError(
message:
"library plugin for module '\(moduleName)' is already loaded from different path '\(loaded.libraryPath)'"
)
}
return
}

let dlHandle = try _loadLibrary(libraryPath)

loadedLibraryPlugins[moduleName] = LoadedLibraryPlugin(
libraryPath: libraryPath,
handle: dlHandle
)
}

public func resolveMacro(moduleName: String, typeName: String) throws -> SwiftSyntaxMacros.Macro.Type {
let macroRef = MacroRef(moduleName: moduleName, typeName: typeName)
if let resolved = resolvedMacros[macroRef] {
return resolved
}

// Find 'dlopen'ed library for the module name.
guard let plugin = loadedLibraryPlugins[moduleName] else {
// NOTE: Should be unreachable. Compiler should not use this server
// unless the plugin loading succeeded.
throw LibraryPluginError(message: "plugin not loaded for module '\(moduleName)'")
}

// Lookup the type metadata.
guard let type = _findAnyType(moduleName, typeName) else {
throw LibraryPluginError(
message: "type '\(moduleName).\(typeName)' could not be found in library plugin '\(plugin.libraryPath)'"
)
}

// The type must be a 'Macro' type.
guard let macro = type as? Macro.Type else {
throw LibraryPluginError(
message:
"type '\(moduleName).\(typeName)' is not a valid macro implementation type in library plugin '\(plugin.libraryPath)'"
)
}

// Cache the resolved type.
resolvedMacros[macroRef] = macro
return macro
}
}

#if os(Windows)
private func _loadLibrary(_ path: String) throws -> UnsafeMutableRawPointer {
// Create NUL terminated UTF16 path.
let utf16Path = UnsafeMutableBufferPointer<UInt16>.allocate(capacity: path.utf16.count + 1)
defer { utf16Path.deallocate() }
let end = utf16Path.initialize(fromContentsOf: path.utf16)
utf16Path.initializeElement(at: end, to: 0)

if let dlHandle = LoadLibraryW(utf16Path) {
return dlHandle
}
// FIXME: Format the error code to string.
throw LibraryPluginError(message: "loader error: \(GetLastError())")
}
#else
private func _loadLibrary(_ path: String) throws -> UnsafeMutableRawPointer {
if let dlHandle = dlopen(path, RTLD_LAZY | RTLD_LOCAL) {
return dlHandle
}
throw LibraryPluginError(message: "loader error: \(String(cString: dlerror()))")
}
#endif

private func _findAnyType(_ moduleName: String, _ typeName: String) -> Any.Type? {
// Create a mangled name for struct, enum, and class. And use a runtime
// function to find the type. Note that this simple mangling works even if the
// actual symbol name doesn't match with it. i.e. We don't need to perform
// punycode encodings or word substitutions.
// FIXME: This is process global. Can we limit it to a specific .dylib ?
for suffix in [ /*struct*/"V", /*enum*/ "O", /*class*/ "C"] {
let mangled = "\(moduleName.utf8.count)\(moduleName)\(typeName.utf8.count)\(typeName)\(suffix)"
if let type = _typeByName(mangled) {
return type
}
}
return nil
}

private struct LibraryPluginError: Error, CustomStringConvertible {
var description: String
init(message: String) {
self.description = message
}
}
48 changes: 48 additions & 0 deletions Sources/SwiftCompilerPluginMessageHandling/StdlibExtra.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

// Compatibility shim for SE-0370
#if swift(<5.8)
extension UnsafeMutableBufferPointer {
fileprivate func initialize(
fromContentsOf source: some Collection<Element>
) -> Index {
let count = source.withContiguousStorageIfAvailable {
guard let sourceAddress = $0.baseAddress, !$0.isEmpty else {
return 0
}
precondition(
$0.count <= self.count,
"buffer cannot contain every element from source."
)
baseAddress?.initialize(from: sourceAddress, count: $0.count)
return $0.count
}
if let count {
return startIndex.advanced(by: count)
}

var (iterator, copied) = self.initialize(from: source)
precondition(
iterator.next() == nil,
"buffer cannot contain every element from source."
)
return startIndex.advanced(by: copied)
}

fileprivate func initializeElement(at index: Index, to value: Element) {
precondition(startIndex <= index && index < endIndex)
let p = baseAddress!.advanced(by: index)
p.initialize(to: value)
}
}
#endif
2 changes: 2 additions & 0 deletions Sources/_SwiftSyntaxCShims/include/swiftsyntax/_includes.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@

#if defined(_WIN32)
#include <io.h>
#include <libloaderapi.h>
#elif defined(__unix__) || defined(__APPLE__)
#include <unistd.h>
#include <dlfcn.h>
#endif

#include <stdio.h>
Expand Down

0 comments on commit 2a486f7

Please sign in to comment.