Skip to content

Commit 033ff48

Browse files
Add escape hatch for old environments (#139)
1 parent 43a33ee commit 033ff48

File tree

7 files changed

+183
-93
lines changed

7 files changed

+183
-93
lines changed

IntegrationTests/Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
CONFIGURATION ?= debug
2+
SWIFT_BUILD_FLAGS ?=
23

34
FORCE:
45
TestSuites/.build/$(CONFIGURATION)/%.wasm: FORCE
56
swift build --package-path TestSuites \
67
--product $(basename $(notdir $@)) \
78
--triple wasm32-unknown-wasi \
8-
--configuration $(CONFIGURATION)
9+
--configuration $(CONFIGURATION) \
10+
$(SWIFT_BUILD_FLAGS)
911

1012
dist/%.wasm: TestSuites/.build/$(CONFIGURATION)/%.wasm
1113
mkdir -p dist

IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,18 +197,26 @@ try test("Closure Lifetime") {
197197
return arguments[0]
198198
}
199199
try expectEqual(evalClosure(c1, JSValue.number(1.0)), .number(1.0))
200+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
201+
c1.release()
202+
#endif
200203
}
201204

202205
do {
203206
let c1 = JSClosure { _ in .undefined }
207+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
204208
c1.release()
205-
c1.release()
209+
#endif
206210
}
207211

208212
do {
209213
let array = JSObject.global.Array.function!.new()
210-
_ = array.push!(JSClosure { _ in .number(3) })
214+
let c1 = JSClosure { _ in .number(3) }
215+
_ = array.push!(c1)
211216
try expectEqual(array[0].function!().number, 3.0)
217+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
218+
c1.release()
219+
#endif
212220
}
213221

