Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Macros] Add support for wasm plugins #1582

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,10 @@ extension Driver {
}

// Emit user-provided plugin paths, in order.
if isFrontendArgSupported(.externalPluginPath) {
if isFrontendArgSupported(.loadPlugin) {
try commandLine.appendAll(.pluginPath, .externalPluginPath, .loadPluginLibrary, .loadPlugin, from: &parsedOptions)
try addLoadPluginExecutableArguments(commandLine: &commandLine)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. This is going change the relative ordering of -load-plugin-executable arguments with respect to the other plugin-like arguments, which we generally try to avoid. I think we'll need to iterate through all of the options, selecting these ones and translating the -external-plugin-path ones on-the-fly. I do appreciate how this approach makes the SwiftPM changes more limited, though, so SwiftPM can "just" treat wasm plugins like any other external plugin executable.

} else if isFrontendArgSupported(.externalPluginPath) {
try commandLine.appendAll(.pluginPath, .externalPluginPath, .loadPluginLibrary, .loadPluginExecutable, from: &parsedOptions)
} else if isFrontendArgSupported(.pluginPath) {
try commandLine.appendAll(.pluginPath, .loadPluginLibrary, from: &parsedOptions)
Expand Down Expand Up @@ -792,6 +795,40 @@ extension Driver {
commandLine.appendPath(pluginPathRoot.localPluginPath)
}

/// Forward `-load-plugin-executable` arguments, with special handling for Wasm plugins.
///
/// Wasm plugins are updated to use `-load-plugin` with the plugin server.
mutating func addLoadPluginExecutableArguments(commandLine: inout [Job.ArgTemplate]) throws {
var cachedPluginServerPath: VirtualPath?
var pluginServerPath: VirtualPath {
get throws {
if let cachedPluginServerPath = cachedPluginServerPath {
return cachedPluginServerPath
}
let pluginPathRoot = VirtualPath.absolute(try toolchain.executableDir.parentDirectory)
let pluginServerPath = pluginPathRoot.pluginServerPath
cachedPluginServerPath = pluginServerPath
return pluginServerPath
}
}

for loadPluginExecutable in parsedOptions.arguments(for: .loadPluginExecutable) {
let argument = loadPluginExecutable.argument.asSingle
guard let separator = argument.lastIndex(of: "#") else { continue }

let path = argument[..<separator]
let afterPath = argument[separator...]

guard path.hasSuffix(".wasm") else {
try commandLine.append(loadPluginExecutable)
continue
}

commandLine.appendFlag(.loadPlugin)
try commandLine.appendFlag("\(path):\(pluginServerPath)\(afterPath)")
}
}


/// If explicit dependency planner supports creating bridging header pch command.
public func supportsBridgingHeaderPCHCommand() throws -> Bool {
Expand Down
10 changes: 0 additions & 10 deletions Sources/SwiftDriver/Toolchains/DarwinToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -482,16 +482,6 @@ private extension Version {
}

extension VirtualPath {
// Given a virtual path pointing into a toolchain/SDK/platform, produce the
// path to `swift-plugin-server`.
fileprivate var pluginServerPath: VirtualPath {
#if os(Windows)
self.appending(components: "bin", "swift-plugin-server.exe")
#else
self.appending(components: "bin", "swift-plugin-server")
#endif
}

// Given a virtual path pointing into a toolchain/SDK/platform, produce the
// path to the plugins.
var pluginPath: VirtualPath {
Expand Down
12 changes: 12 additions & 0 deletions Sources/SwiftDriver/Toolchains/Toolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,15 @@ extension Toolchain {
@_spi(Testing) public enum ToolchainError: Swift.Error {
case unableToFind(tool: String)
}

extension VirtualPath {
// Given a virtual path pointing into a toolchain/SDK/platform, produce the
// path to `swift-plugin-server`.
var pluginServerPath: VirtualPath {
#if os(Windows)
self.appending(components: "bin", "swift-plugin-server.exe")
#else
self.appending(components: "bin", "swift-plugin-server")
#endif
}
}
10 changes: 8 additions & 2 deletions Sources/SwiftOptions/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ extension Option {
public static let disableColocateTypeDescriptors: Option = Option("-disable-colocate-type-descriptors", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable colocate type descriptors")
public static let disableConcreteTypeMetadataMangledNameAccessors: Option = Option("-disable-concrete-type-metadata-mangled-name-accessors", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable concrete type metadata access by mangled name")
public static let disableConstraintSolverPerformanceHacks: Option = Option("-disable-constraint-solver-performance-hacks", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable all the hacks in the constraint solver")
public static let disableCrossImportOverlaySearch: Option = Option("-disable-cross-import-overlay-search", .flag, attributes: [.frontend, .noDriver], helpText: "Disable searching for cross import overlay file")
public static let disableCrossImportOverlays: Option = Option("-disable-cross-import-overlays", .flag, attributes: [.frontend, .noDriver], helpText: "Do not automatically import declared cross-import overlays.")
public static let cxxInteropDisableRequirementAtImport: Option = Option("-disable-cxx-interop-requirement-at-import", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Do not require C++ interoperability to be enabled when importing a Swift module that enables C++ interoperability")
public static let disableDebuggerShadowCopies: Option = Option("-disable-debugger-shadow-copies", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable debugger shadow copies of local variables.This option is only useful for testing the compiler.")
Expand All @@ -183,6 +184,7 @@ extension Option {
public static let disableGenericMetadataPrespecialization: Option = Option("-disable-generic-metadata-prespecialization", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Do not statically specialize metadata for generic types at types that are known to be used in source.")
public static let disableImplicitBacktracingModuleImport: Option = Option("-disable-implicit-backtracing-module-import", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable the implicit import of the _Backtracing module.")
public static let disableImplicitConcurrencyModuleImport: Option = Option("-disable-implicit-concurrency-module-import", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable the implicit import of the _Concurrency module.")
public static let disableImplicitCxxModuleImport: Option = Option("-disable-implicit-cxx-module-import", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable the implicit import of the C++ Standard Library module.")
public static let disableImplicitStringProcessingModuleImport: Option = Option("-disable-implicit-string-processing-module-import", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable the implicit import of the _StringProcessing module.")
public static let disableImplicitSwiftModules: Option = Option("-disable-implicit-swift-modules", .flag, attributes: [.frontend, .noDriver], helpText: "Disable building Swift modules implicitly by the compiler")
public static let disableImportPtrauthFieldFunctionPointers: Option = Option("-disable-import-ptrauth-field-function-pointers", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable import of custom ptrauth qualified field function pointers")
Expand Down Expand Up @@ -486,7 +488,6 @@ extension Option {
public static let experimentalPrintFullConvention: Option = Option("-experimental-print-full-convention", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "When emitting a module interface or SIL, emit additional @convention arguments, regardless of whether they were written in the source. Also requires -use-clang-function-types to be enabled.")
public static let experimentalSkipAllFunctionBodies: Option = Option("-experimental-skip-all-function-bodies", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Skip type-checking function bodies and all SIL generation")
public static let experimentalSkipNonExportableDecls: Option = Option("-experimental-skip-non-exportable-decls", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Skip decls that are not exported to clients")
public static let experimentalSkipNonInlinableFunctionBodiesIsLazy: Option = Option("-experimental-skip-non-inlinable-function-bodies-is-lazy", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Infer lazy typechecking for -experimental-skip-non-inlinable-function-bodies")
public static let experimentalSkipNonInlinableFunctionBodiesWithoutTypes: Option = Option("-experimental-skip-non-inlinable-function-bodies-without-types", .flag, attributes: [.helpHidden, .frontend], helpText: "Skip work on non-inlinable function bodies that do not declare nested types")
public static let experimentalSkipNonInlinableFunctionBodies: Option = Option("-experimental-skip-non-inlinable-function-bodies", .flag, attributes: [.helpHidden, .frontend], helpText: "Skip type-checking and SIL generation for non-inlinable function bodies")
public static let experimentalSpiImports: Option = Option("-experimental-spi-imports", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable experimental support for SPI imports")
Expand Down Expand Up @@ -584,6 +585,7 @@ extension Option {
public static let reuseDependencyScanCache: Option = Option("-load-dependency-scan-cache", .flag, attributes: [.frontend, .noDriver], helpText: "After performing a dependency scan, serialize the scanner's internal state.")
public static let loadPluginExecutable: Option = Option("-load-plugin-executable", .separate, attributes: [.frontend, .doesNotAffectIncrementalBuild, .argumentIsPath], metaVar: "<path>#<module-names>", helpText: "Path to a compiler plugin executable and a comma-separated list of module names where the macro types are declared", group: .pluginSearch)
public static let loadPluginLibrary: Option = Option("-load-plugin-library", .separate, attributes: [.frontend, .doesNotAffectIncrementalBuild, .argumentIsPath], metaVar: "<path>", helpText: "Path to a dynamic library containing compiler plugins such as macros", group: .pluginSearch)
public static let loadPlugin: Option = Option("-load-plugin", .separate, attributes: [.frontend, .doesNotAffectIncrementalBuild], metaVar: "<library-path>:<server-path>#<module-names>", helpText: "Path to a plugin library, a server to load it in, and a comma-separated list of module names where the macro types are declared", group: .pluginSearch)
public static let locale: Option = Option("-locale", .separate, attributes: [.frontend, .doesNotAffectIncrementalBuild], metaVar: "<locale-code>", helpText: "Choose a language for diagnostic messages")
public static let localizationPath: Option = Option("-localization-path", .separate, attributes: [.frontend, .doesNotAffectIncrementalBuild, .argumentIsPath], metaVar: "<path>", helpText: "Path to localized diagnostic messages directory")
public static let location: Option = Option("-location", .separate, attributes: [.noDriver], metaVar: "<location>", helpText: "Filter nodes with the given location.")
Expand Down Expand Up @@ -775,6 +777,7 @@ extension Option {
public static let suppressStaticExclusivitySwap: Option = Option("-suppress-static-exclusivity-swap", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Suppress static violations of exclusive access with swap()")
public static let suppressWarnings: Option = Option("-suppress-warnings", .flag, attributes: [.frontend], helpText: "Suppress all warnings")
public static let swiftAsyncFramePointerEQ: Option = Option("-swift-async-frame-pointer=", .joined, attributes: [.helpHidden, .frontend, .noDriver], helpText: "One of 'auto', 'always' or 'never'")
public static let swiftModuleCrossImport: Option = Option("-swift-module-cross-import", .multiArg, attributes: [.frontend, .noDriver], metaVar: "<moduleName> <crossImport.swiftoverlay>", helpText: "Specify the cross import module", numArgs: 2)
public static let swiftModuleFile: Option = Option("-swift-module-file=", .joined, attributes: [.frontend, .noDriver], metaVar: "<name>=<path>", helpText: "Specify Swift module input explicitly built from textual interface")
public static let swiftOnly: Option = Option("-swift-only", .flag, attributes: [.noDriver], helpText: "Only include APIs defined from Swift source")
public static let swiftOnly_: Option = Option("--swift-only", .flag, alias: Option.swiftOnly, attributes: [.noDriver], helpText: "Only include APIs defined from Swift source")
Expand Down Expand Up @@ -1020,6 +1023,7 @@ extension Option {
Option.disableColocateTypeDescriptors,
Option.disableConcreteTypeMetadataMangledNameAccessors,
Option.disableConstraintSolverPerformanceHacks,
Option.disableCrossImportOverlaySearch,
Option.disableCrossImportOverlays,
Option.cxxInteropDisableRequirementAtImport,
Option.disableDebuggerShadowCopies,
Expand All @@ -1039,6 +1043,7 @@ extension Option {
Option.disableGenericMetadataPrespecialization,
Option.disableImplicitBacktracingModuleImport,
Option.disableImplicitConcurrencyModuleImport,
Option.disableImplicitCxxModuleImport,
Option.disableImplicitStringProcessingModuleImport,
Option.disableImplicitSwiftModules,
Option.disableImportPtrauthFieldFunctionPointers,
Expand Down Expand Up @@ -1342,7 +1347,6 @@ extension Option {
Option.experimentalPrintFullConvention,
Option.experimentalSkipAllFunctionBodies,
Option.experimentalSkipNonExportableDecls,
Option.experimentalSkipNonInlinableFunctionBodiesIsLazy,
Option.experimentalSkipNonInlinableFunctionBodiesWithoutTypes,
Option.experimentalSkipNonInlinableFunctionBodies,
Option.experimentalSpiImports,
Expand Down Expand Up @@ -1440,6 +1444,7 @@ extension Option {
Option.reuseDependencyScanCache,
Option.loadPluginExecutable,
Option.loadPluginLibrary,
Option.loadPlugin,
Option.locale,
Option.localizationPath,
Option.location,
Expand Down Expand Up @@ -1631,6 +1636,7 @@ extension Option {
Option.suppressStaticExclusivitySwap,
Option.suppressWarnings,
Option.swiftAsyncFramePointerEQ,
Option.swiftModuleCrossImport,
Option.swiftModuleFile,
Option.swiftOnly,
Option.swiftOnly_,
Expand Down
37 changes: 36 additions & 1 deletion Tests/SwiftDriverTests/SwiftDriverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7398,7 +7398,7 @@ final class SwiftDriverTests: XCTestCase {
func pluginPathTest(platform: String, sdk: String, searchPlatform: String) throws {
let sdkRoot = try testInputsPath.appending(
components: ["Platform Checks", "\(platform).platform", "Developer", "SDKs", "\(sdk).sdk"])
var driver = try Driver(args: ["swiftc", "-typecheck", "foo.swift", "-sdk", VirtualPath.absolute(sdkRoot).name, "-plugin-path", "PluginA", "-external-plugin-path", "Plugin~B#Bexe", "-load-plugin-library", "PluginB2", "-plugin-path", "PluginC", "-working-directory", "/tmp"])
var driver = try Driver(args: ["swiftc", "-typecheck", "foo.swift", "-sdk", VirtualPath.absolute(sdkRoot).name, "-plugin-path", "PluginA", "-external-plugin-path", "Plugin~B#Bexe", "-load-plugin-library", "PluginB2", "-plugin-path", "PluginC", "-load-plugin-executable", "PluginD#PluginD", "-working-directory", "/tmp"])
guard driver.isFrontendArgSupported(.pluginPath) && driver.isFrontendArgSupported(.externalPluginPath) else {
return
}
Expand Down Expand Up @@ -7454,6 +7454,7 @@ final class SwiftDriverTests: XCTestCase {
#endif

XCTAssertTrue(job.commandLine.contains(.flag("-plugin-path")))
XCTAssertFalse(job.commandLine.contains(.flag("-load-plugin")))
#if os(Windows)
XCTAssertTrue(job.commandLine.contains(.path(.absolute(try driver.toolchain.executableDir.parentDirectory.appending(components: "bin")))))
#else
Expand All @@ -7462,6 +7463,40 @@ final class SwiftDriverTests: XCTestCase {
#endif
}

func testWasmPlugins() throws {
let sdkRoot = try testInputsPath.appending(
components: ["Platform Checks", "iPhoneOS.platform", "Developer", "SDKs", "iPhoneOS13.0.sdk"])
let executor = try SwiftDriverExecutor(
diagnosticsEngine: DiagnosticsEngine(handlers: [Driver.stderrDiagnosticsHandler]),
processSet: ProcessSet(),
fileSystem: localFileSystem,
env: [:]
)
let executableDir = try AbsolutePath(validating: "/tmp/swift/bin")
var driver = try Driver(
args: ["swiftc", "-typecheck", "foo.swift", "-sdk", VirtualPath.absolute(sdkRoot).name, "-load-plugin-executable", "PluginA.wasm#ModuleA", "-working-directory", "/tmp"],
executor: executor,
compilerExecutableDir: executableDir
)
guard driver.isFrontendArgSupported(.loadPlugin) else {
return
}

let jobs = try driver.planBuild().removingAutolinkExtractJobs()
XCTAssertEqual(jobs.count, 1)
let job = jobs.first!

#if os(Windows)
let expectedWasmServerPath = executableDir.appending(component: "swift-plugin-server.exe")
#else
let expectedWasmServerPath = executableDir.appending(component: "swift-plugin-server")
#endif

// Check that the we have the plugin path
let pluginAIndex = job.commandLine.firstIndex(of: .flag("/tmp/PluginA.wasm:\(expectedWasmServerPath)#ModuleA"))
XCTAssertNotNil(pluginAIndex)
}

func testClangModuleValidateOnce() throws {
let flagTest = try Driver(args: ["swiftc", "-typecheck", "foo.swift"])
guard flagTest.isFrontendArgSupported(.clangBuildSessionFile),
Expand Down