From 36dd08e143a7c42724f18b7f71f8d1f9240529d2 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 29 Apr 2021 13:55:38 -0400 Subject: [PATCH 1/5] Refactor JSClosure to leverage FinalizationRegistry --- .../Sources/BenchmarkTests/Benchmark.swift | 1 - .../Sources/PrimaryTests/main.swift | 49 ++++++------- Runtime/src/index.ts | 19 ++++- Runtime/tsconfig.json | 1 + Sources/JavaScriptKit/Deprecated.swift | 3 + .../FundamentalObjects/JSClosure.swift | 70 +++++-------------- Sources/_CJavaScriptKit/_CJavaScriptKit.c | 7 ++ package-lock.json | 44 ++---------- package.json | 2 +- 9 files changed, 71 insertions(+), 125 deletions(-) diff --git a/IntegrationTests/TestSuites/Sources/BenchmarkTests/Benchmark.swift b/IntegrationTests/TestSuites/Sources/BenchmarkTests/Benchmark.swift index bd145c3ac..3cebb9d07 100644 --- a/IntegrationTests/TestSuites/Sources/BenchmarkTests/Benchmark.swift +++ b/IntegrationTests/TestSuites/Sources/BenchmarkTests/Benchmark.swift @@ -15,6 +15,5 @@ class Benchmark { return .undefined } runner("\(title)/\(name)", jsBody) - jsBody.release() } } diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index e93dfc1b6..ca5f4c994 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -191,36 +191,36 @@ try test("Closure Lifetime") { return arguments[0] } try expectEqual(evalClosure(c1, JSValue.number(1.0)), .number(1.0)) - c1.release() } do { - let c1 = JSClosure { arguments in - return arguments[0] - } + let c1 = JSClosure { _ in .undefined } + c1.release() c1.release() - // Call a released closure - _ = try expectThrow(try evalClosure.throws(c1)) } do { - let c1 = JSClosure { _ in - // JSClosure will be deallocated before `release()` - _ = JSClosure { _ in .undefined } - return .undefined - } - _ = try expectThrow(try evalClosure.throws(c1)) - c1.release() + let array = JSObject.global.Array.function!.new() + _ = array.push!(JSClosure { _ in .number(3) }) + try expectEqual(array[0].function!().number, 3.0) } +// do { +// let weakRef = { () -> JSObject in +// let c1 = JSClosure { _ in .undefined } +// return JSObject.global.WeakRef.function!.new(c1) +// }() +// +// // unsure if this will actually work since GC may not run immediately +// try expectEqual(weakRef.deref!(), .undefined) +// } + do { let c1 = JSOneshotClosure { _ in return .boolean(true) } try expectEqual(evalClosure(c1), .boolean(true)) - // second call will cause `fatalError` that can be caught as a JavaScript exception - _ = try expectThrow(try evalClosure.throws(c1)) - // OneshotClosure won't call fatalError even if it's deallocated before `release` + try expectEqual(evalClosure(c1), .boolean(true)) } } @@ -253,8 +253,6 @@ try test("Host Function Registration") { try expectEqual(call_host_1Func(), .number(1)) try expectEqual(isHostFunc1Called, true) - hostFunc1.release() - let hostFunc2 = JSClosure { (arguments) -> JSValue in do { let input = try expectNumber(arguments[0]) @@ -266,7 +264,6 @@ try test("Host Function Registration") { try expectEqual(evalClosure(hostFunc2, 3), .number(6)) _ = try expectString(evalClosure(hostFunc2, true)) - hostFunc2.release() } try test("New Object Construction") { @@ -375,19 +372,14 @@ try test("ObjectRef Lifetime") { // } // ``` - let identity = JSClosure { $0[0] } let ref1 = getJSValue(this: .global, name: "globalObject1").object! - let ref2 = evalClosure(identity, ref1).object! + let ref2 = evalClosure(JSClosure { $0[0] }, ref1).object! try expectEqual(ref1.prop_2, .number(2)) try expectEqual(ref2.prop_2, .number(2)) - identity.release() } func closureScope() -> ObjectIdentifier { - let closure = JSClosure { _ in .undefined } - let result = ObjectIdentifier(closure) - closure.release() - return result + ObjectIdentifier(JSClosure { _ in .undefined }) } try test("Closure Identifiers") { @@ -513,7 +505,7 @@ try test("Timer") { interval = JSTimer(millisecondsDelay: 5, isRepeating: true) { // ensure that JSTimer is living try! expectNotNil(interval) - // verify that at least `timeoutMilliseconds * count` passed since the `timeout` + // verify that at least `timeoutMilliseconds * count` passed since the `timeout` // timer started try! expectEqual(start + timeoutMilliseconds * count <= JSDate().valueOf(), true) @@ -549,7 +541,8 @@ try test("Promise") { exp1.fulfill() return JSValue.undefined } - .catch { _ -> JSValue in + .catch { err -> JSValue in + print(err.object!.stack.string!) fatalError("Not fired due to no throw") } .finally { exp1.fulfill() } diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 144fed737..516840f1c 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -29,6 +29,8 @@ interface SwiftRuntimeExportedFunctions { argc: number, callback_func_ref: ref ): void; + + swjs_free_host_function(host_func_id: number): void; } enum JavaScriptValueKind { @@ -118,11 +120,22 @@ class SwiftRuntimeHeap { export class SwiftRuntime { private instance: WebAssembly.Instance | null; private heap: SwiftRuntimeHeap; + private functionRegistry: FinalizationRegistry; private version: number = 701; constructor() { this.instance = null; this.heap = new SwiftRuntimeHeap(); + this.functionRegistry = new FinalizationRegistry( + this.handleFree.bind(this) + ); + } + + handleFree(id: unknown) { + if (!this.instance || typeof id !== "number") return; + const exports = (this.instance + .exports as any) as SwiftRuntimeExportedFunctions; + exports.swjs_free_host_function(id); } setInstance(instance: WebAssembly.Instance) { @@ -452,12 +465,14 @@ export class SwiftRuntime { host_func_id: number, func_ref_ptr: pointer ) => { - const func_ref = this.heap.retain(function () { + const func = function () { return callHostFunction( host_func_id, Array.prototype.slice.call(arguments) ); - }); + }; + const func_ref = this.heap.retain(func); + this.functionRegistry.register(func, func_ref); writeUint32(func_ref_ptr, func_ref); }, swjs_call_throwing_new: ( diff --git a/Runtime/tsconfig.json b/Runtime/tsconfig.json index 011c119de..3182659ca 100644 --- a/Runtime/tsconfig.json +++ b/Runtime/tsconfig.json @@ -7,6 +7,7 @@ "rootDir": "src", "strict": true, "target": "es2017", + "lib": ["es2017", "DOM", "ESNext.WeakRef"], "skipLibCheck": true }, "include": ["src/**/*"], diff --git a/Sources/JavaScriptKit/Deprecated.swift b/Sources/JavaScriptKit/Deprecated.swift index 0d81888c9..11daf562b 100644 --- a/Sources/JavaScriptKit/Deprecated.swift +++ b/Sources/JavaScriptKit/Deprecated.swift @@ -15,3 +15,6 @@ public typealias JSValueConstructible = ConstructibleFromJSValue @available(*, deprecated, renamed: "JSValueCompatible") public typealias JSValueCodable = JSValueCompatible + +@available(*, deprecated, renamed: "JSClosure") +public typealias JSOneshotClosure = JSClosure diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift index cd96dd0b5..78136cbe8 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift @@ -1,5 +1,6 @@ import _CJavaScriptKit +fileprivate var closureRef: JavaScriptHostFuncRef = 0 fileprivate var sharedClosures: [JavaScriptHostFuncRef: ([JSValue]) -> JSValue] = [:] /// JSClosureProtocol abstracts closure object in JavaScript, whose lifetime is manualy managed @@ -10,40 +11,9 @@ public protocol JSClosureProtocol: JSValueCompatible { func release() } -/// `JSOneshotClosure` is a JavaScript function that can be called only once. -public class JSOneshotClosure: JSObject, JSClosureProtocol { - private var hostFuncRef: JavaScriptHostFuncRef = 0 - - public init(_ body: @escaping ([JSValue]) -> JSValue) { - // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. - super.init(id: 0) - let objectId = ObjectIdentifier(self) - let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue)) - // 2. Retain the given body in static storage by `funcRef`. - sharedClosures[funcRef] = { - defer { self.release() } - return body($0) - } - // 3. Create a new JavaScript function which calls the given Swift function. - var objectRef: JavaScriptObjectRef = 0 - _create_function(funcRef, &objectRef) - - hostFuncRef = funcRef - id = objectRef - } - - /// Release this function resource. - /// After calling `release`, calling this function from JavaScript will fail. - public func release() { - sharedClosures[hostFuncRef] = nil - } -} /// `JSClosure` represents a JavaScript function the body of which is written in Swift. /// This type can be passed as a callback handler to JavaScript functions. -/// Note that the lifetime of `JSClosure` should be managed by users manually -/// due to GC boundary between Swift and JavaScript. -/// For further discussion, see also [swiftwasm/JavaScriptKit #33](https://github.com/swiftwasm/JavaScriptKit/pull/33) /// /// e.g. /// ```swift @@ -55,12 +25,10 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol { /// button.addEventListener!("click", JSValue.function(eventListenter)) /// ... /// button.removeEventListener!("click", JSValue.function(eventListenter)) -/// eventListenter.release() /// ``` /// public class JSClosure: JSObject, JSClosureProtocol { private var hostFuncRef: JavaScriptHostFuncRef = 0 - var isReleased: Bool = false @available(*, deprecated, message: "This initializer will be removed in the next minor version update. Please use `init(_ body: @escaping ([JSValue]) -> JSValue)` and add `return .undefined` to the end of your closure") @_disfavoredOverload @@ -72,33 +40,29 @@ public class JSClosure: JSObject, JSClosureProtocol { } public init(_ body: @escaping ([JSValue]) -> JSValue) { - // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. - super.init(id: 0) - let objectId = ObjectIdentifier(self) - let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue)) - // 2. Retain the given body in static storage by `funcRef`. - sharedClosures[funcRef] = body - // 3. Create a new JavaScript function which calls the given Swift function. + self.hostFuncRef = closureRef + closureRef += 1 + + // Retain the given body in static storage by `closureRef`. + sharedClosures[self.hostFuncRef] = body + + // Create a new JavaScript function which calls the given Swift function. var objectRef: JavaScriptObjectRef = 0 - _create_function(funcRef, &objectRef) + _create_function(self.hostFuncRef, &objectRef) - hostFuncRef = funcRef - id = objectRef + super.init(id: objectRef) } - public func release() { - isReleased = true - sharedClosures[hostFuncRef] = nil - } + @available(*, deprecated, message: "JSClosure.release() is no longer necessary") + public func release() {} +} - deinit { - guard isReleased else { - // Safari doesn't support `FinalizationRegistry`, so we cannot automatically manage the lifetime of Swift objects - fatalError("release() must be called on JSClosure objects manually before they are deallocated") - } - } +@_cdecl("_free_host_function_impl") +func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) { + sharedClosures[hostFuncRef] = nil } + // MARK: - `JSClosure` mechanism note // // 1. Create a thunk in the JavaScript world, which has a reference diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c index fd9e39cd8..8834cd214 100644 --- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c +++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c @@ -14,6 +14,13 @@ void swjs_call_host_function(const JavaScriptHostFuncRef host_func_ref, _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); +} + __attribute__((export_name("swjs_prepare_host_function_call"))) void *swjs_prepare_host_function_call(const int argc) { return malloc(argc * sizeof(RawJSValue)); diff --git a/package-lock.json b/package-lock.json index ad1e9fd93..eee60ccb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,44 +1,8 @@ { "name": "javascript-kit-swift", "version": "0.10.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "javascript-kit-swift", - "version": "0.10.0", - "license": "MIT", - "devDependencies": { - "prettier": "2.1.2", - "typescript": "^4.0.2" - } - }, - "node_modules/prettier": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", - "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", - "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - } - }, "dependencies": { "prettier": { "version": "2.1.2", @@ -47,9 +11,9 @@ "dev": true }, "typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", - "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", "dev": true } } diff --git a/package.json b/package.json index b06a4ec24..526c992f5 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,6 @@ "license": "MIT", "devDependencies": { "prettier": "2.1.2", - "typescript": "^4.0.2" + "typescript": "^4.2.4" } } From c77e069c2d9d8b819a9e2649f6a8643d07c0f43f Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Sat, 5 Jun 2021 13:30:47 -0400 Subject: [PATCH 2/5] Restore support for older runtimes using JSOneshotClosure + JSUnretainedClosure --- Runtime/src/index.ts | 8 ++- Sources/JavaScriptKit/Deprecated.swift | 3 - .../FundamentalObjects/JSClosure.swift | 64 +++++++++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 516840f1c..42a0c7259 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -126,9 +126,11 @@ export class SwiftRuntime { constructor() { this.instance = null; this.heap = new SwiftRuntimeHeap(); - this.functionRegistry = new FinalizationRegistry( - this.handleFree.bind(this) - ); + if (typeof FinalizationRegistry !== "undefined") { + this.functionRegistry = new FinalizationRegistry( + this.handleFree.bind(this) + ); + } } handleFree(id: unknown) { diff --git a/Sources/JavaScriptKit/Deprecated.swift b/Sources/JavaScriptKit/Deprecated.swift index 11daf562b..0d81888c9 100644 --- a/Sources/JavaScriptKit/Deprecated.swift +++ b/Sources/JavaScriptKit/Deprecated.swift @@ -15,6 +15,3 @@ public typealias JSValueConstructible = ConstructibleFromJSValue @available(*, deprecated, renamed: "JSValueCompatible") public typealias JSValueCodable = JSValueCompatible - -@available(*, deprecated, renamed: "JSClosure") -public typealias JSOneshotClosure = JSClosure diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift index 78136cbe8..f8411528d 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift @@ -113,3 +113,67 @@ func _call_host_function_impl( let callbackFuncRef = JSFunction(id: callbackFuncRef) _ = callbackFuncRef(result) } + +// MARK: - Legacy Closure Types + +/// `JSOneshotClosure` is a JavaScript function that can be called only once. +/// It is recommended to use `JSClosure` instead if your target runtimes support `FinalizationRegistry`. +public class JSOneshotClosure: JSObject, JSClosureProtocol { + private var hostFuncRef: JavaScriptHostFuncRef = 0 + + public init(_ body: @escaping ([JSValue]) -> JSValue) { + // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. + super.init(id: 0) + let objectId = ObjectIdentifier(self) + let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue)) + // 2. Retain the given body in static storage by `funcRef`. + sharedClosures[funcRef] = { + defer { self.release() } + return body($0) + } + // 3. Create a new JavaScript function which calls the given Swift function. + var objectRef: JavaScriptObjectRef = 0 + _create_function(funcRef, &objectRef) + + hostFuncRef = funcRef + id = objectRef + } + + /// Release this function resource. + /// After calling `release`, calling this function from JavaScript will fail. + public func release() { + sharedClosures[hostFuncRef] = nil + } +} + +public class JSUnretainedClosure: JSObject, JSClosureProtocol { + private var hostFuncRef: JavaScriptHostFuncRef = 0 + var isReleased: Bool = false + + public init(_ body: @escaping ([JSValue]) -> JSValue) { + // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. + super.init(id: 0) + let objectId = ObjectIdentifier(self) + let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue)) + // 2. Retain the given body in static storage by `funcRef`. + sharedClosures[funcRef] = body + // 3. Create a new JavaScript function which calls the given Swift function. + var objectRef: JavaScriptObjectRef = 0 + _create_function(funcRef, &objectRef) + + hostFuncRef = funcRef + id = objectRef + } + + public func release() { + isReleased = true + sharedClosures[hostFuncRef] = nil + } + + deinit { + guard isReleased else { + // Safari doesn't support `FinalizationRegistry`, so we cannot automatically manage the lifetime of Swift objects + fatalError("release() must be called on JSClosure objects manually before they are deallocated") + } + } +} From ae62488c691271f96d931b505d5b96756356e0f0 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 3 Sep 2021 22:20:16 -0400 Subject: [PATCH 3/5] Bump TS version --- IntegrationTests/package-lock.json | 156 ++++++++++------------------- package-lock.json | 44 +++++++- package.json | 2 +- 3 files changed, 95 insertions(+), 107 deletions(-) diff --git a/IntegrationTests/package-lock.json b/IntegrationTests/package-lock.json index 50727e508..fb83681e6 100644 --- a/IntegrationTests/package-lock.json +++ b/IntegrationTests/package-lock.json @@ -16,24 +16,34 @@ "license": "MIT", "devDependencies": { "prettier": "2.1.2", - "typescript": "^4.0.2" + "typescript": "^4.4.2" } }, "../node_modules/prettier": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", - "dev": true + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } }, "../node_modules/typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", - "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", - "dev": true + "version": "4.4.2", + "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } }, "node_modules/@wasmer/wasi": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@wasmer/wasi/-/wasi-0.12.0.tgz", "integrity": "sha512-FJhLZKAfLWm/yjQI7eCRHNbA8ezmb7LSpUYFkHruZXs2mXk2+DaQtSElEtOoNrVQ4vApTyVaAd5/b7uEu8w6wQ==", "dependencies": { "browser-process-hrtime": "^1.0.0", @@ -44,7 +54,6 @@ }, "node_modules/@wasmer/wasmfs": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@wasmer/wasmfs/-/wasmfs-0.12.0.tgz", "integrity": "sha512-m1ftchyQ1DfSenm5XbbdGIpb6KJHH5z0gODo3IZr6lATkj4WXfX/UeBTZ0aG9YVShBp+kHLdUHvOkqjy6p/GWw==", "dependencies": { "memfs": "3.0.4", @@ -54,12 +63,10 @@ }, "node_modules/base64-js": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "node_modules/bl": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", "dependencies": { "buffer": "^5.5.0", @@ -69,12 +76,10 @@ }, "node_modules/browser-process-hrtime": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "node_modules/buffer": { "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", "dependencies": { "base64-js": "^1.0.2", @@ -83,12 +88,10 @@ }, "node_modules/buffer-es6": { "version": "4.9.3", - "resolved": "https://registry.npmjs.org/buffer-es6/-/buffer-es6-4.9.3.tgz", "integrity": "sha1-8mNHuC33b9N+GLy1KIxJcM/VxAQ=" }, "node_modules/end-of-stream": { "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dependencies": { "once": "^1.4.0" @@ -96,27 +99,22 @@ }, "node_modules/fast-extend": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fast-extend/-/fast-extend-1.0.2.tgz", "integrity": "sha512-XXA9RmlPatkFKUzqVZAFth18R4Wo+Xug/S+C7YlYA3xrXwfPlW3dqNwOb4hvQo7wZJ2cNDYhrYuPzVOfHy5/uQ==" }, "node_modules/fs-constants": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "node_modules/fs-monkey": { "version": "0.3.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-0.3.3.tgz", "integrity": "sha512-FNUvuTAJ3CqCQb5ELn+qCbGR/Zllhf2HtwsdAtBi59s1WeCjKMT81fHcSu7dwIskqGVK+MmOrb7VOBlq3/SItw==" }, "node_modules/ieee754": { "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/javascript-kit-swift": { @@ -125,7 +123,6 @@ }, "node_modules/memfs": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.0.4.tgz", "integrity": "sha512-OcZEzwX9E5AoY8SXjuAvw0DbIAYwUzV/I236I8Pqvrlv7sL/Y0E9aRCon05DhaV8pg1b32uxj76RgW0s5xjHBA==", "dependencies": { "fast-extend": "1.0.2", @@ -134,7 +131,6 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dependencies": { "wrappy": "1" @@ -142,17 +138,14 @@ }, "node_modules/pako": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "node_modules/path-browserify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" }, "node_modules/randombytes": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dependencies": { "safe-buffer": "^5.1.0" @@ -160,7 +153,6 @@ }, "node_modules/randomfill": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dependencies": { "randombytes": "^2.0.5", @@ -169,22 +161,36 @@ }, "node_modules/readable-stream": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dependencies": { "safe-buffer": "~5.2.0" @@ -192,7 +198,6 @@ }, "node_modules/tar-stream": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", "dependencies": { "bl": "^4.0.3", @@ -200,24 +205,23 @@ "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" } }, "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } }, "dependencies": { "@wasmer/wasi": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@wasmer/wasi/-/wasi-0.12.0.tgz", - "integrity": "sha512-FJhLZKAfLWm/yjQI7eCRHNbA8ezmb7LSpUYFkHruZXs2mXk2+DaQtSElEtOoNrVQ4vApTyVaAd5/b7uEu8w6wQ==", "requires": { "browser-process-hrtime": "^1.0.0", "buffer-es6": "^4.9.3", @@ -227,8 +231,6 @@ }, "@wasmer/wasmfs": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@wasmer/wasmfs/-/wasmfs-0.12.0.tgz", - "integrity": "sha512-m1ftchyQ1DfSenm5XbbdGIpb6KJHH5z0gODo3IZr6lATkj4WXfX/UeBTZ0aG9YVShBp+kHLdUHvOkqjy6p/GWw==", "requires": { "memfs": "3.0.4", "pako": "^1.0.11", @@ -236,14 +238,10 @@ } }, "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + "version": "1.3.1" }, "bl": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -251,82 +249,58 @@ } }, "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + "version": "1.0.0" }, "buffer": { "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" } }, "buffer-es6": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/buffer-es6/-/buffer-es6-4.9.3.tgz", - "integrity": "sha1-8mNHuC33b9N+GLy1KIxJcM/VxAQ=" + "version": "4.9.3" }, "end-of-stream": { "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "requires": { "once": "^1.4.0" } }, "fast-extend": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fast-extend/-/fast-extend-1.0.2.tgz", - "integrity": "sha512-XXA9RmlPatkFKUzqVZAFth18R4Wo+Xug/S+C7YlYA3xrXwfPlW3dqNwOb4hvQo7wZJ2cNDYhrYuPzVOfHy5/uQ==" + "version": "1.0.2" }, "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + "version": "1.0.0" }, "fs-monkey": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-0.3.3.tgz", - "integrity": "sha512-FNUvuTAJ3CqCQb5ELn+qCbGR/Zllhf2HtwsdAtBi59s1WeCjKMT81fHcSu7dwIskqGVK+MmOrb7VOBlq3/SItw==" + "version": "0.3.3" }, "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + "version": "1.1.13" }, "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "version": "2.0.4" }, "javascript-kit-swift": { "version": "file:..", "requires": { "prettier": "2.1.2", - "typescript": "^4.0.2" + "typescript": "^4.4.2" }, "dependencies": { "prettier": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", - "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", "dev": true }, "typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", - "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "version": "4.4.2", "dev": true } } }, "memfs": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.0.4.tgz", - "integrity": "sha512-OcZEzwX9E5AoY8SXjuAvw0DbIAYwUzV/I236I8Pqvrlv7sL/Y0E9aRCon05DhaV8pg1b32uxj76RgW0s5xjHBA==", "requires": { "fast-extend": "1.0.2", "fs-monkey": "0.3.3" @@ -334,34 +308,24 @@ }, "once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + "version": "1.0.11" }, "path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + "version": "1.0.1" }, "randombytes": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "requires": { "safe-buffer": "^5.1.0" } }, "randomfill": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "requires": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" @@ -369,8 +333,6 @@ }, "readable-stream": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -378,22 +340,16 @@ } }, "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "version": "5.2.1" }, "string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { "safe-buffer": "~5.2.0" } }, "tar-stream": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", "requires": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -403,14 +359,10 @@ } }, "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "version": "1.0.2" }, "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "version": "1.0.2" } } } diff --git a/package-lock.json b/package-lock.json index 2a0447f5b..1213bf3cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,44 @@ { "name": "javascript-kit-swift", "version": "0.10.1", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "javascript-kit-swift", + "version": "0.10.1", + "license": "MIT", + "devDependencies": { + "prettier": "2.1.2", + "typescript": "^4.4.2" + } + }, + "node_modules/prettier": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", + "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/typescript": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz", + "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, "dependencies": { "prettier": { "version": "2.1.2", @@ -11,9 +47,9 @@ "dev": true }, "typescript": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", - "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz", + "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", "dev": true } } diff --git a/package.json b/package.json index 0941c203f..856474df7 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,6 @@ "license": "MIT", "devDependencies": { "prettier": "2.1.2", - "typescript": "^4.2.4" + "typescript": "^4.4.2" } } From 43a33eed17c13ba575b30c722679a8134e7ef155 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 3 Sep 2021 22:36:36 -0400 Subject: [PATCH 4/5] Fix build errors --- Runtime/src/index.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 42a0c7259..3b99447e7 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -120,17 +120,15 @@ class SwiftRuntimeHeap { export class SwiftRuntime { private instance: WebAssembly.Instance | null; private heap: SwiftRuntimeHeap; - private functionRegistry: FinalizationRegistry; + private functionRegistry: FinalizationRegistry; private version: number = 701; constructor() { this.instance = null; this.heap = new SwiftRuntimeHeap(); - if (typeof FinalizationRegistry !== "undefined") { - this.functionRegistry = new FinalizationRegistry( - this.handleFree.bind(this) - ); - } + this.functionRegistry = new FinalizationRegistry( + this.handleFree.bind(this) + ); } handleFree(id: unknown) { From 033ff487a8c67bb8110ef9ade4e879a8afd3e091 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 10 Sep 2021 23:59:43 +0900 Subject: [PATCH 5/5] Add escape hatch for old environments (#139) --- IntegrationTests/Makefile | 4 +- .../Sources/PrimaryTests/main.swift | 36 ++++- Makefile | 4 +- Runtime/src/index.ts | 59 +++++-- Sources/JavaScriptKit/Features.swift | 12 ++ .../FundamentalObjects/JSClosure.swift | 150 +++++++++--------- Sources/_CJavaScriptKit/_CJavaScriptKit.c | 11 +- 7 files changed, 183 insertions(+), 93 deletions(-) create mode 100644 Sources/JavaScriptKit/Features.swift diff --git a/IntegrationTests/Makefile b/IntegrationTests/Makefile index 66eeda4c6..cdb295704 100644 --- a/IntegrationTests/Makefile +++ b/IntegrationTests/Makefile @@ -1,11 +1,13 @@ CONFIGURATION ?= debug +SWIFT_BUILD_FLAGS ?= FORCE: TestSuites/.build/$(CONFIGURATION)/%.wasm: FORCE swift build --package-path TestSuites \ --product $(basename $(notdir $@)) \ --triple wasm32-unknown-wasi \ - --configuration $(CONFIGURATION) + --configuration $(CONFIGURATION) \ + $(SWIFT_BUILD_FLAGS) dist/%.wasm: TestSuites/.build/$(CONFIGURATION)/%.wasm mkdir -p dist diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 8d32792e5..6c054a791 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -197,18 +197,26 @@ try test("Closure Lifetime") { return arguments[0] } try expectEqual(evalClosure(c1, JSValue.number(1.0)), .number(1.0)) +#if JAVASCRIPTKIT_WITHOUT_WEAKREFS + c1.release() +#endif } do { let c1 = JSClosure { _ in .undefined } +#if JAVASCRIPTKIT_WITHOUT_WEAKREFS c1.release() - c1.release() +#endif } do { let array = JSObject.global.Array.function!.new() - _ = array.push!(JSClosure { _ in .number(3) }) + let c1 = JSClosure { _ in .number(3) } + _ = array.push!(c1) try expectEqual(array[0].function!().number, 3.0) +#if JAVASCRIPTKIT_WITHOUT_WEAKREFS + c1.release() +#endif } // do { @@ -221,6 +229,7 @@ try test("Closure Lifetime") { // try expectEqual(weakRef.deref!(), .undefined) // } +#if JAVASCRIPTKIT_WITHOUT_WEAKREFS do { let c1 = JSOneshotClosure { _ in return .boolean(true) @@ -230,6 +239,7 @@ try test("Closure Lifetime") { try expectCrashByCall(ofClosure: c1) // OneshotClosure won't call fatalError even if it's deallocated before `release` } +#endif } try test("Host Function Registration") { @@ -261,6 +271,10 @@ try test("Host Function Registration") { try expectEqual(call_host_1Func(), .number(1)) try expectEqual(isHostFunc1Called, true) +#if JAVASCRIPTKIT_WITHOUT_WEAKREFS + hostFunc1.release() +#endif + let hostFunc2 = JSClosure { (arguments) -> JSValue in do { let input = try expectNumber(arguments[0]) @@ -272,6 +286,10 @@ try test("Host Function Registration") { try expectEqual(evalClosure(hostFunc2, 3), .number(6)) _ = try expectString(evalClosure(hostFunc2, true)) + +#if JAVASCRIPTKIT_WITHOUT_WEAKREFS + hostFunc2.release() +#endif } try test("New Object Construction") { @@ -380,14 +398,23 @@ try test("ObjectRef Lifetime") { // } // ``` + let identity = JSClosure { $0[0] } let ref1 = getJSValue(this: .global, name: "globalObject1").object! - let ref2 = evalClosure(JSClosure { $0[0] }, ref1).object! + let ref2 = evalClosure(identity, ref1).object! try expectEqual(ref1.prop_2, .number(2)) try expectEqual(ref2.prop_2, .number(2)) + +#if JAVASCRIPTKIT_WITHOUT_WEAKREFS + identity.release() +#endif } +#if JAVASCRIPTKIT_WITHOUT_WEAKREFS func closureScope() -> ObjectIdentifier { - ObjectIdentifier(JSClosure { _ in .undefined }) + let closure = JSClosure { _ in .undefined } + let result = ObjectIdentifier(closure) + closure.release() + return result } try test("Closure Identifiers") { @@ -395,6 +422,7 @@ try test("Closure Identifiers") { let oid2 = closureScope() try expectEqual(oid1, oid2) } +#endif func checkArray(_ array: [T]) throws where T: TypedArrayElement { try expectEqual(toString(JSTypedArray(array).jsValue().object!), jsStringify(array)) diff --git a/Makefile b/Makefile index b80f301aa..b48af9e2b 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,9 @@ build: test: cd IntegrationTests && \ CONFIGURATION=debug make test && \ - CONFIGURATION=release make test + CONFIGURATION=debug SWIFT_BUILD_FLAGS="-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS" make test && \ + CONFIGURATION=release make test && \ + CONFIGURATION=release SWIFT_BUILD_FLAGS="-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS" make test .PHONY: benchmark_setup benchmark_setup: diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 3b99447e7..a48e8fa72 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -21,6 +21,7 @@ if (typeof globalThis !== "undefined") { interface SwiftRuntimeExportedFunctions { swjs_library_version(): number; + swjs_library_features(): number; swjs_prepare_host_function_call(size: number): pointer; swjs_cleanup_host_function_call(argv: pointer): void; swjs_call_host_function( @@ -44,6 +45,10 @@ enum JavaScriptValueKind { Function = 6, } +enum LibraryFeatures { + WeakRefs = 1 << 0, +} + type TypedArray = | Int8ArrayConstructor | Uint8ArrayConstructor @@ -117,25 +122,31 @@ class SwiftRuntimeHeap { } } +/// Memory lifetime of closures in Swift are managed by Swift side +class SwiftClosureHeap { + private functionRegistry: FinalizationRegistry; + + constructor(exports: SwiftRuntimeExportedFunctions) { + this.functionRegistry = new FinalizationRegistry((id) => { + exports.swjs_free_host_function(id); + }); + } + + alloc(func: any, func_ref: number) { + this.functionRegistry.register(func, func_ref); + } +} + export class SwiftRuntime { private instance: WebAssembly.Instance | null; private heap: SwiftRuntimeHeap; - private functionRegistry: FinalizationRegistry; - private version: number = 701; + private _closureHeap: SwiftClosureHeap | null; + private version: number = 702; constructor() { this.instance = null; this.heap = new SwiftRuntimeHeap(); - this.functionRegistry = new FinalizationRegistry( - this.handleFree.bind(this) - ); - } - - handleFree(id: unknown) { - if (!this.instance || typeof id !== "number") return; - const exports = (this.instance - .exports as any) as SwiftRuntimeExportedFunctions; - exports.swjs_free_host_function(id); + this._closureHeap = null; } setInstance(instance: WebAssembly.Instance) { @@ -146,6 +157,28 @@ export class SwiftRuntime { throw new Error("The versions of JavaScriptKit are incompatible."); } } + get closureHeap(): SwiftClosureHeap | null { + if (this._closureHeap) return this._closureHeap; + if (!this.instance) + throw new Error("WebAssembly instance is not set yet"); + + const exports = (this.instance + .exports as any) as SwiftRuntimeExportedFunctions; + const features = exports.swjs_library_features(); + const librarySupportsWeakRef = + (features & LibraryFeatures.WeakRefs) != 0; + if (librarySupportsWeakRef) { + if (typeof FinalizationRegistry !== "undefined") { + this._closureHeap = new SwiftClosureHeap(exports); + return this._closureHeap; + } else { + throw new Error( + "The Swift part of JavaScriptKit was configured to require the availability of JavaScript WeakRefs. Please build with `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` to disable features that use WeakRefs." + ); + } + } + return null; + } importObjects() { const memory = () => { @@ -472,7 +505,7 @@ export class SwiftRuntime { ); }; const func_ref = this.heap.retain(func); - this.functionRegistry.register(func, func_ref); + this.closureHeap?.alloc(func, func_ref); writeUint32(func_ref_ptr, func_ref); }, swjs_call_throwing_new: ( diff --git a/Sources/JavaScriptKit/Features.swift b/Sources/JavaScriptKit/Features.swift new file mode 100644 index 000000000..e479003c5 --- /dev/null +++ b/Sources/JavaScriptKit/Features.swift @@ -0,0 +1,12 @@ +enum LibraryFeatures { + static let weakRefs: Int32 = 1 << 0 +} + +@_cdecl("_library_features") +func _library_features() -> Int32 { + var features: Int32 = 0 +#if !JAVASCRIPTKIT_WITHOUT_WEAKREFS + features |= LibraryFeatures.weakRefs +#endif + return features +} diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift index f8411528d..a8fcd01e9 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift @@ -1,9 +1,8 @@ import _CJavaScriptKit -fileprivate var closureRef: JavaScriptHostFuncRef = 0 -fileprivate var sharedClosures: [JavaScriptHostFuncRef: ([JSValue]) -> JSValue] = [:] - -/// JSClosureProtocol abstracts closure object in JavaScript, whose lifetime is manualy managed +/// JSClosureProtocol wraps Swift closure objects for use in JavaScript. Conforming types +/// are responsible for managing the lifetime of the closure they wrap, but can delegate that +/// task to the user by requiring an explicit `release()` call. public protocol JSClosureProtocol: JSValueCompatible { /// Release this function resource. @@ -12,6 +11,35 @@ public protocol JSClosureProtocol: JSValueCompatible { } +/// `JSOneshotClosure` is a JavaScript function that can be called only once. +public class JSOneshotClosure: JSObject, JSClosureProtocol { + private var hostFuncRef: JavaScriptHostFuncRef = 0 + + public init(_ body: @escaping ([JSValue]) -> JSValue) { + // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. + super.init(id: 0) + let objectId = ObjectIdentifier(self) + let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue)) + // 2. Retain the given body in static storage by `funcRef`. + JSClosure.sharedClosures[funcRef] = (self, { + defer { self.release() } + return body($0) + }) + // 3. Create a new JavaScript function which calls the given Swift function. + var objectRef: JavaScriptObjectRef = 0 + _create_function(funcRef, &objectRef) + + hostFuncRef = funcRef + id = objectRef + } + + /// Release this function resource. + /// After calling `release`, calling this function from JavaScript will fail. + public func release() { + JSClosure.sharedClosures[hostFuncRef] = nil + } +} + /// `JSClosure` represents a JavaScript function the body of which is written in Swift. /// This type can be passed as a callback handler to JavaScript functions. /// @@ -28,8 +56,16 @@ public protocol JSClosureProtocol: JSValueCompatible { /// ``` /// public class JSClosure: JSObject, JSClosureProtocol { + + // Note: Retain the closure object itself also to avoid funcRef conflicts + fileprivate static var sharedClosures: [JavaScriptHostFuncRef: (object: JSObject, body: ([JSValue]) -> JSValue)] = [:] + private var hostFuncRef: JavaScriptHostFuncRef = 0 + #if JAVASCRIPTKIT_WITHOUT_WEAKREFS + private var isReleased: Bool = false + #endif + @available(*, deprecated, message: "This initializer will be removed in the next minor version update. Please use `init(_ body: @escaping ([JSValue]) -> JSValue)` and add `return .undefined` to the end of your closure") @_disfavoredOverload public convenience init(_ body: @escaping ([JSValue]) -> ()) { @@ -40,26 +76,27 @@ public class JSClosure: JSObject, JSClosureProtocol { } public init(_ body: @escaping ([JSValue]) -> JSValue) { - self.hostFuncRef = closureRef - closureRef += 1 - - // Retain the given body in static storage by `closureRef`. - sharedClosures[self.hostFuncRef] = body - - // Create a new JavaScript function which calls the given Swift function. + // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. + super.init(id: 0) + let objectId = ObjectIdentifier(self) + let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue)) + // 2. Retain the given body in static storage by `funcRef`. + Self.sharedClosures[funcRef] = (self, body) + // 3. Create a new JavaScript function which calls the given Swift function. var objectRef: JavaScriptObjectRef = 0 - _create_function(self.hostFuncRef, &objectRef) + _create_function(funcRef, &objectRef) - super.init(id: objectRef) + hostFuncRef = funcRef + id = objectRef } - @available(*, deprecated, message: "JSClosure.release() is no longer necessary") - public func release() {} -} - -@_cdecl("_free_host_function_impl") -func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) { - sharedClosures[hostFuncRef] = nil + #if JAVASCRIPTKIT_WITHOUT_WEAKREFS + deinit { + guard isReleased else { + fatalError("release() must be called on JSClosure objects manually before they are deallocated") + } + } + #endif } @@ -103,7 +140,7 @@ func _call_host_function_impl( _ argv: UnsafePointer, _ argc: Int32, _ callbackFuncRef: JavaScriptObjectRef ) { - guard let hostFunc = sharedClosures[hostFuncRef] else { + guard let (_, hostFunc) = JSClosure.sharedClosures[hostFuncRef] else { fatalError("The function was already released") } let arguments = UnsafeBufferPointer(start: argv, count: Int(argc)).map { @@ -114,66 +151,35 @@ func _call_host_function_impl( _ = callbackFuncRef(result) } -// MARK: - Legacy Closure Types -/// `JSOneshotClosure` is a JavaScript function that can be called only once. -/// It is recommended to use `JSClosure` instead if your target runtimes support `FinalizationRegistry`. -public class JSOneshotClosure: JSObject, JSClosureProtocol { - private var hostFuncRef: JavaScriptHostFuncRef = 0 - - public init(_ body: @escaping ([JSValue]) -> JSValue) { - // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. - super.init(id: 0) - let objectId = ObjectIdentifier(self) - let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue)) - // 2. Retain the given body in static storage by `funcRef`. - sharedClosures[funcRef] = { - defer { self.release() } - return body($0) - } - // 3. Create a new JavaScript function which calls the given Swift function. - var objectRef: JavaScriptObjectRef = 0 - _create_function(funcRef, &objectRef) +/// [WeakRefs](https://github.com/tc39/proposal-weakrefs) are already Stage 4, +/// but was added recently enough that older browser versions don’t support it. +/// Build with `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` to disable the relevant behavior. +#if JAVASCRIPTKIT_WITHOUT_WEAKREFS - hostFuncRef = funcRef - id = objectRef - } +// MARK: - Legacy Closure Types - /// Release this function resource. - /// After calling `release`, calling this function from JavaScript will fail. +extension JSClosure { public func release() { - sharedClosures[hostFuncRef] = nil + isReleased = true + Self.sharedClosures[hostFuncRef] = nil } } -public class JSUnretainedClosure: JSObject, JSClosureProtocol { - private var hostFuncRef: JavaScriptHostFuncRef = 0 - var isReleased: Bool = false +@_cdecl("_free_host_function_impl") +func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) {} - public init(_ body: @escaping ([JSValue]) -> JSValue) { - // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. - super.init(id: 0) - let objectId = ObjectIdentifier(self) - let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue)) - // 2. Retain the given body in static storage by `funcRef`. - sharedClosures[funcRef] = body - // 3. Create a new JavaScript function which calls the given Swift function. - var objectRef: JavaScriptObjectRef = 0 - _create_function(funcRef, &objectRef) +#else - hostFuncRef = funcRef - id = objectRef - } +extension JSClosure { - public func release() { - isReleased = true - sharedClosures[hostFuncRef] = nil - } + @available(*, deprecated, message: "JSClosure.release() is no longer necessary") + public func release() {} - deinit { - guard isReleased else { - // Safari doesn't support `FinalizationRegistry`, so we cannot automatically manage the lifetime of Swift objects - fatalError("release() must be called on JSClosure objects manually before they are deallocated") - } - } } + +@_cdecl("_free_host_function_impl") +func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) { + JSClosure.sharedClosures[hostFuncRef] = nil +} +#endif diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c index 8834cd214..98f444fea 100644 --- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c +++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c @@ -35,8 +35,15 @@ void swjs_cleanup_host_function_call(void *argv_buffer) { /// 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() { - return 701; +int swjs_library_version(void) { + return 702; +} + +int _library_features(void); + +__attribute__((export_name("swjs_library_features"))) +int swjs_library_features(void) { + return _library_features(); } #endif