diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 69f63a27c..419c772a0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: target: "wasm32-unknown-wasip1" - os: ubuntu-24.04 toolchain: - download-url: https://download.swift.org/swift-6.3-branch/ubuntu2404/swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-05-a/swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-05-a-ubuntu24.04.tar.gz + download-url: https://download.swift.org/swift-6.3-branch/ubuntu2404/swift-6.3-DEVELOPMENT-SNAPSHOT-2026-03-05-a/swift-6.3-DEVELOPMENT-SNAPSHOT-2026-03-05-a-ubuntu24.04.tar.gz wasi-backend: Node target: "wasm32-unknown-wasip1" - os: ubuntu-22.04 @@ -167,14 +167,16 @@ jobs: - uses: actions/checkout@v6 - uses: ./.github/actions/install-swift with: - download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2026-02-02-a/swift-DEVELOPMENT-SNAPSHOT-2026-02-02-a-ubuntu22.04.tar.gz + download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2026-03-09-a/swift-DEVELOPMENT-SNAPSHOT-2026-03-09-a-ubuntu22.04.tar.gz - uses: swiftwasm/setup-swiftwasm@v2 id: setup-wasm32-unknown-wasip1 with: { target: wasm32-unknown-wasip1 } - uses: swiftwasm/setup-swiftwasm@v2 id: setup-wasm32-unknown-wasip1-threads with: { target: wasm32-unknown-wasip1-threads } - - run: ./Utilities/build-examples.sh + - run: | + swift --version + ./Utilities/build-examples.sh env: SWIFT_SDK_ID_wasm32_unknown_wasip1_threads: ${{ steps.setup-wasm32-unknown-wasip1-threads.outputs.swift-sdk-id }} SWIFT_SDK_ID_wasm32_unknown_wasip1: ${{ steps.setup-wasm32-unknown-wasip1.outputs.swift-sdk-id }} diff --git a/Examples/Embedded/Sources/EmbeddedApp/main.swift b/Examples/Embedded/Sources/EmbeddedApp/main.swift index f6bf5b6ac..5e7f01a3c 100644 --- a/Examples/Embedded/Sources/EmbeddedApp/main.swift +++ b/Examples/Embedded/Sources/EmbeddedApp/main.swift @@ -3,7 +3,7 @@ import JavaScriptKit let alert = JSObject.global.alert.object! let document = JSObject.global.document -print("Hello from WASM, document title: \(document.title.string ?? "")") +print("Hello from Wasm, document title: \(document.title.string ?? "")") var count = 0 diff --git a/Sources/JavaScriptEventLoop/JSSending.swift b/Sources/JavaScriptEventLoop/JSSending.swift index 7a3750c15..fb2fb1ddf 100644 --- a/Sources/JavaScriptEventLoop/JSSending.swift +++ b/Sources/JavaScriptEventLoop/JSSending.swift @@ -226,6 +226,32 @@ extension JSSending { /// - Parameter isolation: The actor isolation context for this call, used in Swift concurrency. /// - Returns: The received object of type `T`. /// - Throws: `JSSendingError` if the sending operation fails, or `JSException` if a JavaScript error occurs. + #if compiler(>=6.4) + @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) + public func receive( + isolation: isolated (any Actor)? = #isolation, + file: StaticString = #file, + line: UInt = #line + ) async throws(JSException) -> T { + #if _runtime(_multithreaded) + let idInDestination = try await withCheckedThrowingContinuation { continuation in + let context = _JSSendingContext(continuation: continuation) + let idInSource = self.storage.idInSource + let transferring = self.storage.transferring ? [idInSource] : [] + swjs_request_sending_object( + idInSource, + transferring, + Int32(transferring.count), + self.storage.sourceTid, + Unmanaged.passRetained(context).toOpaque() + ) + } + return storage.construct(JSObject(id: idInDestination)) + #else + return storage.construct(storage.sourceObject) + #endif + } + #else @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public func receive( isolation: isolated (any Actor)? = #isolation, @@ -250,6 +276,7 @@ extension JSSending { return storage.construct(storage.sourceObject) #endif } + #endif // 6.0 and below can't compile the following without a compiler crash. #if compiler(>=6.1) @@ -341,11 +368,19 @@ extension JSSending { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) private final class _JSSendingContext: Sendable { + #if compiler(>=6.4) + let continuation: CheckedContinuation + + init(continuation: CheckedContinuation) { + self.continuation = continuation + } + #else let continuation: CheckedContinuation init(continuation: CheckedContinuation) { self.continuation = continuation } + #endif } /// Error type representing failures during JavaScript object sending operations. diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop+ExecutorFactory.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop+ExecutorFactory.swift index 7de4cb74a..a9b6091e8 100644 --- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop+ExecutorFactory.swift +++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop+ExecutorFactory.swift @@ -5,10 +5,10 @@ // See: https://forums.swift.org/t/pitch-2-custom-main-and-global-executors/78437 #if compiler(>=6.3) -@_spi(ExperimentalCustomExecutors) import _Concurrency +@_spi(ExperimentalCustomExecutors) @_spi(ExperimentalScheduling) import _Concurrency #else import _Concurrency -#endif +#endif // #if compiler(>=6.3) import _CJavaScriptKit #if compiler(>=6.3) @@ -40,6 +40,22 @@ extension JavaScriptEventLoop: SchedulingExecutor { tolerance: C.Duration?, clock: C ) { + #if hasFeature(Embedded) + #if compiler(>=6.4) + // In Embedded Swift, ContinuousClock and SuspendingClock are unavailable. + // Hand-off the scheduling work to the Clock implementation for custom clocks. + clock.enqueue( + job, + on: self, + at: clock.now.advanced(by: delay), + tolerance: tolerance + ) + #else + fatalError( + "Delayed enqueue requires Swift 6.4+ in Embedded mode" + ) + #endif // #if compiler(>=6.4) (Embedded) + #else // #if hasFeature(Embedded) let duration: Duration // Handle clocks we know if let _ = clock as? ContinuousClock { @@ -47,7 +63,9 @@ extension JavaScriptEventLoop: SchedulingExecutor { } else if let _ = clock as? SuspendingClock { duration = delay as! SuspendingClock.Duration } else { - // Hand-off the scheduling work to Clock implementation for unknown clocks + #if compiler(>=6.4) + // Hand-off the scheduling work to Clock implementation for unknown clocks. + // Clock.enqueue is only available in the development branch (6.4+). clock.enqueue( job, on: self, @@ -55,12 +73,16 @@ extension JavaScriptEventLoop: SchedulingExecutor { tolerance: tolerance ) return + #else + fatalError("Unsupported clock type; only ContinuousClock and SuspendingClock are supported") + #endif // #if compiler(>=6.4) (non-Embedded) } let milliseconds = Self.delayInMilliseconds(from: duration) self.enqueue( UnownedJob(job), withDelay: milliseconds ) + #endif // #if hasFeature(Embedded) } private static func delayInMilliseconds(from swiftDuration: Duration) -> Double { @@ -111,4 +133,4 @@ extension JavaScriptEventLoop: ExecutorFactory { } } -#endif // compiler(>=6.3) +#endif // #if compiler(>=6.3) diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift index aebc90d65..aec1441a5 100644 --- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift +++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift @@ -123,13 +123,16 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable { private static func installGlobalExecutorIsolated() { guard !didInstallGlobalExecutor else { return } didInstallGlobalExecutor = true - #if compiler(>=6.3) + #if compiler(>=6.3) && !hasFeature(Embedded) if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999, *) { // For Swift 6.3 and above, we can use the new `ExecutorFactory` API _Concurrency._createExecutors(factory: JavaScriptEventLoop.self) } #else - // For Swift 6.1 and below, we need to install the global executor by hook API + // For Swift 6.1 and below, or Embedded Swift, we need to install + // the global executor by hook API. The ExecutorFactory mechanism + // does not work in Embedded Swift because ExecutorImpl.swift is + // excluded from the embedded Concurrency library. installByLegacyHook() #endif } diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index dceecf5bf..0ad7b235a 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -98,7 +98,7 @@ public final class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiter /// used as the return value for the `withUnsafeBytes(_:)` method. The /// argument is valid only for the duration of the closure's execution. /// - Returns: The return value, if any, of the `body` closure parameter. - public func withUnsafeBytes(_ body: (UnsafeBufferPointer) throws -> R) rethrows -> R { + public func withUnsafeBytes(_ body: (UnsafeBufferPointer) throws(E) -> R) throws(E) -> R { let buffer = UnsafeMutableBufferPointer.allocate(capacity: length) defer { buffer.deallocate() } copyMemory(to: buffer) @@ -121,7 +121,9 @@ public final class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiter /// argument is valid only for the duration of the closure's execution. /// - Returns: The return value, if any, of the `body`async closure parameter. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public func withUnsafeBytesAsync(_ body: (UnsafeBufferPointer) async throws -> R) async rethrows -> R { + public func withUnsafeBytesAsync( + _ body: (UnsafeBufferPointer) async throws(E) -> R + ) async throws(E) -> R { let buffer = UnsafeMutableBufferPointer.allocate(capacity: length) defer { buffer.deallocate() } copyMemory(to: buffer)