214222
// do {
@@ -221,6 +229,7 @@ try test("Closure Lifetime") {
221229
// try expectEqual(weakRef.deref!(), .undefined)
222230
// }
223231

232+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
224233
do {
225234
let c1 = JSOneshotClosure { _ in
226235
return .boolean(true)
@@ -230,6 +239,7 @@ try test("Closure Lifetime") {
230239
try expectCrashByCall(ofClosure: c1)
231240
// OneshotClosure won't call fatalError even if it's deallocated before `release`
232241
}
242+
#endif
233243
}
234244

235245
try test("Host Function Registration") {
@@ -261,6 +271,10 @@ try test("Host Function Registration") {
261271
try expectEqual(call_host_1Func(), .number(1))
262272
try expectEqual(isHostFunc1Called, true)
263273

274+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
275+
hostFunc1.release()
276+
#endif
277+
264278
let hostFunc2 = JSClosure { (arguments) -> JSValue in
265279
do {
266280
let input = try expectNumber(arguments[0])
@@ -272,6 +286,10 @@ try test("Host Function Registration") {
272286

273287
try expectEqual(evalClosure(hostFunc2, 3), .number(6))
274288
_ = try expectString(evalClosure(hostFunc2, true))
289+
290+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
291+
hostFunc2.release()
292+
#endif
275293
}
276294

277295
try test("New Object Construction") {
@@ -380,21 +398,31 @@ try test("ObjectRef Lifetime") {
380398
// }
381399
// ```
382400

401+
let identity = JSClosure { $0[0] }
383402
let ref1 = getJSValue(this: .global, name: "globalObject1").object!
384-
let ref2 = evalClosure(JSClosure { $0[0] }, ref1).object!
403+
let ref2 = evalClosure(identity, ref1).object!
385404
try expectEqual(ref1.prop_2, .number(2))
386405
try expectEqual(ref2.prop_2, .number(2))
406+
407+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
408+
identity.release()
409+
#endif
387410
}
388411

412+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
389413
func closureScope() -> ObjectIdentifier {
390-
ObjectIdentifier(JSClosure { _ in .undefined })
414+
let closure = JSClosure { _ in .undefined }
415+
let result = ObjectIdentifier(closure)
416+
closure.release()
417+
return result
391418
}
392419

393420
try test("Closure Identifiers") {
394421
let oid1 = closureScope()
395422
let oid2 = closureScope()
396423
try expectEqual(oid1, oid2)
397424
}
425+
#endif
398426

399427
func checkArray<T>(_ array: [T]) throws where T: TypedArrayElement {
400428
try expectEqual(toString(JSTypedArray(array).jsValue().object!), jsStringify(array))

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ build:
1414
test:
1515
cd IntegrationTests && \
1616
CONFIGURATION=debug make test && \
17-
CONFIGURATION=release make test
17+
CONFIGURATION=debug SWIFT_BUILD_FLAGS="-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS" make test && \
18+
CONFIGURATION=release make test && \
19+
CONFIGURATION=release SWIFT_BUILD_FLAGS="-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS" make test
1820

1921
.PHONY: benchmark_setup
2022
benchmark_setup:

Runtime/src/index.ts

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ if (typeof globalThis !== "undefined") {
2121

2222
interface SwiftRuntimeExportedFunctions {
2323
swjs_library_version(): number;
24+
swjs_library_features(): number;
2425
swjs_prepare_host_function_call(size: number): pointer;
2526
swjs_cleanup_host_function_call(argv: pointer): void;
2627
swjs_call_host_function(
@@ -44,6 +45,10 @@ enum JavaScriptValueKind {
4445
Function = 6,
4546
}
4647

48+
enum LibraryFeatures {
49+
WeakRefs = 1 << 0,
50+
}
51+
4752
type TypedArray =
4853
| Int8ArrayConstructor
4954
| Uint8ArrayConstructor
@@ -117,25 +122,31 @@ class SwiftRuntimeHeap {
117122
}
118123
}
119124

125+
/// Memory lifetime of closures in Swift are managed by Swift side
126+
class SwiftClosureHeap {
127+
private functionRegistry: FinalizationRegistry<number>;
128+
129+
constructor(exports: SwiftRuntimeExportedFunctions) {
130+
this.functionRegistry = new FinalizationRegistry((id) => {
131+
exports.swjs_free_host_function(id);
132+
});
133+
}
134+
135+
alloc(func: any, func_ref: number) {
136+
this.functionRegistry.register(func, func_ref);
137+
}
138+
}
139+
120140
export class SwiftRuntime {
121141
private instance: WebAssembly.Instance | null;
122142
private heap: SwiftRuntimeHeap;
123-
private functionRegistry: FinalizationRegistry<unknown>;
124-
private version: number = 701;
143+
private _closureHeap: SwiftClosureHeap | null;
144+
private version: number = 702;
125145

126146
constructor() {
127147
this.instance = null;
128148
this.heap = new SwiftRuntimeHeap();
129-
this.functionRegistry = new FinalizationRegistry(
130-
this.handleFree.bind(this)
131-
);
132-
}
133-
134-
handleFree(id: unknown) {
135-
if (!this.instance || typeof id !== "number") return;
136-
const exports = (this.instance
137-
.exports as any) as SwiftRuntimeExportedFunctions;
138-
exports.swjs_free_host_function(id);
149+
this._closureHeap = null;
139150
}
140151

141152
setInstance(instance: WebAssembly.Instance) {
@@ -146,6 +157,28 @@ export class SwiftRuntime {
146157
throw new Error("The versions of JavaScriptKit are incompatible.");
147158
}
148159
}
160+
get closureHeap(): SwiftClosureHeap | null {
161+
if (this._closureHeap) return this._closureHeap;
162+
if (!this.instance)
163+
throw new Error("WebAssembly instance is not set yet");
164+
165+
const exports = (this.instance
166+
.exports as any) as SwiftRuntimeExportedFunctions;
167+
const features = exports.swjs_library_features();
168+
const librarySupportsWeakRef =
169+
(features & LibraryFeatures.WeakRefs) != 0;
170+
if (librarySupportsWeakRef) {
171+
if (typeof FinalizationRegistry !== "undefined") {
172+
this._closureHeap = new SwiftClosureHeap(exports);
173+
return this._closureHeap;
174+
} else {
175+
throw new Error(
176+
"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."
177+
);
178+
}
179+
}
180+
return null;
181+
}
149182

150183
importObjects() {
151184
const memory = () => {
@@ -472,7 +505,7 @@ export class SwiftRuntime {
472505
);
473506
};
474507
const func_ref = this.heap.retain(func);
475-
this.functionRegistry.register(func, func_ref);
508+
this.closureHeap?.alloc(func, func_ref);
476509
writeUint32(func_ref_ptr, func_ref);
477510
},
478511
swjs_call_throwing_new: (

Sources/JavaScriptKit/Features.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
enum LibraryFeatures {
2+
static let weakRefs: Int32 = 1 << 0
3+
}
4+
5+
@_cdecl("_library_features")
6+
func _library_features() -> Int32 {
7+
var features: Int32 = 0
8+
#if !JAVASCRIPTKIT_WITHOUT_WEAKREFS
9+
features |= LibraryFeatures.weakRefs
10+
#endif
11+
return features
12+
}

0 commit comments

Comments
 (0)