From 653deb5cb4b05cdc3b913fd588d712b489f1bff4 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 17 Mar 2025 23:59:04 +0000 Subject: [PATCH 01/13] Remove `swjs_library_version` This function was used to check the compatibility between the JavaScriptKit Swift library and JS runtime because they were manually kept in sync by users of the library with npm and SwiftPM. Now that the library is distributed as a single SwiftPM package, there is no need for this function. --- Runtime/src/index.ts | 8 -------- Runtime/src/types.ts | 1 - Sources/JavaScriptKit/Runtime/index.js | 4 ---- Sources/JavaScriptKit/Runtime/index.mjs | 4 ---- Sources/_CJavaScriptKit/_CJavaScriptKit.c | 7 ------- 5 files changed, 24 deletions(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 3f23ed753..64c1225e0 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -56,14 +56,6 @@ export class SwiftRuntime { ` ); } - if (this.exports.swjs_library_version() != this.version) { - throw new Error( - `The versions of JavaScriptKit are incompatible. - WebAssembly runtime ${this.exports.swjs_library_version()} != JS runtime ${ - this.version - }` - ); - } } main() { diff --git a/Runtime/src/types.ts b/Runtime/src/types.ts index 587b60770..e6aa99363 100644 --- a/Runtime/src/types.ts +++ b/Runtime/src/types.ts @@ -7,7 +7,6 @@ export type JavaScriptValueKind = number; export type JavaScriptValueKindAndFlags = number; export interface ExportedFunctions { - swjs_library_version(): number; swjs_library_features(): number; swjs_prepare_host_function_call(size: number): pointer; swjs_cleanup_host_function_call(argv: pointer): void; diff --git a/Sources/JavaScriptKit/Runtime/index.js b/Sources/JavaScriptKit/Runtime/index.js index 25b6af3c9..696a7df9d 100644 --- a/Sources/JavaScriptKit/Runtime/index.js +++ b/Sources/JavaScriptKit/Runtime/index.js @@ -328,10 +328,6 @@ -Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor `); } - if (this.exports.swjs_library_version() != this.version) { - throw new Error(`The versions of JavaScriptKit are incompatible. - WebAssembly runtime ${this.exports.swjs_library_version()} != JS runtime ${this.version}`); - } } main() { const instance = this.instance; diff --git a/Sources/JavaScriptKit/Runtime/index.mjs b/Sources/JavaScriptKit/Runtime/index.mjs index 668368203..a18d90c37 100644 --- a/Sources/JavaScriptKit/Runtime/index.mjs +++ b/Sources/JavaScriptKit/Runtime/index.mjs @@ -322,10 +322,6 @@ class SwiftRuntime { -Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor `); } - if (this.exports.swjs_library_version() != this.version) { - throw new Error(`The versions of JavaScriptKit are incompatible. - WebAssembly runtime ${this.exports.swjs_library_version()} != JS runtime ${this.version}`); - } } main() { const instance = this.instance; diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c index ed8240ca1..aaf3f69f0 100644 --- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c +++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c @@ -13,13 +13,6 @@ extern void *memcpy (void *__restrict, const void *__restrict, size_t); #include #endif -/// The compatibility runtime library version. -/// Notes: If you change any interface of runtime library, please increment -/// this and `SwiftRuntime.version` in `./Runtime/src/index.ts`. -__attribute__((export_name("swjs_library_version"))) -int swjs_library_version(void) { - return 708; -} __attribute__((export_name("swjs_prepare_host_function_call"))) void *swjs_prepare_host_function_call(const int argc) { From 13362700e6e7f9f732a62fb72f9627178aa6f480 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 18 Mar 2025 00:04:42 +0000 Subject: [PATCH 02/13] Expose `swjs_library_features` from Swift side We now support only Swift 6.0 and above, so we can use `@_expose(wasm)` instead of `__attribute__((export_name))` and `@_cdecl` hack. --- Sources/JavaScriptKit/Features.swift | 12 ++++-------- Sources/_CJavaScriptKit/_CJavaScriptKit.c | 7 ------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/Sources/JavaScriptKit/Features.swift b/Sources/JavaScriptKit/Features.swift index db6e00f26..4148f2adb 100644 --- a/Sources/JavaScriptKit/Features.swift +++ b/Sources/JavaScriptKit/Features.swift @@ -2,17 +2,13 @@ enum LibraryFeatures { static let weakRefs: Int32 = 1 << 0 } -@_cdecl("_library_features") -func _library_features() -> Int32 { +@_expose(wasm, "swjs_library_features") +@_cdecl("_swjs_library_features") +@available(*, unavailable) +public func _swjs_library_features() -> Int32 { var features: Int32 = 0 #if !JAVASCRIPTKIT_WITHOUT_WEAKREFS features |= LibraryFeatures.weakRefs #endif return features } - -#if compiler(>=6.0) && hasFeature(Embedded) -// cdecls currently don't work in embedded, and expose for wasm only works >=6.0 -@_expose(wasm, "swjs_library_features") -public func _swjs_library_features() -> Int32 { _library_features() } -#endif \ No newline at end of file diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c index aaf3f69f0..67a83cab0 100644 --- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c +++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c @@ -46,13 +46,6 @@ void swjs_free_host_function(const JavaScriptHostFuncRef host_func_ref) { _free_host_function_impl(host_func_ref); } -int _library_features(void); - -__attribute__((export_name("swjs_library_features"))) -int swjs_library_features(void) { - return _library_features(); -} - int swjs_get_worker_thread_id_cached(void) { _Thread_local static int tid = 0; if (tid == 0) { From 396899c2d2e1c20fa37aa0a672e4d02fb00a3807 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 18 Mar 2025 00:23:36 +0000 Subject: [PATCH 03/13] Remove remaining export_name + cdecl hacks --- .../_thingsThatShouldNotBeNeeded.swift | 7 ---- .../FundamentalObjects/JSClosure.swift | 36 +++++++------------ Sources/_CJavaScriptKit/_CJavaScriptKit.c | 18 ---------- 3 files changed, 12 insertions(+), 49 deletions(-) diff --git a/Examples/Embedded/Sources/EmbeddedApp/_thingsThatShouldNotBeNeeded.swift b/Examples/Embedded/Sources/EmbeddedApp/_thingsThatShouldNotBeNeeded.swift index 8f45ccee9..a5da489dc 100644 --- a/Examples/Embedded/Sources/EmbeddedApp/_thingsThatShouldNotBeNeeded.swift +++ b/Examples/Embedded/Sources/EmbeddedApp/_thingsThatShouldNotBeNeeded.swift @@ -1,12 +1,5 @@ import JavaScriptKit -// NOTE: it seems the embedded tree shaker gets rid of these exports if they are not used somewhere -func _i_need_to_be_here_for_wasm_exports_to_work() { - _ = _swjs_library_features - _ = _swjs_call_host_function - _ = _swjs_free_host_function -} - // TODO: why do I need this? and surely this is not ideal... figure this out, or at least have this come from a C lib @_cdecl("strlen") func strlen(_ s: UnsafePointer) -> Int { diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift index 261b5b5cb..e56713483 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift @@ -197,8 +197,10 @@ private func makeAsyncClosure( // └─────────────────────┴──────────────────────────┘ /// Returns true if the host function has been already released, otherwise false. -@_cdecl("_call_host_function_impl") -func _call_host_function_impl( +@_expose(wasm, "swjs_call_host_function") +@_cdecl("_swjs_call_host_function") +@available(*, unavailable) +public func _swjs_call_host_function( _ hostFuncRef: JavaScriptHostFuncRef, _ argv: UnsafePointer, _ argc: Int32, _ callbackFuncRef: JavaScriptObjectRef @@ -231,9 +233,10 @@ extension JSClosure { } } - -@_cdecl("_free_host_function_impl") -func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) {} +@_expose(wasm, "swjs_free_host_function") +@_cdecl("_swjs_free_host_function") +@available(*, unavailable) +func _swjs_free_host_function(_ hostFuncRef: JavaScriptHostFuncRef) {} #else @@ -244,25 +247,10 @@ extension JSClosure { } -@_cdecl("_free_host_function_impl") -func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) { - JSClosure.sharedClosures.wrappedValue[hostFuncRef] = nil -} -#endif - -#if compiler(>=6.0) && hasFeature(Embedded) -// cdecls currently don't work in embedded, and expose for wasm only works >=6.0 -@_expose(wasm, "swjs_call_host_function") -public func _swjs_call_host_function( - _ hostFuncRef: JavaScriptHostFuncRef, - _ argv: UnsafePointer, _ argc: Int32, - _ callbackFuncRef: JavaScriptObjectRef) -> Bool { - - _call_host_function_impl(hostFuncRef, argv, argc, callbackFuncRef) -} - @_expose(wasm, "swjs_free_host_function") -public func _swjs_free_host_function(_ hostFuncRef: JavaScriptHostFuncRef) { - _free_host_function_impl(hostFuncRef) +@_cdecl("_swjs_free_host_function") +@available(*, unavailable) +func _swjs_free_host_function(_ hostFuncRef: JavaScriptHostFuncRef) { + JSClosure.sharedClosures.wrappedValue[hostFuncRef] = nil } #endif diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c index 67a83cab0..ed52a55f6 100644 --- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c +++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c @@ -28,24 +28,6 @@ void swjs_cleanup_host_function_call(void *argv_buffer) { // cdecls don't work in Embedded, but @_expose(wasm) can be used with Swift >=6.0 // the previously used `#if __Embedded` did not play well with SwiftPM (defines needed to be on every target up the chain) #ifdef __wasi__ -bool _call_host_function_impl(const JavaScriptHostFuncRef host_func_ref, - const RawJSValue *argv, const int argc, - const JavaScriptObjectRef callback_func); - -__attribute__((export_name("swjs_call_host_function"))) -bool swjs_call_host_function(const JavaScriptHostFuncRef host_func_ref, - const RawJSValue *argv, const int argc, - const JavaScriptObjectRef callback_func) { - return _call_host_function_impl(host_func_ref, argv, argc, callback_func); -} - -void _free_host_function_impl(const JavaScriptHostFuncRef host_func_ref); - -__attribute__((export_name("swjs_free_host_function"))) -void swjs_free_host_function(const JavaScriptHostFuncRef host_func_ref) { - _free_host_function_impl(host_func_ref); -} - int swjs_get_worker_thread_id_cached(void) { _Thread_local static int tid = 0; if (tid == 0) { From e907e5cd7f4a8d772da5d5288698ef05cac07f88 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 18 Mar 2025 12:12:03 +0900 Subject: [PATCH 04/13] Add more hints for common build failures --- .../Sources/PackageToJSPlugin.swift | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index 2844d52ec..269e45cf0 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -19,8 +19,8 @@ struct PackageToJSPlugin: CommandPlugin { // In case user misses the `--swift-sdk` option { build, arguments in guard - build.logText.contains( - "ld.gold: --export-if-defined=__main_argc_argv: unknown option") + build.logText.contains("ld.gold: --export-if-defined=__main_argc_argv: unknown option") || + build.logText.contains("-static-stdlib is no longer supported for Apple platforms") else { return nil } let didYouMean = [ @@ -53,13 +53,26 @@ struct PackageToJSPlugin: CommandPlugin { 3. Select a matching SDK version with --swift-sdk option """ }), + ( + // In case selected toolchain is a Xcode toolchain, not OSS toolchain + { build, arguments in + guard build.logText.contains("No available targets are compatible with triple \"wasm32-unknown-wasi\"") else { + return nil + } + return """ + The selected toolchain might be an Xcode toolchain, which doesn't support WebAssembly target. + + Please use a swift.org Open Source toolchain with WebAssembly support. + See https://book.swiftwasm.org/getting-started/setup.html for more information. + """ + }), ] private func reportBuildFailure( _ build: PackageManager.BuildResult, _ arguments: [String] ) { for diagnostic in Self.friendlyBuildDiagnostics { if let message = diagnostic(build, arguments) { - printStderr("\n" + message) + printStderr("\n" + "Hint: " + message) } } } From 66863cf4c977dd3cce3c0161cdcedad308746287 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 18 Mar 2025 12:20:45 +0900 Subject: [PATCH 05/13] PackageToJS: Highlight hint messages in bold --- Plugins/PackageToJS/Sources/PackageToJSPlugin.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index 269e45cf0..72be4655b 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -72,7 +72,7 @@ struct PackageToJSPlugin: CommandPlugin { ) { for diagnostic in Self.friendlyBuildDiagnostics { if let message = diagnostic(build, arguments) { - printStderr("\n" + "Hint: " + message) + printStderr("\n" + "\u{001B}[1m\u{001B}[97mHint:\u{001B}[0m " + message) } } } From 8719580c1d2a3961e9cb45a0015f63f7219a610f Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 18 Mar 2025 12:25:15 +0900 Subject: [PATCH 06/13] [skip ci] Fix usage instructions for PackageToJS plugin --- .../PackageToJS/Sources/PackageToJSPlugin.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index 72be4655b..b1fd75a3b 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -333,7 +333,7 @@ extension PackageToJS.BuildOptions { return """ OVERVIEW: Builds a JavaScript module from a Swift package. - USAGE: swift package --swift-sdk [SwiftPM options] PackageToJS [options] [subcommand] + USAGE: swift package --swift-sdk [SwiftPM options] js [options] [subcommand] OPTIONS: --product Product to build (default: executable target if there's only one) @@ -349,14 +349,14 @@ extension PackageToJS.BuildOptions { test Builds and runs tests EXAMPLES: - $ swift package --swift-sdk wasm32-unknown-wasi plugin js + $ swift package --swift-sdk wasm32-unknown-wasi js # Build a specific product - $ swift package --swift-sdk wasm32-unknown-wasi plugin js --product Example + $ swift package --swift-sdk wasm32-unknown-wasi js --product Example # Build in release configuration $ swift package --swift-sdk wasm32-unknown-wasi -c release plugin js # Run tests - $ swift package --swift-sdk wasm32-unknown-wasi plugin js test + $ swift package --swift-sdk wasm32-unknown-wasi js test """ } } @@ -391,7 +391,7 @@ extension PackageToJS.TestOptions { return """ OVERVIEW: Builds and runs tests - USAGE: swift package --swift-sdk [SwiftPM options] PackageToJS test [options] + USAGE: swift package --swift-sdk [SwiftPM options] js test [options] OPTIONS: --build-only Whether to build only (default: false) @@ -404,10 +404,10 @@ extension PackageToJS.TestOptions { -Xnode Extra arguments to pass to Node.js EXAMPLES: - $ swift package --swift-sdk wasm32-unknown-wasi plugin js test - $ swift package --swift-sdk wasm32-unknown-wasi plugin js test --environment browser + $ swift package --swift-sdk wasm32-unknown-wasi js test + $ swift package --swift-sdk wasm32-unknown-wasi js test --environment browser # Just build tests, don't run them - $ swift package --swift-sdk wasm32-unknown-wasi plugin js test --build-only + $ swift package --swift-sdk wasm32-unknown-wasi js test --build-only $ node .build/plugins/PackageToJS/outputs/PackageTests/bin/test.js """ } From fc5d334312b1c8b744b9491973917d2d094dec4c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 18 Mar 2025 12:27:46 +0900 Subject: [PATCH 07/13] [skip ci] Update options help message --- .../Sources/PackageToJSPlugin.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index b1fd75a3b..3795ffaa4 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -339,10 +339,10 @@ extension PackageToJS.BuildOptions { --product Product to build (default: executable target if there's only one) --output Path to the output directory (default: .build/plugins/PackageToJS/outputs/Package) --package-name Name of the package (default: lowercased Package.swift name) - --explain Whether to explain the build plan (default: false) - --no-optimize Whether to disable wasm-opt optimization (default: false) - --use-cdn Whether to use CDN for dependency packages (default: false) - --enable-code-coverage Whether to enable code coverage collection (default: false) + --explain Whether to explain the build plan + --no-optimize Whether to disable wasm-opt optimization + --use-cdn Whether to use CDN for dependency packages + --enable-code-coverage Whether to enable code coverage collection --debug-info-format The format of debug info to keep in the final wasm file (values: none, dwarf, name; default: none) SUBCOMMANDS: @@ -394,13 +394,13 @@ extension PackageToJS.TestOptions { USAGE: swift package --swift-sdk [SwiftPM options] js test [options] OPTIONS: - --build-only Whether to build only (default: false) + --build-only Whether to build only --prelude Path to the prelude script - --environment The environment to use for the tests + --environment The environment to use for the tests (values: node, browser; default: node) --inspect Whether to run tests in the browser with inspector enabled - --use-cdn Whether to use CDN for dependency packages (default: false) - --enable-code-coverage Whether to enable code coverage collection (default: false) - --verbose Whether to print verbose output (default: false) + --use-cdn Whether to use CDN for dependency packages + --enable-code-coverage Whether to enable code coverage collection + --verbose Whether to print verbose output -Xnode Extra arguments to pass to Node.js EXAMPLES: From f12b41a26a55cc3d8acb7edc9190146c4da84228 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 18 Mar 2025 13:16:40 +0900 Subject: [PATCH 08/13] PackageToJS: Emit hints for permission denied errors --- .../Sources/PackageToJSPlugin.swift | 65 ++++++++++++++++--- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index 3795ffaa4..7dc2a6841 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -5,6 +5,7 @@ @preconcurrency import class Foundation.Process @preconcurrency import class Foundation.ProcessInfo @preconcurrency import class Foundation.FileManager +@preconcurrency import struct Foundation.CocoaError @preconcurrency import func Foundation.fputs @preconcurrency import func Foundation.exit @preconcurrency import var Foundation.stderr @@ -27,10 +28,10 @@ struct PackageToJSPlugin: CommandPlugin { "swift", "package", "--swift-sdk", "wasm32-unknown-wasi", "js", ] + arguments return """ - Please pass the `--swift-sdk` option to the "swift package" command. + Please pass `--swift-sdk` to "swift package". - Did you mean: - \(didYouMean.joined(separator: " ")) + Did you mean this? + \(didYouMean.joined(separator: " ")) """ }), ( @@ -67,22 +68,70 @@ struct PackageToJSPlugin: CommandPlugin { """ }), ] + + private func emitHintMessage(_ message: String) { + printStderr("\n" + "\u{001B}[1m\u{001B}[97mHint:\u{001B}[0m " + message) + } + private func reportBuildFailure( _ build: PackageManager.BuildResult, _ arguments: [String] ) { for diagnostic in Self.friendlyBuildDiagnostics { if let message = diagnostic(build, arguments) { - printStderr("\n" + "\u{001B}[1m\u{001B}[97mHint:\u{001B}[0m " + message) + emitHintMessage(message) + return } } } func performCommand(context: PluginContext, arguments: [String]) throws { - if arguments.first == "test" { - return try performTestCommand(context: context, arguments: Array(arguments.dropFirst())) - } + do { + if arguments.first == "test" { + return try performTestCommand(context: context, arguments: Array(arguments.dropFirst())) + } + + return try performBuildCommand(context: context, arguments: arguments) + } catch let error as CocoaError where error.code == .fileWriteNoPermission { + guard let filePath = error.filePath else { throw error } - return try performBuildCommand(context: context, arguments: arguments) + let packageDir = context.package.directoryURL + printStderr("\n\u{001B}[1m\u{001B}[91merror:\u{001B}[0m \(error.localizedDescription)") + + if filePath.hasPrefix(packageDir.path) { + // Emit hint for --allow-writing-to-package-directory if the destination path + // is under the package directory + let didYouMean = [ + "swift", "package", "--swift-sdk", "wasm32-unknown-wasi", + "plugin", "--allow-writing-to-package-directory", + "js", + ] + arguments + emitHintMessage( + """ + Please pass `--allow-writing-to-package-directory` to "swift package". + + Did you mean this? + \(didYouMean.joined(separator: " ")) + """ + ) + } else { + // Emit hint for --allow-writing-to-directory + // if the destination path is outside the package directory + let didYouMean = [ + "swift", "package", "--swift-sdk", "wasm32-unknown-wasi", + "plugin", "--allow-writing-to-directory", "\(filePath)", + "js", + ] + arguments + emitHintMessage( + """ + Please pass `--allow-writing-to-directory ` to "swift package". + + Did you mean this? + \(didYouMean.joined(separator: " ")) + """ + ) + } + exit(1) + } } static let JAVASCRIPTKIT_PACKAGE_ID: Package.ID = "javascriptkit" From b787af2646bccb5391918dedd4a2479707ccbf10 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 18 Mar 2025 08:11:47 +0000 Subject: [PATCH 09/13] Make `JSObject.id` getter inlinable This change makes `JSObject.id` getter inlinable. Those who use the API to avoid dynamic member lookup overhead can now benefit from the inlinable getter. --- .../JavaScriptKit/FundamentalObjects/JSClosure.swift | 4 ++-- .../JavaScriptKit/FundamentalObjects/JSObject.swift | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift index 261b5b5cb..828cb40f2 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift @@ -21,7 +21,7 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol { // 2. Create a new JavaScript function which calls the given Swift function. hostFuncRef = JavaScriptHostFuncRef(bitPattern: ObjectIdentifier(self)) - id = withExtendedLifetime(JSString(file)) { file in + _id = withExtendedLifetime(JSString(file)) { file in swjs_create_function(hostFuncRef, line, file.asInternalJSRef()) } @@ -105,7 +105,7 @@ public class JSClosure: JSFunction, JSClosureProtocol { // 2. Create a new JavaScript function which calls the given Swift function. hostFuncRef = JavaScriptHostFuncRef(bitPattern: ObjectIdentifier(self)) - id = withExtendedLifetime(JSString(file)) { file in + _id = withExtendedLifetime(JSString(file)) { file in swjs_create_function(hostFuncRef, line, file.asInternalJSRef()) } diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 0958b33f4..9006ec7b7 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -19,16 +19,20 @@ public class JSObject: Equatable { internal static var constructor: JSFunction { _constructor.wrappedValue } private static let _constructor = LazyThreadLocal(initialize: { JSObject.global.Object.function! }) - @_spi(JSObject_id) - public var id: JavaScriptObjectRef + @usableFromInline + internal var _id: JavaScriptObjectRef #if compiler(>=6.1) && _runtime(_multithreaded) package let ownerTid: Int32 #endif + @_spi(JSObject_id) + @inlinable + public var id: JavaScriptObjectRef { _id } + @_spi(JSObject_id) public init(id: JavaScriptObjectRef) { - self.id = id + self._id = id #if compiler(>=6.1) && _runtime(_multithreaded) self.ownerTid = swjs_get_worker_thread_id_cached() #endif From d6eecb07b6f022281c4bd21294c4b9f5a704f904 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 18 Mar 2025 12:57:43 +0000 Subject: [PATCH 10/13] Revert "Merge pull request #304 from swiftwasm/yt/remove-cdecls" This reverts commit 1de7accbe8fc24cbb330b41dd91289edb761938a, reversing changes made to e36e93c2653bfcd21aa4a3d85d4363903cb53a6a. --- .../_thingsThatShouldNotBeNeeded.swift | 7 ++++ Runtime/src/index.ts | 8 +++++ Runtime/src/types.ts | 1 + Sources/JavaScriptKit/Features.swift | 12 ++++--- .../FundamentalObjects/JSClosure.swift | 36 ++++++++++++------- Sources/JavaScriptKit/Runtime/index.js | 4 +++ Sources/JavaScriptKit/Runtime/index.mjs | 4 +++ Sources/_CJavaScriptKit/_CJavaScriptKit.c | 32 +++++++++++++++++ 8 files changed, 88 insertions(+), 16 deletions(-) diff --git a/Examples/Embedded/Sources/EmbeddedApp/_thingsThatShouldNotBeNeeded.swift b/Examples/Embedded/Sources/EmbeddedApp/_thingsThatShouldNotBeNeeded.swift index a5da489dc..8f45ccee9 100644 --- a/Examples/Embedded/Sources/EmbeddedApp/_thingsThatShouldNotBeNeeded.swift +++ b/Examples/Embedded/Sources/EmbeddedApp/_thingsThatShouldNotBeNeeded.swift @@ -1,5 +1,12 @@ import JavaScriptKit +// NOTE: it seems the embedded tree shaker gets rid of these exports if they are not used somewhere +func _i_need_to_be_here_for_wasm_exports_to_work() { + _ = _swjs_library_features + _ = _swjs_call_host_function + _ = _swjs_free_host_function +} + // TODO: why do I need this? and surely this is not ideal... figure this out, or at least have this come from a C lib @_cdecl("strlen") func strlen(_ s: UnsafePointer) -> Int { diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 64c1225e0..3f23ed753 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -56,6 +56,14 @@ export class SwiftRuntime { ` ); } + if (this.exports.swjs_library_version() != this.version) { + throw new Error( + `The versions of JavaScriptKit are incompatible. + WebAssembly runtime ${this.exports.swjs_library_version()} != JS runtime ${ + this.version + }` + ); + } } main() { diff --git a/Runtime/src/types.ts b/Runtime/src/types.ts index e6aa99363..587b60770 100644 --- a/Runtime/src/types.ts +++ b/Runtime/src/types.ts @@ -7,6 +7,7 @@ export type JavaScriptValueKind = number; export type JavaScriptValueKindAndFlags = number; export interface ExportedFunctions { + swjs_library_version(): number; swjs_library_features(): number; swjs_prepare_host_function_call(size: number): pointer; swjs_cleanup_host_function_call(argv: pointer): void; diff --git a/Sources/JavaScriptKit/Features.swift b/Sources/JavaScriptKit/Features.swift index 4148f2adb..db6e00f26 100644 --- a/Sources/JavaScriptKit/Features.swift +++ b/Sources/JavaScriptKit/Features.swift @@ -2,13 +2,17 @@ enum LibraryFeatures { static let weakRefs: Int32 = 1 << 0 } -@_expose(wasm, "swjs_library_features") -@_cdecl("_swjs_library_features") -@available(*, unavailable) -public func _swjs_library_features() -> Int32 { +@_cdecl("_library_features") +func _library_features() -> Int32 { var features: Int32 = 0 #if !JAVASCRIPTKIT_WITHOUT_WEAKREFS features |= LibraryFeatures.weakRefs #endif return features } + +#if compiler(>=6.0) && hasFeature(Embedded) +// cdecls currently don't work in embedded, and expose for wasm only works >=6.0 +@_expose(wasm, "swjs_library_features") +public func _swjs_library_features() -> Int32 { _library_features() } +#endif \ No newline at end of file diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift index 01ad1ab02..828cb40f2 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift @@ -197,10 +197,8 @@ private func makeAsyncClosure( // └─────────────────────┴──────────────────────────┘ /// Returns true if the host function has been already released, otherwise false. -@_expose(wasm, "swjs_call_host_function") -@_cdecl("_swjs_call_host_function") -@available(*, unavailable) -public func _swjs_call_host_function( +@_cdecl("_call_host_function_impl") +func _call_host_function_impl( _ hostFuncRef: JavaScriptHostFuncRef, _ argv: UnsafePointer, _ argc: Int32, _ callbackFuncRef: JavaScriptObjectRef @@ -233,10 +231,9 @@ extension JSClosure { } } -@_expose(wasm, "swjs_free_host_function") -@_cdecl("_swjs_free_host_function") -@available(*, unavailable) -func _swjs_free_host_function(_ hostFuncRef: JavaScriptHostFuncRef) {} + +@_cdecl("_free_host_function_impl") +func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) {} #else @@ -247,10 +244,25 @@ extension JSClosure { } -@_expose(wasm, "swjs_free_host_function") -@_cdecl("_swjs_free_host_function") -@available(*, unavailable) -func _swjs_free_host_function(_ hostFuncRef: JavaScriptHostFuncRef) { +@_cdecl("_free_host_function_impl") +func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) { JSClosure.sharedClosures.wrappedValue[hostFuncRef] = nil } #endif + +#if compiler(>=6.0) && hasFeature(Embedded) +// cdecls currently don't work in embedded, and expose for wasm only works >=6.0 +@_expose(wasm, "swjs_call_host_function") +public func _swjs_call_host_function( + _ hostFuncRef: JavaScriptHostFuncRef, + _ argv: UnsafePointer, _ argc: Int32, + _ callbackFuncRef: JavaScriptObjectRef) -> Bool { + + _call_host_function_impl(hostFuncRef, argv, argc, callbackFuncRef) +} + +@_expose(wasm, "swjs_free_host_function") +public func _swjs_free_host_function(_ hostFuncRef: JavaScriptHostFuncRef) { + _free_host_function_impl(hostFuncRef) +} +#endif diff --git a/Sources/JavaScriptKit/Runtime/index.js b/Sources/JavaScriptKit/Runtime/index.js index 696a7df9d..25b6af3c9 100644 --- a/Sources/JavaScriptKit/Runtime/index.js +++ b/Sources/JavaScriptKit/Runtime/index.js @@ -328,6 +328,10 @@ -Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor `); } + if (this.exports.swjs_library_version() != this.version) { + throw new Error(`The versions of JavaScriptKit are incompatible. + WebAssembly runtime ${this.exports.swjs_library_version()} != JS runtime ${this.version}`); + } } main() { const instance = this.instance; diff --git a/Sources/JavaScriptKit/Runtime/index.mjs b/Sources/JavaScriptKit/Runtime/index.mjs index a18d90c37..668368203 100644 --- a/Sources/JavaScriptKit/Runtime/index.mjs +++ b/Sources/JavaScriptKit/Runtime/index.mjs @@ -322,6 +322,10 @@ class SwiftRuntime { -Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor `); } + if (this.exports.swjs_library_version() != this.version) { + throw new Error(`The versions of JavaScriptKit are incompatible. + WebAssembly runtime ${this.exports.swjs_library_version()} != JS runtime ${this.version}`); + } } main() { const instance = this.instance; diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c index ed52a55f6..ed8240ca1 100644 --- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c +++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c @@ -13,6 +13,13 @@ extern void *memcpy (void *__restrict, const void *__restrict, size_t); #include #endif +/// The compatibility runtime library version. +/// Notes: If you change any interface of runtime library, please increment +/// this and `SwiftRuntime.version` in `./Runtime/src/index.ts`. +__attribute__((export_name("swjs_library_version"))) +int swjs_library_version(void) { + return 708; +} __attribute__((export_name("swjs_prepare_host_function_call"))) void *swjs_prepare_host_function_call(const int argc) { @@ -28,6 +35,31 @@ void swjs_cleanup_host_function_call(void *argv_buffer) { // cdecls don't work in Embedded, but @_expose(wasm) can be used with Swift >=6.0 // the previously used `#if __Embedded` did not play well with SwiftPM (defines needed to be on every target up the chain) #ifdef __wasi__ +bool _call_host_function_impl(const JavaScriptHostFuncRef host_func_ref, + const RawJSValue *argv, const int argc, + const JavaScriptObjectRef callback_func); + +__attribute__((export_name("swjs_call_host_function"))) +bool swjs_call_host_function(const JavaScriptHostFuncRef host_func_ref, + const RawJSValue *argv, const int argc, + const JavaScriptObjectRef callback_func) { + return _call_host_function_impl(host_func_ref, argv, argc, callback_func); +} + +void _free_host_function_impl(const JavaScriptHostFuncRef host_func_ref); + +__attribute__((export_name("swjs_free_host_function"))) +void swjs_free_host_function(const JavaScriptHostFuncRef host_func_ref) { + _free_host_function_impl(host_func_ref); +} + +int _library_features(void); + +__attribute__((export_name("swjs_library_features"))) +int swjs_library_features(void) { + return _library_features(); +} + int swjs_get_worker_thread_id_cached(void) { _Thread_local static int tid = 0; if (tid == 0) { From 5502474ebdfe6a97ccd8387d6f14d5e6e69f3be5 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 19 Mar 2025 13:47:06 +0000 Subject: [PATCH 11/13] PackageToJS: Colorize diagnostics by default --- Plugins/PackageToJS/Sources/PackageToJSPlugin.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index 7dc2a6841..596a9ee21 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -292,6 +292,7 @@ struct PackageToJSPlugin: CommandPlugin { logging: .concise ) parameters.echoLogs = true + parameters.otherSwiftcFlags = ["-color-diagnostics"] let buildingForEmbedded = ProcessInfo.processInfo.environment["JAVASCRIPTKIT_EXPERIMENTAL_EMBEDDED_WASM"].flatMap( Bool.init) ?? false @@ -299,10 +300,10 @@ struct PackageToJSPlugin: CommandPlugin { // NOTE: We only support static linking for now, and the new SwiftDriver // does not infer `-static-stdlib` for WebAssembly targets intentionally // for future dynamic linking support. - parameters.otherSwiftcFlags = [ + parameters.otherSwiftcFlags += [ "-static-stdlib", "-Xclang-linker", "-mexec-model=reactor", ] - parameters.otherLinkerFlags = [ + parameters.otherLinkerFlags += [ "--export-if-defined=__main_argc_argv" ] From 29ba8aed947584ccf56fc567fa058a04ba68e012 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 19 Mar 2025 23:47:06 +0900 Subject: [PATCH 12/13] [skip ci] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c03561587..2f41b7a31 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # JavaScriptKit [![Run unit tests](https://github.com/swiftwasm/JavaScriptKit/actions/workflows/test.yml/badge.svg)](https://github.com/swiftwasm/JavaScriptKit/actions/workflows/test.yml) +[![](https://img.shields.io/badge/docc-read_documentation-blue)](https://swiftpackageindex.com/swiftwasm/JavaScriptKit/documentation) Swift framework to interact with JavaScript through WebAssembly. ## Quick Start -Check out the [Hello World](https://swiftpackageindex.com/swiftwasm/JavaScriptKit/main/tutorials/javascriptkit/hello-world) tutorial for a step-by-step guide to getting started. +Check out the [Hello World](https://swiftpackageindex.com/swiftwasm/JavaScriptKit/tutorials/javascriptkit/hello-world) tutorial for a step-by-step guide to getting started. ## Overview From 56ec458e736aef3eddfa7715972f729b8b0727a2 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 20 Mar 2025 06:31:15 +0000 Subject: [PATCH 13/13] PackageToJS: Generalize `--verbose` flag to all commands And use it to control the verbosity of the build process. --- Plugins/PackageToJS/Sources/PackageToJS.swift | 6 +++--- .../Sources/PackageToJSPlugin.swift | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Plugins/PackageToJS/Sources/PackageToJS.swift b/Plugins/PackageToJS/Sources/PackageToJS.swift index c766995d2..80ad9b805 100644 --- a/Plugins/PackageToJS/Sources/PackageToJS.swift +++ b/Plugins/PackageToJS/Sources/PackageToJS.swift @@ -8,6 +8,8 @@ struct PackageToJS { var packageName: String? /// Whether to explain the build plan (default: false) var explain: Bool = false + /// Whether to print verbose output + var verbose: Bool = false /// Whether to use CDN for dependency packages (default: false) var useCDN: Bool = false /// Whether to enable code coverage collection (default: false) @@ -49,8 +51,6 @@ struct PackageToJS { var inspect: Bool /// The extra arguments to pass to node var extraNodeArguments: [String] - /// Whether to print verbose output - var verbose: Bool /// The options for packaging var packageOptions: PackageOptions } @@ -99,7 +99,7 @@ struct PackageToJS { try PackageToJS.runSingleTestingLibrary( testRunner: testRunner, currentDirectoryURL: currentDirectoryURL, extraArguments: extraArguments, - testParser: testOptions.verbose ? nil : FancyTestsParser(write: { print($0, terminator: "") }), + testParser: testOptions.packageOptions.verbose ? nil : FancyTestsParser(write: { print($0, terminator: "") }), testOptions: testOptions ) } diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index 596a9ee21..62e7dc16e 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -156,7 +156,7 @@ struct PackageToJSPlugin: CommandPlugin { let productName = try buildOptions.product ?? deriveDefaultProduct(package: context.package) let build = try buildWasm( productName: productName, context: context, - enableCodeCoverage: buildOptions.packageOptions.enableCodeCoverage + options: buildOptions.packageOptions ) guard build.succeeded else { reportBuildFailure(build, arguments) @@ -212,7 +212,7 @@ struct PackageToJSPlugin: CommandPlugin { let productName = "\(context.package.displayName)PackageTests" let build = try buildWasm( productName: productName, context: context, - enableCodeCoverage: testOptions.packageOptions.enableCodeCoverage + options: testOptions.packageOptions ) guard build.succeeded else { reportBuildFailure(build, arguments) @@ -284,12 +284,12 @@ struct PackageToJSPlugin: CommandPlugin { } } - private func buildWasm(productName: String, context: PluginContext, enableCodeCoverage: Bool) throws + private func buildWasm(productName: String, context: PluginContext, options: PackageToJS.PackageOptions) throws -> PackageManager.BuildResult { var parameters = PackageManager.BuildParameters( configuration: .inherit, - logging: .concise + logging: options.verbose ? .verbose : .concise ) parameters.echoLogs = true parameters.otherSwiftcFlags = ["-color-diagnostics"] @@ -308,7 +308,7 @@ struct PackageToJSPlugin: CommandPlugin { ] // Enable code coverage options if requested - if enableCodeCoverage { + if options.enableCodeCoverage { parameters.otherSwiftcFlags += ["-profile-coverage-mapping", "-profile-generate"] parameters.otherCFlags += ["-fprofile-instr-generate", "-fcoverage-mapping"] } @@ -356,9 +356,10 @@ extension PackageToJS.PackageOptions { let packageName = extractor.extractOption(named: "package-name").last let explain = extractor.extractFlag(named: "explain") let useCDN = extractor.extractFlag(named: "use-cdn") + let verbose = extractor.extractFlag(named: "verbose") let enableCodeCoverage = extractor.extractFlag(named: "enable-code-coverage") return PackageToJS.PackageOptions( - outputPath: outputPath, packageName: packageName, explain: explain != 0, useCDN: useCDN != 0, enableCodeCoverage: enableCodeCoverage != 0 + outputPath: outputPath, packageName: packageName, explain: explain != 0, verbose: verbose != 0, useCDN: useCDN != 0, enableCodeCoverage: enableCodeCoverage != 0 ) } } @@ -390,6 +391,7 @@ extension PackageToJS.BuildOptions { --output Path to the output directory (default: .build/plugins/PackageToJS/outputs/Package) --package-name Name of the package (default: lowercased Package.swift name) --explain Whether to explain the build plan + --verbose Whether to print verbose output --no-optimize Whether to disable wasm-opt optimization --use-cdn Whether to use CDN for dependency packages --enable-code-coverage Whether to enable code coverage collection @@ -419,14 +421,12 @@ extension PackageToJS.TestOptions { let prelude = extractor.extractOption(named: "prelude").last let environment = extractor.extractOption(named: "environment").last let inspect = extractor.extractFlag(named: "inspect") - let verbose = extractor.extractFlag(named: "verbose") let extraNodeArguments = extractor.extractSingleDashOption(named: "Xnode") let packageOptions = PackageToJS.PackageOptions.parse(from: &extractor) var options = PackageToJS.TestOptions( buildOnly: buildOnly != 0, listTests: listTests != 0, filter: filter, prelude: prelude, environment: environment, inspect: inspect != 0, extraNodeArguments: extraNodeArguments, - verbose: verbose != 0, packageOptions: packageOptions ) @@ -448,9 +448,10 @@ extension PackageToJS.TestOptions { --prelude Path to the prelude script --environment The environment to use for the tests (values: node, browser; default: node) --inspect Whether to run tests in the browser with inspector enabled + --explain Whether to explain the build plan + --verbose Whether to print verbose output --use-cdn Whether to use CDN for dependency packages --enable-code-coverage Whether to enable code coverage collection - --verbose Whether to print verbose output -Xnode Extra arguments to pass to Node.js EXAMPLES: