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/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 ea12262ef..6c054a791 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -197,28 +197,39 @@ 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 { arguments in - return arguments[0] - } + let c1 = JSClosure { _ in .undefined } +#if JAVASCRIPTKIT_WITHOUT_WEAKREFS c1.release() - // Call a released closure - try expectCrashByCall(ofClosure: c1) +#endif } do { - let c1 = JSClosure { _ in - // JSClosure will be deallocated before `release()` - _ = JSClosure { _ in .undefined } - return .undefined - } - try expectCrashByCall(ofClosure: c1) + let array = JSObject.global.Array.function!.new() + 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 { +// 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) +// } + +#if JAVASCRIPTKIT_WITHOUT_WEAKREFS do { let c1 = JSOneshotClosure { _ in return .boolean(true) @@ -228,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") { @@ -259,7 +271,9 @@ 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 { @@ -272,7 +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") { @@ -386,9 +403,13 @@ try test("ObjectRef Lifetime") { 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 { let closure = JSClosure { _ in .undefined } let result = ObjectIdentifier(closure) @@ -401,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)) @@ -519,7 +541,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) @@ -555,7 +577,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/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/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 144fed737..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( @@ -29,6 +30,8 @@ interface SwiftRuntimeExportedFunctions { argc: number, callback_func_ref: ref ): void; + + swjs_free_host_function(host_func_id: number): void; } enum JavaScriptValueKind { @@ -42,6 +45,10 @@ enum JavaScriptValueKind { Function = 6, } +enum LibraryFeatures { + WeakRefs = 1 << 0, +} + type TypedArray = | Int8ArrayConstructor | Uint8ArrayConstructor @@ -115,14 +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 version: number = 701; + private _closureHeap: SwiftClosureHeap | null; + private version: number = 702; constructor() { this.instance = null; this.heap = new SwiftRuntimeHeap(); + this._closureHeap = null; } setInstance(instance: WebAssembly.Instance) { @@ -133,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 = () => { @@ -452,12 +498,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.closureHeap?.alloc(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/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 cd96dd0b5..a8fcd01e9 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift @@ -1,8 +1,8 @@ import _CJavaScriptKit -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. @@ -10,6 +10,7 @@ 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 @@ -20,10 +21,10 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol { let objectId = ObjectIdentifier(self) let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue)) // 2. Retain the given body in static storage by `funcRef`. - sharedClosures[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) @@ -35,15 +36,12 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol { /// Release this function resource. /// After calling `release`, calling this function from JavaScript will fail. public func release() { - sharedClosures[hostFuncRef] = nil + 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. -/// 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 +53,18 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol { /// button.addEventListener!("click", JSValue.function(eventListenter)) /// ... /// button.removeEventListener!("click", JSValue.function(eventListenter)) -/// eventListenter.release() /// ``` /// 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 - var isReleased: Bool = false + + #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 @@ -77,7 +81,7 @@ public class JSClosure: JSObject, JSClosureProtocol { let objectId = ObjectIdentifier(self) let funcRef = JavaScriptHostFuncRef(bitPattern: Int32(objectId.hashValue)) // 2. Retain the given body in static storage by `funcRef`. - sharedClosures[funcRef] = body + Self.sharedClosures[funcRef] = (self, body) // 3. Create a new JavaScript function which calls the given Swift function. var objectRef: JavaScriptObjectRef = 0 _create_function(funcRef, &objectRef) @@ -86,19 +90,16 @@ public class JSClosure: JSObject, JSClosureProtocol { id = objectRef } - public func release() { - isReleased = true - sharedClosures[hostFuncRef] = nil - } - + #if JAVASCRIPTKIT_WITHOUT_WEAKREFS 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") } } + #endif } + // MARK: - `JSClosure` mechanism note // // 1. Create a thunk in the JavaScript world, which has a reference @@ -139,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 { @@ -149,3 +150,36 @@ func _call_host_function_impl( let callbackFuncRef = JSFunction(id: callbackFuncRef) _ = callbackFuncRef(result) } + + +/// [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 + +// MARK: - Legacy Closure Types + +extension JSClosure { + public func release() { + isReleased = true + Self.sharedClosures[hostFuncRef] = nil + } +} + +@_cdecl("_free_host_function_impl") +func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) {} + +#else + +extension JSClosure { + + @available(*, deprecated, message: "JSClosure.release() is no longer necessary") + public func release() {} + +} + +@_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 fd9e39cd8..98f444fea 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)); @@ -28,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 diff --git a/package-lock.json b/package-lock.json index b5891c5e1..1213bf3cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "prettier": "2.1.2", - "typescript": "^4.0.2" + "typescript": "^4.4.2" } }, "node_modules/prettier": { @@ -26,9 +26,9 @@ } }, "node_modules/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, "bin": { "tsc": "bin/tsc", @@ -47,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 a7b87797b..856474df7 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,6 @@ "license": "MIT", "devDependencies": { "prettier": "2.1.2", - "typescript": "^4.0.2" + "typescript": "^4.4.2" } }