Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion for #128: Add escape hatch for old environments #139

Merged
merged 5 commits into from
Sep 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion IntegrationTests/Makefile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
36 changes: 32 additions & 4 deletions IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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") {
Expand Down Expand Up @@ -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])
Expand All @@ -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") {
Expand Down Expand Up @@ -380,21 +398,31 @@ 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") {
let oid1 = closureScope()
let oid2 = closureScope()
try expectEqual(oid1, oid2)
}
#endif

func checkArray<T>(_ array: [T]) throws where T: TypedArrayElement {
try expectEqual(toString(JSTypedArray(array).jsValue().object!), jsStringify(array))
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
59 changes: 46 additions & 13 deletions Runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -44,6 +45,10 @@ enum JavaScriptValueKind {
Function = 6,
}

enum LibraryFeatures {
WeakRefs = 1 << 0,
}

type TypedArray =
| Int8ArrayConstructor
| Uint8ArrayConstructor
Expand Down Expand Up @@ -117,25 +122,31 @@ class SwiftRuntimeHeap {
}
}

/// Memory lifetime of closures in Swift are managed by Swift side
class SwiftClosureHeap {
private functionRegistry: FinalizationRegistry<number>;

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<unknown>;
private version: number = 701;
private _closureHeap: SwiftClosureHeap | null;
j-f1 marked this conversation as resolved.
Show resolved Hide resolved
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) {
Expand All @@ -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 = () => {
Expand Down Expand Up @@ -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: (
Expand Down
12 changes: 12 additions & 0 deletions Sources/JavaScriptKit/Features.swift
Original file line number Diff line number Diff line change
@@ -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
}
Loading