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

Swift 6 language mode compatibility #286

Merged
merged 19 commits into from
Mar 5, 2025
Merged

Swift 6 language mode compatibility #286

merged 19 commits into from
Mar 5, 2025

Conversation

kateinoigakukun
Copy link
Member

@kateinoigakukun kateinoigakukun commented Mar 5, 2025

Breaking changes

  • Drop Swift 5.10 and earlier supports
  • JSValue no longer conforms Sendable nor Error
  • JSError no longer conforms Sendable nor Error
  • Several Sendable-related API adjustment

Added APIs

  • JSException type is added to replace usage of Error conformance of JSValue

Rationale for removal of Sendable conformance from JSValue

  • Sendable type values:
    • Must be able to be accessed from multiple "isolation domains" without additional synchronization.
    • Must be able to be sent to other threads.
  • non-Sendable type values:
    • Can only be accessed from a single "isolation domain".
    • Can be sent to other threads by sending convention.
  • JSValue
    • Want to ban sending it to other threads because it cannot access objects in other threads.
    • Want to allow it to be accessed from multiple "isolation domains" living on the same thread without additional synchronization.

The problem is that there is no way to express "values that are accessible from multiple isolation domains living on the same thread" in the current type system.

There are some ways to send non-Sendable values to other isolation domains, so removing Sendable conformance can't fully ban sending JSValue to other threads. However, it's still useful to prevent accessing JSValue from other threads for some cases.

It might be considerable option to introduce something like JSRemote that allows asynchronously accessing objects living in different threads in the future.

Unfortunately, `@LazyThreadLocal static var` is considered as
concurrency-unsafe in Swift 6 mode even though the underlying PW storage
is read-only and concurrency-safe. Also Swift bans `static let` with
`@propertyWrapper` syntax, so we need to use `LazyThreadLocal` directly.

See the discussion in the Swift forum:
https://forums.swift.org/t/static-property-wrappers-and-strict-concurrency-in-5-10/70116/27
…d(unsafe)`

Even though `JSObject` is not a `Sendable` type, `JSError` must be
`Sendable` because of `Error` conformance. For this reason, we need to
annotate the `jsObject` property as `nonisolated(unsafe)` to suppress
the compiler error. Accessing this property from a different isolation
domain scheduled on a different thread will result in a runtime
assertion failure, but better than corrupting memory.
`Error` protocol now requires `Sendable` conformance, which is not
possible for `JSError` because `JSObject` is not `Sendable`.
They are accessed from a single thread, so there is no need to enforce
`@Sendable` requirement on them. And also the following code is not
working with `@Sendable` requirement because the captured `JSPromise`
is not `Sendable`.

```
let promise = JSPromise(resolver: { resolver -> Void in
    resolver(.success(.undefined))
})
let setTimeout = JSObject.global.setTimeout.function!
let eventLoop = JavaScriptEventLoop(
    queueTask: { job in
        // TODO(katei): Should prefer `queueMicrotask` if available?
        // We should measure if there is performance advantage.
        promise.then { _ in
            job()
            return JSValue.undefined
        }
    },
    setTimeout: { delay, job in
        setTimeout(JSOneshotClosure { _ in
            job()
            return JSValue.undefined
        }, delay)
    }
)
```
…rom `JSValue`

This is a breaking change. It introduces a new `JSException` type to represent
exceptions thrown from JavaScript code. This change is necessary to remove `Sendable`
conformance from `JSValue`, which is derived from `Error` conformance.
Copy link

github-actions bot commented Mar 5, 2025

Time Change: +65ms (0%)

Total Time: 9,726ms

Test name Duration Change
Serialization/JavaScript function call through Wasm import with int 21ms -3ms (14%) 👏
Serialization/JavaScript function call from Swift 121ms -8ms (6%)
View Unchanged
Test name Duration Change
Serialization/JavaScript function call through Wasm import 23ms -1ms
Serialization/Swift Int to JavaScript with assignment 334ms -9ms (2%)
Serialization/Swift Int to JavaScript with call 969ms -30ms (3%)
Serialization/JavaScript Number to Swift Int 284ms +4ms (1%)
Serialization/Swift String to JavaScript with assignment 425ms -12ms (2%)
Serialization/Swift String to JavaScript with call 1,112ms +28ms (2%)
Serialization/JavaScript String to Swift String 3,658ms +84ms (2%)
Object heap/Increment and decrement RC 2,766ms +12ms (0%)
View Baselines
Test name Duration
Serialization/Call JavaScript function directly 3ms
Serialization/Assign JavaScript number directly 2ms
Serialization/Call with JavaScript number directly 3ms
Serialization/Write JavaScript string directly 3ms
Serialization/Call with JavaScript string directly 3ms

…Promise.result`

To reduce burden type casting, it's better to remove the wrapper from
the API.
…xecutor`

The installation of the global executor should be done before any
job scheduling, so it should be able to be called at top-level immediately
executed code.
Our new code htis assertion in 2024-10-30-a, but it's fixed in 2025-02-26-a.
@kateinoigakukun kateinoigakukun merged commit 6b05d07 into main Mar 5, 2025
7 checks passed
@kateinoigakukun kateinoigakukun deleted the yt/swift6-mode branch March 5, 2025 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant