diff --git a/.github/workflows/compatibility.yml b/.github/workflows/compatibility.yml index e4534507..422e08c4 100644 --- a/.github/workflows/compatibility.yml +++ b/.github/workflows/compatibility.yml @@ -22,3 +22,4 @@ jobs: make bootstrap cd Example/JavaScriptKitExample swift build --triple wasm32-unknown-wasi + swift build --triple wasm32-unknown-wasi -Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS diff --git a/IntegrationTests/TestSuites/Package.swift b/IntegrationTests/TestSuites/Package.swift index 0344d049..297fa838 100644 --- a/IntegrationTests/TestSuites/Package.swift +++ b/IntegrationTests/TestSuites/Package.swift @@ -8,7 +8,7 @@ let package = Package( // This package doesn't work on macOS host, but should be able to be built for it // for developing on Xcode. This minimum version requirement is to prevent availability // errors for Concurrency API, whose runtime support is shipped from macOS 12.0 - .macOS("12.0") + .macOS("12.0"), ], products: [ .executable( @@ -25,6 +25,7 @@ let package = Package( targets: [ .target(name: "CHelpers"), .target(name: "PrimaryTests", dependencies: [ + .product(name: "JavaScriptBigIntSupport", package: "JavaScriptKit"), "JavaScriptKit", "CHelpers", ]), diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/I64.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/I64.swift new file mode 100644 index 00000000..bd0831e7 --- /dev/null +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/I64.swift @@ -0,0 +1,35 @@ +import JavaScriptBigIntSupport +import JavaScriptKit + +func testI64() throws { + try test("BigInt") { + func expectPassesThrough(signed value: Int64) throws { + let bigInt = JSBigInt(value) + try expectEqual(bigInt.description, value.description) + } + + func expectPassesThrough(unsigned value: UInt64) throws { + let bigInt = JSBigInt(unsigned: value) + try expectEqual(bigInt.description, value.description) + } + + try expectPassesThrough(signed: 0) + try expectPassesThrough(signed: 1 << 62) + try expectPassesThrough(signed: -2305) + for _ in 0 ..< 100 { + try expectPassesThrough(signed: .random(in: .min ... .max)) + } + try expectPassesThrough(signed: .min) + try expectPassesThrough(signed: .max) + + try expectPassesThrough(unsigned: 0) + try expectPassesThrough(unsigned: 1 << 62) + try expectPassesThrough(unsigned: 1 << 63) + try expectPassesThrough(unsigned: .min) + try expectPassesThrough(unsigned: .max) + try expectPassesThrough(unsigned: ~0) + for _ in 0 ..< 100 { + try expectPassesThrough(unsigned: .random(in: .min ... .max)) + } + } +} diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 6a9ff54c..a48c6fb0 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -852,5 +852,5 @@ try test("JSValueDecoder") { try expectEqual(decodedTama.isCat, true) } - +try testI64() Expectation.wait(expectations) diff --git a/IntegrationTests/bin/concurrency-tests.js b/IntegrationTests/bin/concurrency-tests.js index 2d705761..47ef4abd 100644 --- a/IntegrationTests/bin/concurrency-tests.js +++ b/IntegrationTests/bin/concurrency-tests.js @@ -1,5 +1,7 @@ const { startWasiTask } = require("../lib"); +Error.stackTraceLimit = Infinity; + startWasiTask("./dist/ConcurrencyTests.wasm").catch((err) => { console.log(err); process.exit(1); diff --git a/IntegrationTests/bin/primary-tests.js b/IntegrationTests/bin/primary-tests.js index 597590bd..94bcf7da 100644 --- a/IntegrationTests/bin/primary-tests.js +++ b/IntegrationTests/bin/primary-tests.js @@ -1,3 +1,5 @@ +Error.stackTraceLimit = Infinity; + global.globalObject1 = { prop_1: { nested_prop: 1, @@ -44,23 +46,26 @@ global.globalObject1 = { throw "String Error"; }, func3: function () { - throw 3.0 + throw 3.0; }, }, eval_closure: function (fn) { - return fn(arguments[1]) + return fn(arguments[1]); }, observable_obj: { set_called: false, - target: new Proxy({ - nested: {} - }, { - set(target, key, value) { - global.globalObject1.observable_obj.set_called = true; - target[key] = value; - return true; + target: new Proxy( + { + nested: {}, + }, + { + set(target, key, value) { + global.globalObject1.observable_obj.set_called = true; + target[key] = value; + return true; + }, } - }) + ), }, }; @@ -79,16 +84,16 @@ global.Animal = function (name, age, isCat) { }; this.setName = function (name) { this.name = name; - } + }; }; -global.callThrowingClosure = (c) => { +global.callThrowingClosure = (c) => { try { - c() + c(); } catch (error) { - return error + return error; } -} +}; const { startWasiTask } = require("../lib"); diff --git a/Package.swift b/Package.swift index 8a75bed3..3d07321a 100644 --- a/Package.swift +++ b/Package.swift @@ -7,6 +7,7 @@ let package = Package( products: [ .library(name: "JavaScriptKit", targets: ["JavaScriptKit"]), .library(name: "JavaScriptEventLoop", targets: ["JavaScriptEventLoop"]), + .library(name: "JavaScriptBigIntSupport", targets: ["JavaScriptBigIntSupport"]), ], targets: [ .target( @@ -14,6 +15,11 @@ let package = Package( dependencies: ["_CJavaScriptKit"] ), .target(name: "_CJavaScriptKit"), + .target( + name: "JavaScriptBigIntSupport", + dependencies: ["_CJavaScriptBigIntSupport", "JavaScriptKit"] + ), + .target(name: "_CJavaScriptBigIntSupport", dependencies: ["_CJavaScriptKit"]), .target( name: "JavaScriptEventLoop", dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"] diff --git a/README.md b/README.md index 46dc963a..b5c64539 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ JavaScript features should work, which currently includes: - Mobile Safari 14.8+ If you need to support older browser versions, you'll have to build with -`JAVASCRIPTKIT_WITHOUT_WEAKREFS` flag, passing `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` flags +the `JAVASCRIPTKIT_WITHOUT_WEAKREFS` flag, passing `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` flags when compiling. This should lower browser requirements to these versions: - Edge 16+ diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 15fc570e..dcd817a7 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -14,7 +14,7 @@ export class SwiftRuntime { private _instance: WebAssembly.Instance | null; private _memory: Memory | null; private _closureDeallocator: SwiftClosureDeallocator | null; - private version: number = 706; + private version: number = 707; private textDecoder = new TextDecoder("utf-8"); private textEncoder = new TextEncoder(); // Only support utf-8 @@ -114,7 +114,6 @@ export class SwiftRuntime { const value = JSValue.decode(kind, payload1, payload2, this.memory); obj[key] = value; }, - swjs_get_prop: ( ref: ref, name: ref, @@ -146,7 +145,6 @@ export class SwiftRuntime { const value = JSValue.decode(kind, payload1, payload2, this.memory); obj[index] = value; }, - swjs_get_subscript: ( ref: ref, index: number, @@ -172,7 +170,6 @@ export class SwiftRuntime { this.memory.writeUint32(bytes_ptr_result, bytes_ptr); return bytes.length; }, - swjs_decode_string: (bytes_ptr: pointer, length: number) => { const bytes = this.memory .bytes() @@ -180,7 +177,6 @@ export class SwiftRuntime { const string = this.textDecoder.decode(bytes); return this.memory.retain(string); }, - swjs_load_string: (ref: ref, buffer: pointer) => { const bytes = this.memory.getObject(ref); this.memory.writeBytes(buffer, bytes); @@ -290,7 +286,6 @@ export class SwiftRuntime { this.memory ); }, - swjs_call_function_with_this_no_catch: ( obj_ref: ref, func_ref: ref, @@ -328,13 +323,13 @@ export class SwiftRuntime { } } }, + swjs_call_new: (ref: ref, argv: pointer, argc: number) => { const constructor = this.memory.getObject(ref); const args = JSValue.decodeArray(argv, argc, this.memory); const instance = new constructor(...args); return this.memory.retain(instance); }, - swjs_call_throwing_new: ( ref: ref, argv: pointer, @@ -409,5 +404,25 @@ export class SwiftRuntime { swjs_release: (ref: ref) => { this.memory.release(ref); }, + + swjs_i64_to_bigint: (value: bigint, signed: number) => { + return this.memory.retain( + signed ? value : BigInt.asUintN(64, value) + ); + }, + swjs_bigint_to_i64: (ref: ref, signed: number) => { + const object = this.memory.getObject(ref); + if (typeof object !== "bigint") { + throw new Error(`Expected a BigInt, but got ${typeof object}`); + } + if (signed) { + return object; + } else { + if (object < BigInt(0)) { + return BigInt(0); + } + return BigInt.asIntN(64, object); + } + }, }; } diff --git a/Runtime/src/js-value.ts b/Runtime/src/js-value.ts index 67ac5d46..c8896900 100644 --- a/Runtime/src/js-value.ts +++ b/Runtime/src/js-value.ts @@ -1,8 +1,7 @@ import { Memory } from "./memory"; -import { pointer } from "./types"; +import { assertNever, pointer } from "./types"; -export enum Kind { - Invalid = -1, +export const enum Kind { Boolean = 0, String = 1, Number = 2, @@ -11,6 +10,7 @@ export enum Kind { Undefined = 5, Function = 6, Symbol = 7, + BigInt = 8, } export const decode = ( @@ -33,6 +33,8 @@ export const decode = ( case Kind.String: case Kind.Object: case Kind.Function: + case Kind.Symbol: + case Kind.BigInt: return memory.getObject(payload1); case Kind.Null: @@ -42,7 +44,7 @@ export const decode = ( return undefined; default: - throw new Error(`JSValue Type kind "${kind}" is not supported`); + assertNever(kind, `JSValue Type kind "${kind}" is not supported`); } }; @@ -73,7 +75,14 @@ export const write = ( memory.writeUint32(kind_ptr, exceptionBit | Kind.Null); return; } - switch (typeof value) { + + const writeRef = (kind: Kind) => { + memory.writeUint32(kind_ptr, exceptionBit | kind); + memory.writeUint32(payload1_ptr, memory.retain(value)); + }; + + const type = typeof value; + switch (type) { case "boolean": { memory.writeUint32(kind_ptr, exceptionBit | Kind.Boolean); memory.writeUint32(payload1_ptr, value ? 1 : 0); @@ -85,8 +94,7 @@ export const write = ( break; } case "string": { - memory.writeUint32(kind_ptr, exceptionBit | Kind.String); - memory.writeUint32(payload1_ptr, memory.retain(value)); + writeRef(Kind.String); break; } case "undefined": { @@ -94,21 +102,22 @@ export const write = ( break; } case "object": { - memory.writeUint32(kind_ptr, exceptionBit | Kind.Object); - memory.writeUint32(payload1_ptr, memory.retain(value)); + writeRef(Kind.Object); break; } case "function": { - memory.writeUint32(kind_ptr, exceptionBit | Kind.Function); - memory.writeUint32(payload1_ptr, memory.retain(value)); + writeRef(Kind.Function); break; } case "symbol": { - memory.writeUint32(kind_ptr, exceptionBit | Kind.Symbol); - memory.writeUint32(payload1_ptr, memory.retain(value)); + writeRef(Kind.Symbol); + break; + } + case "bigint": { + writeRef(Kind.BigInt); break; } default: - throw new Error(`Type "${typeof value}" is not supported yet`); + assertNever(type, `Type "${type}" is not supported yet`); } }; diff --git a/Runtime/src/memory.ts b/Runtime/src/memory.ts index 3c010a5f..29f82762 100644 --- a/Runtime/src/memory.ts +++ b/Runtime/src/memory.ts @@ -17,13 +17,20 @@ export class Memory { bytes = () => new Uint8Array(this.rawMemory.buffer); dataView = () => new DataView(this.rawMemory.buffer); - writeBytes = (ptr: pointer, bytes: Uint8Array) => this.bytes().set(bytes, ptr); + writeBytes = (ptr: pointer, bytes: Uint8Array) => + this.bytes().set(bytes, ptr); readUint32 = (ptr: pointer) => this.dataView().getUint32(ptr, true); + readUint64 = (ptr: pointer) => this.dataView().getBigUint64(ptr, true); + readInt64 = (ptr: pointer) => this.dataView().getBigInt64(ptr, true); readFloat64 = (ptr: pointer) => this.dataView().getFloat64(ptr, true); writeUint32 = (ptr: pointer, value: number) => this.dataView().setUint32(ptr, value, true); + writeUint64 = (ptr: pointer, value: bigint) => + this.dataView().setBigUint64(ptr, value, true); + writeInt64 = (ptr: pointer, value: bigint) => + this.dataView().setBigInt64(ptr, value, true); writeFloat64 = (ptr: pointer, value: number) => this.dataView().setFloat64(ptr, value, true); } diff --git a/Runtime/src/types.ts b/Runtime/src/types.ts index b291cd91..bc670087 100644 --- a/Runtime/src/types.ts +++ b/Runtime/src/types.ts @@ -2,6 +2,7 @@ import * as JSValue from "./js-value"; export type ref = number; export type pointer = number; +export type bool = number; export interface ExportedFunctions { swjs_library_version(): number; @@ -102,9 +103,11 @@ export interface ImportedFunctions { ): number; swjs_load_typed_array(ref: ref, buffer: pointer): void; swjs_release(ref: number): void; + swjs_i64_to_bigint(value: bigint, signed: bool): ref; + swjs_bigint_to_i64(ref: ref, signed: bool): bigint; } -export enum LibraryFeatures { +export const enum LibraryFeatures { WeakRefs = 1 << 0, } @@ -115,8 +118,11 @@ export type TypedArray = | Uint16ArrayConstructor | Int32ArrayConstructor | Uint32ArrayConstructor - // BigInt is not yet supported, see https://github.com/swiftwasm/JavaScriptKit/issues/56 - // | BigInt64ArrayConstructor - // | BigUint64ArrayConstructor + | BigInt64ArrayConstructor + | BigUint64ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor; + +export function assertNever(x: never, message: string) { + throw new Error(message); +} diff --git a/Runtime/tsconfig.json b/Runtime/tsconfig.json index 8fa8e650..8a53ba2c 100644 --- a/Runtime/tsconfig.json +++ b/Runtime/tsconfig.json @@ -8,7 +8,7 @@ "rootDir": "src", "strict": true, "target": "es2017", - "lib": ["es2017", "DOM", "ESNext.WeakRef"], + "lib": ["es2020", "DOM", "ESNext.WeakRef"], "skipLibCheck": true }, "include": ["src/**/*"], diff --git a/Sources/JavaScriptBigIntSupport/Int64+I64.swift b/Sources/JavaScriptBigIntSupport/Int64+I64.swift new file mode 100644 index 00000000..cce10a1b --- /dev/null +++ b/Sources/JavaScriptBigIntSupport/Int64+I64.swift @@ -0,0 +1,13 @@ +import JavaScriptKit + +extension UInt64: ConvertibleToJSValue, TypedArrayElement { + public static var typedArrayClass = JSObject.global.BigUint64Array.function! + + public var jsValue: JSValue { .bigInt(JSBigInt(unsigned: self)) } +} + +extension Int64: ConvertibleToJSValue, TypedArrayElement { + public static var typedArrayClass = JSObject.global.BigInt64Array.function! + + public var jsValue: JSValue { .bigInt(JSBigInt(self)) } +} diff --git a/Sources/JavaScriptBigIntSupport/JSBigInt+I64.swift b/Sources/JavaScriptBigIntSupport/JSBigInt+I64.swift new file mode 100644 index 00000000..4c8b9bca --- /dev/null +++ b/Sources/JavaScriptBigIntSupport/JSBigInt+I64.swift @@ -0,0 +1,20 @@ +import _CJavaScriptBigIntSupport +@_spi(JSObject_id) import JavaScriptKit + +extension JSBigInt: JSBigIntExtended { + public var int64Value: Int64 { + _bigint_to_i64(id, true) + } + + public var uInt64Value: UInt64 { + UInt64(bitPattern: _bigint_to_i64(id, false)) + } + + public convenience init(_ value: Int64) { + self.init(id: _i64_to_bigint(value, true)) + } + + public convenience init(unsigned value: UInt64) { + self.init(id: _i64_to_bigint(Int64(bitPattern: value), false)) + } +} diff --git a/Sources/JavaScriptBigIntSupport/XcodeSupport.swift b/Sources/JavaScriptBigIntSupport/XcodeSupport.swift new file mode 100644 index 00000000..54912cec --- /dev/null +++ b/Sources/JavaScriptBigIntSupport/XcodeSupport.swift @@ -0,0 +1,11 @@ +import _CJavaScriptKit + +/// Note: +/// Define all runtime function stubs which are imported from JavaScript environment. +/// SwiftPM doesn't support WebAssembly target yet, so we need to define them to +/// avoid link failure. +/// When running with JavaScript runtime library, they are ignored completely. +#if !arch(wasm32) + func _i64_to_bigint(_: Int64, _: Bool) -> JavaScriptObjectRef { fatalError() } + func _bigint_to_i64(_: JavaScriptObjectRef, _: Bool) -> Int64 { fatalError() } +#endif diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift index 8ff30c8a..53008c5e 100644 --- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift +++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift @@ -12,7 +12,7 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable { /// See also: https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide let queueMicrotask: @Sendable (@escaping () -> Void) -> Void /// A function that invokes a given closure after a specified number of milliseconds. - let setTimeout: @Sendable (UInt64, @escaping () -> Void) -> Void + let setTimeout: @Sendable (Double, @escaping () -> Void) -> Void /// A mutable state to manage internal job queue /// Note that this should be guarded atomically when supporting multi-threaded environment. @@ -20,7 +20,7 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable { private init( queueTask: @Sendable @escaping (@escaping () -> Void) -> Void, - setTimeout: @Sendable @escaping (UInt64, @escaping () -> Void) -> Void + setTimeout: @Sendable @escaping (Double, @escaping () -> Void) -> Void ) { self.queueMicrotask = queueTask self.setTimeout = setTimeout @@ -83,7 +83,7 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable { private func enqueue(_ job: UnownedJob, withDelay nanoseconds: UInt64) { let milliseconds = nanoseconds / 1_000_000 - setTimeout(milliseconds, { + setTimeout(Double(milliseconds), { job._runSynchronously(on: self.asUnownedSerialExecutor()) }) } diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index ebcf3595..04c1710d 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -135,15 +135,6 @@ extension UInt32: TypedArrayElement { public static var typedArrayClass = JSObject.global.Uint32Array.function! } -// FIXME: Support passing BigInts across the bridge -// See https://github.com/swiftwasm/JavaScriptKit/issues/56 -//extension Int64: TypedArrayElement { -// public static var typedArrayClass = JSObject.global.BigInt64Array.function! -//} -//extension UInt64: TypedArrayElement { -// public static var typedArrayClass = JSObject.global.BigUint64Array.function! -//} - extension Float32: TypedArrayElement { public static var typedArrayClass = JSObject.global.Float32Array.function! } diff --git a/Sources/JavaScriptKit/ConstructibleFromJSValue.swift b/Sources/JavaScriptKit/ConstructibleFromJSValue.swift index 063597a9..1f43658f 100644 --- a/Sources/JavaScriptKit/ConstructibleFromJSValue.swift +++ b/Sources/JavaScriptKit/ConstructibleFromJSValue.swift @@ -20,80 +20,56 @@ extension String: ConstructibleFromJSValue { } } -extension Double: ConstructibleFromJSValue { - public static func construct(from value: JSValue) -> Double? { - return value.number - } -} - -extension Float: ConstructibleFromJSValue { - public static func construct(from value: JSValue) -> Float? { - return value.number.map(Float.init) - } -} - -extension Int: ConstructibleFromJSValue { - public static func construct(from value: JSValue) -> Self? { - value.number.map(Self.init) - } -} - -extension Int8: ConstructibleFromJSValue { - public static func construct(from value: JSValue) -> Self? { - value.number.map(Self.init) - } -} - -extension Int16: ConstructibleFromJSValue { - public static func construct(from value: JSValue) -> Self? { - value.number.map(Self.init) - } -} - -extension Int32: ConstructibleFromJSValue { - public static func construct(from value: JSValue) -> Self? { - value.number.map(Self.init) - } -} - -extension Int64: ConstructibleFromJSValue { - public static func construct(from value: JSValue) -> Self? { - value.number.map(Self.init) +extension JSString: ConstructibleFromJSValue { + public static func construct(from value: JSValue) -> JSString? { + value.jsString } } -extension UInt: ConstructibleFromJSValue { +extension BinaryFloatingPoint where Self: ConstructibleFromJSValue { public static func construct(from value: JSValue) -> Self? { value.number.map(Self.init) } } +extension Double: ConstructibleFromJSValue {} +extension Float: ConstructibleFromJSValue {} -extension UInt8: ConstructibleFromJSValue { - public static func construct(from value: JSValue) -> Self? { - value.number.map(Self.init) +extension SignedInteger where Self: ConstructibleFromJSValue { + public init(_ bigInt: JSBigIntExtended) { + self.init(bigInt.int64Value) } -} - -extension UInt16: ConstructibleFromJSValue { public static func construct(from value: JSValue) -> Self? { - value.number.map(Self.init) + if let number = value.number { + return Self(number) + } + if let bigInt = value.bigInt as? JSBigIntExtended { + return Self(bigInt) + } + return nil } } +extension Int: ConstructibleFromJSValue {} +extension Int8: ConstructibleFromJSValue {} +extension Int16: ConstructibleFromJSValue {} +extension Int32: ConstructibleFromJSValue {} +extension Int64: ConstructibleFromJSValue {} -extension UInt32: ConstructibleFromJSValue { - public static func construct(from value: JSValue) -> Self? { - value.number.map(Self.init) +extension UnsignedInteger where Self: ConstructibleFromJSValue { + public init(_ bigInt: JSBigIntExtended) { + self.init(bigInt.uInt64Value) } -} - -extension UInt64: ConstructibleFromJSValue { public static func construct(from value: JSValue) -> Self? { - value.number.map(Self.init) - } -} - -extension JSString: ConstructibleFromJSValue { - public static func construct(from value: JSValue) -> JSString? { - value.jsString + if let number = value.number { + return Self(number) + } + if let bigInt = value.bigInt as? JSBigIntExtended { + return Self(bigInt) + } + return nil } } +extension UInt: ConstructibleFromJSValue {} +extension UInt8: ConstructibleFromJSValue {} +extension UInt16: ConstructibleFromJSValue {} +extension UInt32: ConstructibleFromJSValue {} +extension UInt64: ConstructibleFromJSValue {} diff --git a/Sources/JavaScriptKit/ConvertibleToJSValue.swift b/Sources/JavaScriptKit/ConvertibleToJSValue.swift index 83680ad0..572e867b 100644 --- a/Sources/JavaScriptKit/ConvertibleToJSValue.swift +++ b/Sources/JavaScriptKit/ConvertibleToJSValue.swift @@ -26,11 +26,17 @@ extension Bool: ConvertibleToJSValue { } extension Int: ConvertibleToJSValue { - public var jsValue: JSValue { .number(Double(self)) } + public var jsValue: JSValue { + assert(Self.bitWidth == 32) + return .number(Double(self)) + } } extension UInt: ConvertibleToJSValue { - public var jsValue: JSValue { .number(Double(self)) } + public var jsValue: JSValue { + assert(Self.bitWidth == 32) + return .number(Double(self)) + } } extension Float: ConvertibleToJSValue { @@ -57,9 +63,6 @@ extension UInt32: ConvertibleToJSValue { public var jsValue: JSValue { .number(Double(self)) } } -extension UInt64: ConvertibleToJSValue { - public var jsValue: JSValue { .number(Double(self)) } -} extension Int8: ConvertibleToJSValue { public var jsValue: JSValue { .number(Double(self)) } @@ -73,10 +76,6 @@ extension Int32: ConvertibleToJSValue { public var jsValue: JSValue { .number(Double(self)) } } -extension Int64: ConvertibleToJSValue { - public var jsValue: JSValue { .number(Double(self)) } -} - extension JSString: ConvertibleToJSValue { public var jsValue: JSValue { .string(self) } } @@ -191,8 +190,6 @@ extension Array: ConstructibleFromJSValue where Element: ConstructibleFromJSValu extension RawJSValue: ConvertibleToJSValue { public var jsValue: JSValue { switch kind { - case .invalid: - fatalError() case .boolean: return .boolean(payload1 != 0) case .number: @@ -209,6 +206,8 @@ extension RawJSValue: ConvertibleToJSValue { return .function(JSFunction(id: UInt32(payload1))) case .symbol: return .symbol(JSSymbol(id: UInt32(payload1))) + case .bigInt: + return .bigInt(JSBigInt(id: UInt32(payload1))) } } } @@ -243,6 +242,9 @@ extension JSValue { case let .symbol(symbolRef): kind = .symbol payload1 = JavaScriptPayload1(symbolRef.id) + case let .bigInt(bigIntRef): + kind = .bigInt + payload1 = JavaScriptPayload1(bigIntRef.id) } let rawValue = RawJSValue(kind: kind, payload1: payload1, payload2: payload2) return body(rawValue) diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSBigInt.swift b/Sources/JavaScriptKit/FundamentalObjects/JSBigInt.swift new file mode 100644 index 00000000..4513c14a --- /dev/null +++ b/Sources/JavaScriptKit/FundamentalObjects/JSBigInt.swift @@ -0,0 +1,34 @@ +import _CJavaScriptKit + +private let constructor = JSObject.global.BigInt.function! + +public final class JSBigInt: JSObject { + @_spi(JSObject_id) + override public init(id: JavaScriptObjectRef) { + super.init(id: id) + } + + override public class func construct(from value: JSValue) -> Self? { + value.bigInt as? Self + } + + override public var jsValue: JSValue { + .bigInt(self) + } + + public func clamped(bitSize: Int, signed: Bool) -> JSBigInt { + if signed { + return constructor.asIntN!(bitSize, self).bigInt! + } else { + return constructor.asUintN!(bitSize, self).bigInt! + } + } +} + +public protocol JSBigIntExtended: JSBigInt { + var int64Value: Int64 { get } + var uInt64Value: UInt64 { get } + + init(_ value: Int64) + init(unsigned value: UInt64) +} diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 427648bc..4e93853e 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -16,8 +16,10 @@ import _CJavaScriptKit /// reference counting system. @dynamicMemberLookup public class JSObject: Equatable { - internal var id: JavaScriptObjectRef - init(id: JavaScriptObjectRef) { + @_spi(JSObject_id) + public var id: JavaScriptObjectRef + @_spi(JSObject_id) + public init(id: JavaScriptObjectRef) { self.id = id } @@ -120,7 +122,7 @@ public class JSObject: Equatable { /// let animal = JSObject.global.animal.object! /// try animal.throwing.validateAge!() /// ``` - public var `throwing`: JSThrowingObject { + public var throwing: JSThrowingObject { JSThrowingObject(self) } diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift b/Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift index 3ec1b290..a0dea393 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift @@ -37,6 +37,14 @@ public class JSSymbol: JSObject { public static func key(for symbol: JSSymbol) -> String? { Symbol.keyFor!(symbol).string } + + override public class func construct(from value: JSValue) -> Self? { + return value.symbol as? Self + } + + override public var jsValue: JSValue { + .symbol(self) + } } extension JSSymbol { diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift index b001dc7a..973dfcb5 100644 --- a/Sources/JavaScriptKit/JSValue.swift +++ b/Sources/JavaScriptKit/JSValue.swift @@ -11,6 +11,7 @@ public enum JSValue: Equatable { case undefined case function(JSFunction) case symbol(JSSymbol) + case bigInt(JSBigInt) /// Returns the `Bool` value of this JS value if its type is boolean. /// If not, returns `nil`. @@ -68,6 +69,8 @@ public enum JSValue: Equatable { } } + /// Returns the `JSSymbol` of this JS value if its type is function. + /// If not, returns `nil`. public var symbol: JSSymbol? { switch self { case let .symbol(symbol): return symbol @@ -75,6 +78,15 @@ public enum JSValue: Equatable { } } + /// Returns the `JSBigInt` of this JS value if its type is function. + /// If not, returns `nil`. + public var bigInt: JSBigInt? { + switch self { + case let .bigInt(bigInt): return bigInt + default: return nil + } + } + /// Returns the `true` if this JS value is null. /// If not, returns `false`. public var isNull: Bool { @@ -233,7 +245,7 @@ public extension JSValue { /// - Returns: The result of `instanceof` in the JavaScript environment. func isInstanceOf(_ constructor: JSFunction) -> Bool { switch self { - case .boolean, .string, .number, .null, .undefined, .symbol: + case .boolean, .string, .number, .null, .undefined, .symbol, .bigInt: return false case let .object(ref): return ref.isInstanceOf(constructor) @@ -245,20 +257,8 @@ public extension JSValue { extension JSValue: CustomStringConvertible { public var description: String { - switch self { - case let .boolean(boolean): - return boolean.description - case let .string(string): - return string.description - case let .number(number): - return number.description - case let .object(object), let .function(object as JSObject), - .symbol(let object as JSObject): - return object.toString!().fromJSValue()! - case .null: - return "null" - case .undefined: - return "undefined" - } + // per https://tc39.es/ecma262/multipage/text-processing.html#sec-string-constructor-string-value + // this always returns a string + JSObject.global.String.function!(self).string! } } diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index 013c49e2..4cde273f 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -1,7 +1,7 @@ import _CJavaScriptKit /// Note: -/// Define all runtime functions stub which are imported from JavaScript environment. +/// Define all runtime function stubs which are imported from JavaScript environment. /// SwiftPM doesn't support WebAssembly target yet, so we need to define them to /// avoid link failure. /// When running with JavaScript runtime library, they are ignored completely. @@ -102,5 +102,4 @@ import _CJavaScriptKit _: UnsafeMutablePointer! ) { fatalError() } func _release(_: JavaScriptObjectRef) { fatalError() } - #endif diff --git a/Sources/_CJavaScriptBigIntSupport/_CJavaScriptKit+I64.c b/Sources/_CJavaScriptBigIntSupport/_CJavaScriptKit+I64.c new file mode 100644 index 00000000..e6b1f456 --- /dev/null +++ b/Sources/_CJavaScriptBigIntSupport/_CJavaScriptKit+I64.c @@ -0,0 +1 @@ +// empty file to appease build process diff --git a/Sources/_CJavaScriptBigIntSupport/include/_CJavaScriptKit+I64.h b/Sources/_CJavaScriptBigIntSupport/include/_CJavaScriptKit+I64.h new file mode 100644 index 00000000..dc898c43 --- /dev/null +++ b/Sources/_CJavaScriptBigIntSupport/include/_CJavaScriptKit+I64.h @@ -0,0 +1,23 @@ + +#ifndef _CJavaScriptBigIntSupport_h +#define _CJavaScriptBigIntSupport_h + +#include <_CJavaScriptKit.h> + +/// Converts the provided Int64 or UInt64 to a BigInt. +/// +/// @param value The value to convert. +/// @param is_signed Whether to treat the value as a signed integer or not. +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_i64_to_bigint"))) +extern JavaScriptObjectRef _i64_to_bigint(const long long value, bool is_signed); + +/// Converts the provided BigInt to an Int64 or UInt64. +/// +/// @param ref The target JavaScript object. +/// @param is_signed Whether to treat the return value as a signed integer or not. +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_bigint_to_i64"))) +extern long long _bigint_to_i64(const JavaScriptObjectRef ref, bool is_signed); + +#endif /* _CJavaScriptBigIntSupport_h */ diff --git a/Sources/_CJavaScriptBigIntSupport/include/module.modulemap b/Sources/_CJavaScriptBigIntSupport/include/module.modulemap new file mode 100644 index 00000000..944871cd --- /dev/null +++ b/Sources/_CJavaScriptBigIntSupport/include/module.modulemap @@ -0,0 +1,4 @@ +module _CJavaScriptBigIntSupport { + header "_CJavaScriptKit+I64.h" + export * +} diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c index c263b8f7..f2f03b82 100644 --- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c +++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c @@ -36,7 +36,7 @@ void swjs_cleanup_host_function_call(void *argv_buffer) { /// this and `SwiftRuntime.version` in `./Runtime/src/index.ts`. __attribute__((export_name("swjs_library_version"))) int swjs_library_version(void) { - return 706; + return 707; } int _library_features(void); diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index daf40514..c3b56c14 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -13,7 +13,6 @@ typedef unsigned int JavaScriptHostFuncRef; /// `JavaScriptValueKind` represents the kind of JavaScript primitive value. typedef enum __attribute__((enum_extensibility(closed))) { - JavaScriptValueKindInvalid = -1, JavaScriptValueKindBoolean = 0, JavaScriptValueKindString = 1, JavaScriptValueKindNumber = 2, @@ -22,6 +21,7 @@ typedef enum __attribute__((enum_extensibility(closed))) { JavaScriptValueKindUndefined = 5, JavaScriptValueKindFunction = 6, JavaScriptValueKindSymbol = 7, + JavaScriptValueKindBigInt = 8, } JavaScriptValueKind; typedef struct { @@ -61,7 +61,7 @@ typedef double JavaScriptPayload2; /// payload1: the target `JavaScriptHostFuncRef` /// payload2: 0 /// -/// For symbol value: +/// For symbol and bigint values: /// payload1: `JavaScriptObjectRef` /// payload2: 0 /// @@ -300,6 +300,7 @@ __attribute__((__import_module__("javascript_kit"), __import_name__("swjs_release"))) extern void _release(const JavaScriptObjectRef ref); + #endif #endif /* _CJavaScriptKit_h */