Skip to content

Commit 1f3c700

Browse files
Add escape hatch for old environments
1 parent 43a33ee commit 1f3c700

File tree

5 files changed

+163
-88
lines changed

5 files changed

+163
-88
lines changed

Diff for: IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift

+32-4
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,14 +398,24 @@ 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

389412
func closureScope() -> ObjectIdentifier {
390-
ObjectIdentifier(JSClosure { _ in .undefined })
413+
let closure = JSClosure { _ in .undefined }
414+
let result = ObjectIdentifier(closure)
415+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
416+
closure.release()
417+
#endif
418+
return result
391419
}
392420

393421
try test("Closure Identifiers") {

Diff for: Runtime/src/index.ts

+34-12
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,33 @@ 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+
private exports: SwiftRuntimeExportedFunctions
129+
130+
constructor(exports: SwiftRuntimeExportedFunctions) {
131+
this.exports = exports
132+
this.functionRegistry = new FinalizationRegistry((id) => {
133+
this.exports.swjs_free_host_function(id);
134+
});
135+
}
136+
137+
alloc(func: any, func_ref: number) {
138+
this.functionRegistry.register(func, func_ref);
139+
}
140+
}
141+
120142
export class SwiftRuntime {
121143
private instance: WebAssembly.Instance | null;
122144
private heap: SwiftRuntimeHeap;
123-
private functionRegistry: FinalizationRegistry<unknown>;
145+
private closureHeap: SwiftClosureHeap | null;
124146
private version: number = 701;
125147

126148
constructor() {
127149
this.instance = null;
128150
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);
151+
this.closureHeap = null;
139152
}
140153

141154
setInstance(instance: WebAssembly.Instance) {
@@ -145,6 +158,15 @@ export class SwiftRuntime {
145158
if (exports.swjs_library_version() != this.version) {
146159
throw new Error("The versions of JavaScriptKit are incompatible.");
147160
}
161+
const features = 1 // exports.swjs_library_features();
162+
const librarySupportsWeakRef = (features & LibraryFeatures.WeakRefs) != 0;
163+
if (librarySupportsWeakRef) {
164+
if (typeof FinalizationRegistry !== "undefined") {
165+
this.closureHeap = new SwiftClosureHeap(exports);
166+
} else {
167+
throw new Error("The JavaScriptKit in Swift expects the target environment supports WeakRefs. Please build with `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` to disable features using WeakRefs.");
168+
}
169+
}
148170
}
149171

150172
importObjects() {
@@ -472,7 +494,7 @@ export class SwiftRuntime {
472494
);
473495
};
474496
const func_ref = this.heap.retain(func);
475-
this.functionRegistry.register(func, func_ref);
497+
this.closureHeap?.alloc(func, func_ref);
476498
writeUint32(func_ref_ptr, func_ref);
477499
},
478500
swjs_call_throwing_new: (

Diff for: Sources/JavaScriptKit/Features.swift

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
struct LibraryFeatures: OptionSet {
2+
let rawValue: Int32
3+
4+
static let weakRefs = LibraryFeatures(rawValue: 1 << 0)
5+
}
6+
7+
@_cdecl("_library_features")
8+
func _library_features() -> Int32 {
9+
var features: LibraryFeatures = []
10+
#if !JAVASCRIPTKIT_WITHOUT_WEAKREFS
11+
features.insert(.weakRefs)
12+
#endif
13+
return features.rawValue
14+
}

0 commit comments

Comments
 (0)