Skip to content

Add support for WebAssembly Macros #2623

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

Merged
merged 26 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
158ddd5
Rebase
kabiroberai Apr 28, 2024
385cc0a
Merge branch 'main' into kabir/wasm-plugins
kabiroberai Apr 30, 2024
4dd83b3
cleanup
kabiroberai Apr 30, 2024
0b6a5a6
We don’t need internalError
kabiroberai Apr 30, 2024
7a6b576
Comment
kabiroberai Apr 30, 2024
f5d82ef
Feedback
kabiroberai Apr 30, 2024
9f455cb
More feedback
kabiroberai Apr 30, 2024
18814f1
comment
kabiroberai Apr 30, 2024
a38baa4
comment
kabiroberai Apr 30, 2024
a582c2f
Merge branch 'main' into kabir/wasm-plugins
kabiroberai May 5, 2024
85e0374
Change wasm versioning approach
kabiroberai May 5, 2024
921b7ea
Merge branch 'main' into kabir/wasm-plugins
kabiroberai May 11, 2024
9fab7cc
Merge branch 'main' into kabir/wasm-plugins
kabiroberai May 25, 2024
d3495f2
Create PluginMessageHandler
kabiroberai May 25, 2024
76de84f
Make PluginMessage.Diagnostic.init SPI
kabiroberai May 25, 2024
76f7d8f
Use _expose
kabiroberai Jun 11, 2024
3e2e4d4
Merge branch 'main' into kabir/wasm-plugins
kabiroberai Jun 11, 2024
cf060a4
Feedback
kabiroberai Jun 12, 2024
4cd128c
Fix calling convention
kabiroberai Jun 12, 2024
84f3230
Update .spi.yml
kabiroberai Jun 13, 2024
2242b64
Format
kabiroberai Jun 13, 2024
054e5e3
Merge branch 'main' into kabir/wasm-plugins
kabiroberai Jun 14, 2024
595b0ae
re-format
kabiroberai Jun 18, 2024
c7466de
Merge branch 'main' into kabir/wasm-plugins
kabiroberai Jun 18, 2024
0a054ad
Merge branch 'main' into kabir/wasm-plugins
kabiroberai Jun 29, 2024
0e65034
Fix swiftlang/swift build
kabiroberai Jun 29, 2024
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
1 change: 1 addition & 0 deletions .spi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ builder:
- SwiftCompilerPluginMessageHandling
- SwiftDiagnostics
- SwiftIDEUtils
- SwiftLibraryPluginProvider
- SwiftOperators
- SwiftParser
- SwiftParserDiagnostics
Expand Down
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ let package = Package(
.library(name: "SwiftCompilerPluginMessageHandling", targets: ["SwiftCompilerPluginMessageHandling"]),
.library(name: "SwiftDiagnostics", targets: ["SwiftDiagnostics"]),
.library(name: "SwiftIDEUtils", targets: ["SwiftIDEUtils"]),
.library(name: "SwiftLibraryPluginProvider", targets: ["SwiftLibraryPluginProvider"]),
.library(name: "SwiftOperators", targets: ["SwiftOperators"]),
.library(name: "SwiftParser", targets: ["SwiftParser"]),
.library(name: "SwiftParserDiagnostics", targets: ["SwiftParserDiagnostics"]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,21 @@ struct HostCapability {
///
/// The low level connection and the provider is injected by the client.
@_spi(PluginMessage)
public class CompilerPluginMessageListener<Connection: MessageConnection, Provider: PluginProvider> {
public class CompilerPluginMessageListener<Connection: MessageConnection, Handler: PluginMessageHandler> {
/// Message channel for bidirectional communication with the plugin host.
let connection: Connection

let handler: CompilerPluginMessageHandler<Provider>
let handler: Handler

public init(connection: Connection, provider: Provider) {
public init(connection: Connection, messageHandler: Handler) {
self.connection = connection
self.handler = CompilerPluginMessageHandler(provider: provider)
self.handler = messageHandler
}

public init<Provider: PluginProvider>(connection: Connection, provider: Provider)
where Handler == PluginProviderMessageHandler<Provider> {
self.connection = connection
self.handler = PluginProviderMessageHandler(provider: provider)
}

/// Run the main message listener loop.
Expand All @@ -91,11 +97,26 @@ public class CompilerPluginMessageListener<Connection: MessageConnection, Provid
/// On internal errors, such as I/O errors or JSON serialization errors, print
/// an error message and `exit(1)`
public func main() {
#if os(WASI)
// Rather than blocking on read(), let the host tell us when there's data.
readabilityHandler = { _ = self.handleNextMessage() }
#else
while handleNextMessage() {}
#endif
}

/// Receives and handles a single message from the plugin host.
///
/// - Returns: `true` if there was a message to read, `false`
/// if the end-of-file was reached.
private func handleNextMessage() -> Bool {
do {
while let message = try connection.waitForNextMessage(HostToPluginMessage.self) {
let result = handler.handleMessage(message)
try connection.sendMessage(result)
guard let message = try connection.waitForNextMessage(HostToPluginMessage.self) else {
return false
}
let result = handler.handleMessage(message)
try connection.sendMessage(result)
return true
} catch {
// Emit a diagnostic and indicate failure to the plugin host,
// and exit with an error code.
Expand All @@ -105,10 +126,18 @@ public class CompilerPluginMessageListener<Connection: MessageConnection, Provid
}
}

/// 'CompilerPluginMessageHandler' is a type that handle a message and do the
/// corresponding operation.
/// A type that handles a plugin message and returns a response.
///
/// - SeeAlso: ``PluginProviderMessageHandler``
@_spi(PluginMessage)
public protocol PluginMessageHandler {
/// Handles a single message received from the plugin host.
func handleMessage(_ message: HostToPluginMessage) -> PluginToHostMessage
}
Comment on lines +132 to +136
Copy link
Member

Choose a reason for hiding this comment

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

I guess this abstraction is needed anyway, so this is okay. But TBH, I'm not a fan of WasmInterceptingMessageHandler in swiftlang/swift#73031.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I'm not the biggest fan either :/ but that's the only approach I could think of which allows us to send some messages from swift-plugin-server to LibraryPluginProvider while intercepting others. The alternative is to have an entirely separate swift-wasm-plugin-server (so that the primary plugin server can use LibraryPluginProvider directly) but both Doug and Yuta feel that having a unified plugin server is preferable (see swiftlang/swift#73031 (comment)), and I agree that the benefits outweigh the downsides atm.

Curious to hear if you have any alternative suggestions though.


/// A `PluginMessageHandler` that uses a `PluginProvider`.
@_spi(PluginMessage)
public class CompilerPluginMessageHandler<Provider: PluginProvider> {
public class PluginProviderMessageHandler<Provider: PluginProvider>: PluginMessageHandler {
/// Object to provide actual plugin functions.
let provider: Provider

Expand Down Expand Up @@ -212,3 +241,31 @@ extension PluginProvider {
throw UnimplementedError()
}
}

#if compiler(>=6) && os(WASI)

/// A callback invoked by the Wasm Host when new data is available on `stdin`.
///
/// This is safe to access without serialization as Wasm plugins are single-threaded.
nonisolated(unsafe) private var readabilityHandler: () -> Void = {
fatalError(
"""
CompilerPlugin.main wasn't called. Did you annotate your plugin with '@main'?
"""
)
}

@_expose(wasm,"swift_wasm_macro_v1_pump")
@_cdecl("swift_wasm_macro_v1_pump")
func wasmPump() {
readabilityHandler()
}

// we can't nest the whole #if-#else in '#if os(WASI)' due to a bug where
// '#if compiler' directives have to be the top-level #if, otherwise
// the compiler doesn't skip unknown syntax.
#elseif os(WASI)

#error("Building swift-syntax for WebAssembly requires compiler version 6.0 or higher.")

#endif
2 changes: 1 addition & 1 deletion Sources/SwiftCompilerPluginMessageHandling/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import SwiftSyntax
@_spi(ExperimentalLanguageFeature) import SwiftSyntaxMacros
#endif

extension CompilerPluginMessageHandler {
extension PluginProviderMessageHandler {
/// Get concrete macro type from a pair of module name and type name.
private func resolveMacro(_ ref: PluginMessage.MacroReference) throws -> Macro.Type {
try provider.resolveMacro(moduleName: ref.moduleName, typeName: ref.typeName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ public enum PluginMessage {
public var notes: [Note]
public var fixIts: [FixIt]

internal init(
public init(
message: String,
severity: Severity,
position: Position,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ public struct StandardIOMessageConnection: MessageConnection {
self.outputFileDescriptor = outputFileDescriptor
}

#if os(WASI)
/// Convenience initializer for Wasm executable plugins. Connects
/// directly to `stdin` and `stdout` as WASI doesn't support
/// `dup{,2}`.
public init() throws {
let inputFD = fileno(_stdin)
let outputFD = fileno(_stdout)
self.init(inputFileDescriptor: inputFD, outputFileDescriptor: outputFD)
}
#else
/// Convenience initializer for normal executable plugins. Upon creation:
/// - Redirect `stdout` to `stderr` so that print statements from the plugin
/// are treated as plain-text output
Expand Down Expand Up @@ -83,6 +93,7 @@ public struct StandardIOMessageConnection: MessageConnection {

self.init(inputFileDescriptor: inputFD, outputFileDescriptor: outputFD)
}
#endif

/// Write the buffer to the file descriptor. Throws an error on failure.
private func _write(contentsOf buffer: UnsafeRawBufferPointer) throws {
Expand Down