-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[Macros] Add support for wasm macros #73031
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
base: main
Are you sure you want to change the base?
Changes from 37 commits
e5c191d
e5342e7
d5d1cb7
f776c28
43fc233
961fd54
a771631
3f8ef81
23426e0
9fe505f
89158e2
f084e25
5ef86da
02f4324
bddde66
ca22e2d
6a9bbe7
e598064
7d1510e
37b8a61
da74f38
7a18da2
1076b16
805c659
4527842
f9dbbb1
6511672
dd481fe
4ce4749
482871c
771acb0
2460e98
150f3d2
34ed5b0
94d525e
a99f6b3
c5dc6bd
6375faf
826a80f
f053859
6c0b40c
d72e165
564f844
a670f32
aaaaae3
7b16cbd
7d9d015
ea81405
b472f4f
3baf627
eee48e0
4fca102
d465d36
c4efcee
b3f53c2
b629484
af2b87e
bbb916c
77c273f
b56e28a
94f0ad7
14b5b79
af68825
92aef4e
9b1e4ee
f93df4d
95f254c
ca27b45
caa1a8c
2ee417f
fa52ecd
f2f2ea2
c0a75e1
011a0a5
d2d3b94
e2a455a
b307eb4
782956a
8a88dc5
fcb838d
469d84a
cc41cf1
7499295
e89fd6a
2b47724
4285cf1
56b6cae
96363e1
913041b
6cd389e
c3f4276
4341058
393933a
9c5e7fc
5890c3e
0f39ec8
48dd97f
c3f41c7
ee82b85
1dcdd7b
6e56934
dd13597
5770553
94f870c
ed54400
d568d67
75bd7ca
c1e928d
0d2c140
7683e83
e0427b2
be04533
1852df9
e49c980
052058a
f860528
28a9efa
19eef9a
db325e8
54eb483
224ac09
dc8cd3c
6817517
f56ec6a
1626ad8
4204eef
1cf1e20
6277098
0da4f22
3991b60
636fa0e
f915c05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -116,8 +116,18 @@ PluginLoader::getPluginMap() { | |
case PluginSearchOption::Kind::LoadPluginExecutable: { | ||
auto &val = entry.get<PluginSearchOption::LoadPluginExecutable>(); | ||
assert(!val.ExecutablePath.empty() && "empty plugin path"); | ||
for (auto &moduleName : val.ModuleNames) { | ||
try_emplace(moduleName, /*libraryPath=*/"", val.ExecutablePath); | ||
if (llvm::sys::path::filename(val.ExecutablePath).ends_with(".wasm")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I recommend using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, when taking @kateinoigakukun 's advice, we won't need this code because the plugin server will handle it. |
||
// we treat wasm plugins like library plugins that can be loaded by an external | ||
// "wasm server" that in turn invokes the wasm runtime. | ||
const auto &wasmServerPath = Ctx.SearchPathOpts.PluginWasmServerPath; | ||
assert(!wasmServerPath.empty() && "wasm load requested but got empty wasm server path"); | ||
for (auto &moduleName : val.ModuleNames) { | ||
try_emplace(moduleName, val.ExecutablePath, wasmServerPath); | ||
} | ||
} else { | ||
for (auto &moduleName : val.ModuleNames) { | ||
try_emplace(moduleName, /*libraryPath=*/"", val.ExecutablePath); | ||
} | ||
} | ||
continue; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Package.resolved |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,17 @@ | ||
if (SWIFT_BUILD_SWIFT_SYNTAX) | ||
# hack: override macOS deployment target to 10.15.4 | ||
# since that's the minimum required by WasmKit | ||
set(SWIFT_DARWIN_DEPLOYMENT_VERSION_OSX "10.15.4") | ||
include(DarwinSDKs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll want to find a cleaner way to do this... add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also included this in swiftwasm/WasmKit#91 |
||
swift_get_host_triple(SWIFT_HOST_TRIPLE) | ||
|
||
# override the remote SwiftSystem and ArgParser dependencies in WasmKit | ||
FetchContent_Declare(SwiftSystem SOURCE_DIR "${PROJECT_SOURCE_DIR}/../swift-system") | ||
FetchContent_Declare(ArgumentParser SOURCE_DIR "${PROJECT_SOURCE_DIR}/../swift-argument-parser") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might end up having to do something more bespoke here to get things building on all of the platforms, but let's try this to start! |
||
|
||
FetchContent_Declare(WasmKit SOURCE_DIR "${PROJECT_SOURCE_DIR}/../wasmkit") | ||
FetchContent_MakeAvailable(WasmKit) | ||
|
||
# _swiftCSwiftPluginServer is just a C support library for swift-plugin-server | ||
# Don't bother to create '.a' for that. | ||
add_swift_host_library(_swiftCSwiftPluginServer OBJECT | ||
|
@@ -11,6 +24,16 @@ if (SWIFT_BUILD_SWIFT_SYNTAX) | |
Sources/CSwiftPluginServer/include | ||
) | ||
|
||
add_pure_swift_host_library(SwiftPluginServerSupport EMIT_MODULE | ||
Sources/SwiftPluginServerSupport/SwiftPluginServerSupport.swift | ||
DEPENDENCIES | ||
$<TARGET_OBJECTS:_swiftCSwiftPluginServer> | ||
SwiftCompilerPluginMessageHandling | ||
) | ||
target_include_directories(SwiftPluginServerSupport PRIVATE | ||
Sources/CSwiftPluginServer/include | ||
) | ||
|
||
add_pure_swift_host_tool(swift-plugin-server | ||
Sources/swift-plugin-server/swift-plugin-server.swift | ||
DEPENDENCIES | ||
|
@@ -23,8 +46,50 @@ if (SWIFT_BUILD_SWIFT_SYNTAX) | |
SwiftSyntaxMacroExpansion | ||
SwiftCompilerPluginMessageHandling | ||
swiftLLVMJSON | ||
SwiftPluginServerSupport | ||
) | ||
target_include_directories(swift-plugin-server PRIVATE | ||
Sources/CSwiftPluginServer/include | ||
) | ||
|
||
set(SWIFT_WASM_ALLOW_JSC ON CACHE BOOL "Use JavaScriptCore Wasm runtime if possible" FORCE) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wanted to leave this in for the initial review to show what the JSC runtime would look like, but I can remove this before we do a final pass since Doug mentioned that it's better to stick to a uniform implementation to begin with, for simplicity. I think it's definitely worth revisiting this in a follow-up because the JSC runtime is a lot faster than WasmKit when it can be used (500ms vs 1100ms in some cases I've tested.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that's a pretty big time difference. (I still prefer "make it work across platforms, then add in more optimizations afterward") There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Split out the JSC stuff into a separate branch, happy to make a followup PR when we're ready for JSC: kabiroberai/swift@kabir/wasm-plugins...kabir/jsc-wasm |
||
|
||
if(SWIFT_HOST_VARIANT_SDK IN_LIST SWIFT_DARWIN_PLATFORMS) | ||
set(swift_wasm_use_jsc ${SWIFT_WASM_ALLOW_JSC}) | ||
else() | ||
set(swift_wasm_use_jsc OFF) | ||
endif() | ||
|
||
if (swift_wasm_use_jsc) | ||
set(wasi_dep WASI) | ||
set(jsc_defines SWIFT_WASM_USE_JSC) | ||
else() | ||
set(wasi_dep WasmKitWASI) | ||
set(jsc_defines) | ||
endif() | ||
|
||
add_pure_swift_host_tool(swift-wasm-plugin-server | ||
Sources/swift-wasm-plugin-server/swift-wasm-plugin-server.swift | ||
Sources/swift-wasm-plugin-server/JSCWasmEngine.swift | ||
Sources/swift-wasm-plugin-server/WasmEngine.swift | ||
Sources/swift-wasm-plugin-server/WasmKitEngine.swift | ||
DEPENDENCIES | ||
$<TARGET_OBJECTS:_swiftCSwiftPluginServer> | ||
SWIFT_COMPONENT | ||
compiler | ||
SWIFT_DEPENDENCIES | ||
SwiftCompilerPluginMessageHandling | ||
swiftLLVMJSON | ||
SwiftPluginServerSupport | ||
${wasi_dep} | ||
) | ||
target_compile_definitions(swift-wasm-plugin-server PRIVATE ${jsc_defines}) | ||
target_include_directories(swift-wasm-plugin-server PRIVATE | ||
Sources/CSwiftPluginServer/include | ||
) | ||
if (swift_wasm_use_jsc) | ||
add_custom_command(TARGET swift-wasm-plugin-server POST_BUILD COMMAND | ||
codesign -fs - $<TARGET_FILE:swift-wasm-plugin-server> --entitlements ${CMAKE_CURRENT_SOURCE_DIR}/allow-jit.plist | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure if this is sufficient (works in dev but I haven't tested a packaged toolchain.) do we need a corresponding codesign invocation in build_script as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We would need to code sign in build-script, but I don't know if there are other steps. This is another reason I'd like to start with the simpler WasmKit integration. |
||
) | ||
endif() | ||
endif() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,68 @@ | ||
// swift-tools-version: 5.6 | ||
// swift-tools-version: 5.9 | ||
|
||
import PackageDescription | ||
|
||
let allowJSC = true | ||
|
||
let package = Package( | ||
name: "swift-plugin-server", | ||
platforms: [ | ||
.macOS(.v10_15) | ||
.macOS(.v11), | ||
], | ||
products: [ | ||
.library(name: "swift-plugin-server", targets: ["swift-plugin-server"]), | ||
.library(name: "swift-wasm-plugin-server", targets: ["swift-wasm-plugin-server"]), | ||
], | ||
dependencies: [ | ||
.package(path: "../../../swift-syntax"), | ||
.package(path: "../../lib/ASTGen"), | ||
.package(path: "../../../wasmkit"), | ||
], | ||
targets: [ | ||
.target( | ||
name: "CSwiftPluginServer", | ||
cxxSettings: [ | ||
.unsafeFlags([ | ||
"-I", "../../include", | ||
"-I", "../../stdlib/public/SwiftShims", | ||
"-I", "../../../build/Default/swift/include", | ||
"-I", "../../../build/Default/llvm/include", | ||
"-I", "../../../llvm-project/llvm/include", | ||
]) | ||
] | ||
), | ||
.executableTarget( | ||
name: "swift-plugin-server", | ||
.target( | ||
name: "SwiftPluginServerSupport", | ||
dependencies: [ | ||
.product(name: "swiftLLVMJSON", package: "ASTGen"), | ||
.product(name: "SwiftCompilerPluginMessageHandling", package: "swift-syntax"), | ||
.product(name: "SwiftDiagnostics", package: "swift-syntax"), | ||
.product(name: "SwiftSyntax", package: "swift-syntax"), | ||
.product(name: "SwiftOperators", package: "swift-syntax"), | ||
.product(name: "SwiftParser", package: "swift-syntax"), | ||
"CSwiftPluginServer", | ||
], | ||
swiftSettings: [.interoperabilityMode(.Cxx)] | ||
), | ||
.target( | ||
name: "swift-plugin-server", | ||
dependencies: [ | ||
.product(name: "SwiftCompilerPluginMessageHandling", package: "swift-syntax"), | ||
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"), | ||
"CSwiftPluginServer" | ||
] | ||
"CSwiftPluginServer", | ||
"SwiftPluginServerSupport", | ||
], | ||
swiftSettings: [.interoperabilityMode(.Cxx)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using C++ interoperability here is driven by JSC, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. iirc this was required for swiftLLVMJSON but I was able to change the interop mode back to C now that we're using SwiftSyntax's JSON parser |
||
), | ||
.target( | ||
name: "swift-wasm-plugin-server", | ||
dependencies: [ | ||
.product(name: "swiftLLVMJSON", package: "ASTGen"), | ||
.product(name: "SwiftCompilerPluginMessageHandling", package: "swift-syntax"), | ||
"CSwiftPluginServer", | ||
"SwiftPluginServerSupport", | ||
.product(name: "WASI", package: "WasmKit"), | ||
.product(name: "WasmKitWASI", package: "WasmKit", condition: .when(platforms: [.linux, .windows])), | ||
], | ||
swiftSettings: [.interoperabilityMode(.Cxx)] + ( | ||
allowJSC ? [.define("SWIFT_WASM_USE_JSC", .when(platforms: [.macOS]))] : [] | ||
) | ||
), | ||
], | ||
cxxLanguageStandard: .cxx17 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
@_spi(PluginMessage) import SwiftCompilerPluginMessageHandling | ||
import swiftLLVMJSON | ||
import CSwiftPluginServer | ||
|
||
@_spi(PluginMessage) public final class PluginHostConnection: MessageConnection { | ||
let handle: UnsafeRawPointer | ||
public init() throws { | ||
var errorMessage: UnsafePointer<CChar>? = nil | ||
guard let handle = PluginServer_createConnection(&errorMessage) else { | ||
throw PluginServerError(message: String(cString: errorMessage!)) | ||
} | ||
self.handle = handle | ||
} | ||
|
||
deinit { | ||
PluginServer_destroyConnection(self.handle) | ||
} | ||
|
||
public func sendMessage<TX: Encodable>(_ message: TX) throws { | ||
try LLVMJSON.encoding(message) { buffer in | ||
try self.sendMessageData(buffer) | ||
} | ||
} | ||
|
||
public func waitForNextMessage<RX: Decodable>(_ type: RX.Type) throws -> RX? { | ||
return try self.withReadingMessageData { jsonData in | ||
try LLVMJSON.decode(RX.self, from: jsonData) | ||
} | ||
} | ||
|
||
/// Send a serialized message to the message channel. | ||
private func sendMessageData(_ data: UnsafeBufferPointer<Int8>) throws { | ||
// Write the header (a 64-bit length field in little endian byte order). | ||
var header: UInt64 = UInt64(data.count).littleEndian | ||
let writtenSize = try Swift.withUnsafeBytes(of: &header) { buffer in | ||
try self.write(buffer: UnsafeRawBufferPointer(buffer)) | ||
} | ||
guard writtenSize == MemoryLayout.size(ofValue: header) else { | ||
throw PluginServerError(message: "failed to write message header") | ||
} | ||
|
||
// Write the body. | ||
guard try self.write(buffer: UnsafeRawBufferPointer(data)) == data.count else { | ||
throw PluginServerError(message: "failed to write message body") | ||
} | ||
} | ||
|
||
/// Read a serialized message from the message channel and call the 'body' | ||
/// with the data. | ||
private func withReadingMessageData<R>(_ body: (UnsafeBufferPointer<Int8>) throws -> R) throws -> R? { | ||
// Read the header (a 64-bit length field in little endian byte order). | ||
var header: UInt64 = 0 | ||
let readSize = try Swift.withUnsafeMutableBytes(of: &header) { buffer in | ||
try self.read(into: UnsafeMutableRawBufferPointer(buffer)) | ||
} | ||
guard readSize == MemoryLayout.size(ofValue: header) else { | ||
if readSize == 0 { | ||
// The host closed the pipe. | ||
return nil | ||
} | ||
// Otherwise, some error happened. | ||
throw PluginServerError(message: "failed to read message header") | ||
} | ||
|
||
// Read the body. | ||
let count = Int(UInt64(littleEndian: header)) | ||
let data = UnsafeMutableBufferPointer<Int8>.allocate(capacity: count) | ||
defer { data.deallocate() } | ||
guard try self.read(into: UnsafeMutableRawBufferPointer(data)) == count else { | ||
throw PluginServerError(message: "failed to read message body") | ||
} | ||
|
||
// Invoke the handler. | ||
return try body(UnsafeBufferPointer(data)) | ||
} | ||
|
||
/// Write the 'buffer' to the message channel. | ||
/// Returns the number of bytes succeeded to write. | ||
private func write(buffer: UnsafeRawBufferPointer) throws -> Int { | ||
var bytesToWrite = buffer.count | ||
guard bytesToWrite > 0 else { | ||
return 0 | ||
} | ||
var ptr = buffer.baseAddress! | ||
|
||
while (bytesToWrite > 0) { | ||
let writtenSize = PluginServer_write(handle, ptr, bytesToWrite) | ||
if (writtenSize <= 0) { | ||
// error e.g. broken pipe. | ||
break | ||
} | ||
ptr = ptr.advanced(by: writtenSize) | ||
bytesToWrite -= Int(writtenSize) | ||
} | ||
return buffer.count - bytesToWrite | ||
} | ||
|
||
/// Read data from the message channel into the 'buffer' up to 'buffer.count' bytes. | ||
/// Returns the number of bytes succeeded to read. | ||
private func read(into buffer: UnsafeMutableRawBufferPointer) throws -> Int { | ||
var bytesToRead = buffer.count | ||
guard bytesToRead > 0 else { | ||
return 0 | ||
} | ||
var ptr = buffer.baseAddress! | ||
|
||
while bytesToRead > 0 { | ||
let readSize = PluginServer_read(handle, ptr, bytesToRead) | ||
if (readSize <= 0) { | ||
// 0: EOF (the host closed), -1: Broken pipe (the host crashed?) | ||
break; | ||
} | ||
ptr = ptr.advanced(by: readSize) | ||
bytesToRead -= readSize | ||
} | ||
return buffer.count - bytesToRead | ||
} | ||
} | ||
|
||
public struct PluginServerError: Error, CustomStringConvertible { | ||
public var description: String | ||
public init(message: String) { | ||
self.description = message | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we merge wasm plugin server into the main plugin server? Having different frontend options for each mode complicates build systems, so it would be nice to unify the plugin servers into single one and switch underlying plugin mechanisms based on plugin extension or file magic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I like @kateinoigakukun's suggestion here. Let's bring wasm support into the main plugin server, so other tools around this (driver, build systems, etc.) that reason about macros don't need to change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a few concerns about bringing wasm into the main plugin server, curious to hear what you folks think:
-load-plugin-executable Lib.wasm#Module
, with the server being an impl detail. Meanwhile bona fide server plugins are invoked with-external-plugin-path Lib#Exec
, and notably A) this doesn't allow supplying an external module name, B) the client would have to locate the executable themselves, as afaik the Driver only does the locating for a pre-existing set of modules. Perhaps the solution to this would be to A) add a-load-external-plugin-with-module Lib#Exec#Module
to the driver and B) Let the driver convert-load-plugin-executable Lib.wasm#Module
to-load-external-plugin-with-module Lib.wasm#<swift-plugin-server>#Module
? Would be nice to have the-load-external-plugin-with-module
god-option anyway.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, once we will conclude that we cannot satisfy performance expectations without JIT, we need a separate process to minimize such risks, but let's start in a simpler way
-load-plugin <library path>:<server path>#<module name>,...
in the current tree seems good to me