Skip to content

Commit

Permalink
Merge pull request #2300 from rintaro/macros-library-provider
Browse files Browse the repository at this point in the history
[Macros] Add 'LibraryPluginProvider'
  • Loading branch information
rintaro committed May 5, 2024
2 parents c3fab73 + 1c9eb89 commit 60bc6d4
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 0 deletions.
8 changes: 8 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ let package = Package(
dependencies: ["_SwiftSyntaxTestSupport", "SwiftIDEUtils", "SwiftParser", "SwiftSyntax"]
),

// MARK: SwiftLibraryPluginProvider

.target(
name: "SwiftLibraryPluginProvider",
dependencies: ["SwiftSyntaxMacros", "SwiftCompilerPluginMessageHandling"],
exclude: ["CMakeLists.txt"]
),

// MARK: SwiftSyntax

.target(
Expand Down
1 change: 1 addition & 0 deletions Sources/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ add_subdirectory(_SwiftSyntaxCShims)
add_subdirectory(SwiftBasicFormat)
add_subdirectory(SwiftSyntax)
add_subdirectory(SwiftDiagnostics)
add_subdirectory(SwiftLibraryPluginProvider)
add_subdirectory(SwiftParser)
add_subdirectory(SwiftParserDiagnostics)
add_subdirectory(SwiftRefactor)
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
16 changes: 16 additions & 0 deletions Sources/SwiftLibraryPluginProvider/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# This source file is part of the Swift.org 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 Swift project authors

add_swift_syntax_library(SwiftLibraryPluginProvider
LibraryPluginProvider.swift
)

target_link_swift_syntax_libraries(SwiftLibraryPluginProvider PUBLIC
SwiftSyntaxMacros
SwiftCompilerPluginMessageHandling
)
203 changes: 203 additions & 0 deletions Sources/SwiftLibraryPluginProvider/LibraryPluginProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
//===----------------------------------------------------------------------===//
//
// 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
@_spi(PluginMessage) public import SwiftCompilerPluginMessageHandling
// NOTE: Do not use '_SwiftSyntaxCShims' for 'dlopen' and 'LoadLibraryW' (Windows)
// because we don't want other modules depend on 'WinSDK'.
#if canImport(Darwin)
private import Darwin
#elseif canImport(Glibc)
private import Glibc
#elseif canImport(Musl)
private import Musl
#elseif canImport(WinSDK)
private import WinSDK
#endif
#else
import SwiftSyntaxMacros
@_spi(PluginMessage) import SwiftCompilerPluginMessageHandling
#if canImport(Darwin)
@_implementationOnly import Darwin
#elseif canImport(Glibc)
@_implementationOnly import Glibc
#elseif canImport(Musl)
@_implementationOnly import Musl
#elseif canImport(WinSDK)
@_implementationOnly import WinSDK
#endif
#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 NULL terminated UTF16 string.
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)

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

// Compatibility shim for SE-0370
#if swift(<5.8)
extension UnsafeMutableBufferPointer {
private 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)
}

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

#if defined(_WIN32)
// NOTE: Do NOT include "WinSDK" headers here.
// This is a part of compiler. If we use 'WinSDK' here, the compiler links with
// swiftWinSDK.dll when (re)bulding it, and fails because it's used.
#include <io.h>

#elif defined(__unix__) || defined(__APPLE__)
#include <unistd.h>
#endif
Expand Down

0 comments on commit 60bc6d4

Please sign in to comment.