Skip to content

Commit 8791f65

Browse files
Manage JS environment heap allocation
1 parent 53c25ed commit 8791f65

File tree

7 files changed

+89
-52
lines changed

7 files changed

+89
-52
lines changed

Diff for: src/swift/Sources/JavaScriptKit/JSObject.swift

+2-6
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,9 @@ public class JSObjectRef: Equatable {
4242
setJSValue(this: self, index: Int32(index), value: value)
4343
}
4444

45-
public static func global() -> JSObjectRef {
46-
.init(id: _JS_Predef_Value_Global)
47-
}
48-
49-
deinit {
45+
public static let global = JSObjectRef(id: _JS_Predef_Value_Global)
5046

51-
}
47+
deinit { _destroy_ref(id) }
5248

5349
public static func == (lhs: JSObjectRef, rhs: JSObjectRef) -> Bool {
5450
return lhs.id == rhs.id

Diff for: src/swift/Sources/JavaScriptKit/JSValueConvertible.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ extension JSFunctionRef: JSValueConvertible {
2828
public func jsValue() -> JSValue { .function(self) }
2929
}
3030

31-
private let Object = JSObjectRef.global().Object.function!
31+
private let Object = JSObjectRef.global.Object.function!
3232

3333
extension Dictionary where Value: JSValueConvertible, Key == String {
3434
public func jsValue() -> JSValue {
@@ -46,7 +46,7 @@ extension Dictionary: JSValueConvertible where Value == JSValueConvertible, Key
4646
}
4747
}
4848

49-
private let Array = JSObjectRef.global().Array.function!
49+
private let Array = JSObjectRef.global.Array.function!
5050

5151
extension Array where Element: JSValueConvertible {
5252
public func jsValue() -> JSValue {

Diff for: src/swift/Sources/JavaScriptKit/XcodeSupport.swift

+1
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,5 @@ func _call_new(
5353
func _create_function(
5454
_ host_func_id: JavaScriptHostFuncRef,
5555
_ func_ref_ptr: UnsafePointer<JavaScriptObjectRef>!) { fatalError() }
56+
func _destroy_ref(_ ref: JavaScriptObjectRef) { fatalError() }
5657
#endif

Diff for: src/swift/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h

+8
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,12 @@ extern void _create_function(
129129
const JavaScriptObjectRef *func_ref_ptr
130130
);
131131

132+
__attribute__((
133+
__import_module__("javascript_kit"),
134+
__import_name__("swjs_destroy_ref")
135+
))
136+
extern void _destroy_ref(
137+
const JavaScriptObjectRef ref
138+
);
139+
132140
#endif /* _CJavaScriptKit_h */

Diff for: src/web/src/index.ts

+48-25
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ enum JavaScriptValueKind {
3131

3232
export class SwiftRuntime {
3333
private instance: WebAssembly.Instance | null;
34-
private _heapValues: any[]
34+
private _heapValues: Map<number, any>;
35+
private _heapNextKey: number;
3536

3637
constructor() {
3738
this.instance = null;
@@ -41,9 +42,10 @@ export class SwiftRuntime {
4142
} else if (typeof global !== "undefined") {
4243
_global = global
4344
}
44-
this._heapValues = [
45-
_global,
46-
]
45+
this._heapValues = new Map();
46+
this._heapValues.set(0, _global);
47+
// Note: 0 is preserved for global
48+
this._heapNextKey = 1;
4749
}
4850

4951
setInsance(instance: WebAssembly.Instance) {
@@ -57,13 +59,31 @@ export class SwiftRuntime {
5759
throw new Error("WebAssembly instance is not set yet");
5860
}
5961

60-
const allocValue = (value: any) => {
61-
// TODO
62-
const id = this._heapValues.length
63-
this._heapValues.push(value)
62+
const allocHeap = (value: any) => {
63+
const isObject = typeof value == "object"
64+
if (isObject && value.swjs_heap_id) {
65+
return value.swjs_heap_id
66+
}
67+
const id = this._heapNextKey++;
68+
this._heapValues.set(id, value)
69+
if (isObject)
70+
Reflect.set(value, "swjs_heap_id", id);
6471
return id
6572
}
6673

74+
const freeHeap = (ref: ref) => {
75+
const value = this._heapValues.get(ref);
76+
const isObject = typeof value == "object"
77+
if (isObject && value.swjs_heap_id) {
78+
delete value.swjs_heap_id;
79+
}
80+
this._heapValues.delete(ref)
81+
}
82+
83+
const referenceHeap = (ref: ref) => {
84+
return this._heapValues.get(ref)
85+
}
86+
6787
const callHostFunction = (host_func_id: number, args: any[]) => {
6888
if (!this.instance)
6989
throw new Error("WebAssembly instance is not set yet");
@@ -79,7 +99,7 @@ export class SwiftRuntime {
7999
writeUint32(base + 8, value.payload2)
80100
}
81101
let output: any;
82-
const callback_func_ref = allocValue(function(result: any) {
102+
const callback_func_ref = allocHeap(function(result: any) {
83103
output = result
84104
})
85105
exports.swjs_call_host_function(host_func_id, argv, argc, callback_func_ref)
@@ -137,7 +157,7 @@ export class SwiftRuntime {
137157
return readString(payload1, payload2)
138158
}
139159
case JavaScriptValueKind.Object: {
140-
return this._heapValues[payload1]
160+
return referenceHeap(payload1)
141161
}
142162
case JavaScriptValueKind.Null: {
143163
return null
@@ -146,7 +166,7 @@ export class SwiftRuntime {
146166
return undefined
147167
}
148168
case JavaScriptValueKind.Function: {
149-
return this._heapValues[payload1]
169+
return referenceHeap(payload1)
150170
}
151171
default:
152172
throw new Error(`Type kind "${kind}" is not supported`)
@@ -179,7 +199,7 @@ export class SwiftRuntime {
179199
case "string": {
180200
return {
181201
kind: JavaScriptValueKind.String,
182-
payload1: allocValue(value),
202+
payload1: allocHeap(value),
183203
payload2: value.length,
184204
}
185205
}
@@ -193,14 +213,14 @@ export class SwiftRuntime {
193213
case "object": {
194214
return {
195215
kind: JavaScriptValueKind.Object,
196-
payload1: allocValue(value),
216+
payload1: allocHeap(value),
197217
payload2: 0,
198218
}
199219
}
200220
case "function": {
201221
return {
202222
kind: JavaScriptValueKind.Function,
203-
payload1: allocValue(value),
223+
payload1: allocHeap(value),
204224
payload2: 0,
205225
}
206226
}
@@ -230,15 +250,15 @@ export class SwiftRuntime {
230250
kind: JavaScriptValueKind,
231251
payload1: number, payload2: number
232252
) => {
233-
const obj = this._heapValues[ref];
253+
const obj = referenceHeap(ref);
234254
Reflect.set(obj, readString(name, length), decodeValue(kind, payload1, payload2))
235255
},
236256
swjs_get_prop: (
237257
ref: ref, name: pointer, length: number,
238258
kind_ptr: pointer,
239259
payload1_ptr: pointer, payload2_ptr: pointer
240260
) => {
241-
const obj = this._heapValues[ref];
261+
const obj = referenceHeap(ref);
242262
const result = Reflect.get(obj, readString(name, length));
243263
const { kind, payload1, payload2 } = encodeValue(result);
244264
writeUint32(kind_ptr, kind);
@@ -250,31 +270,31 @@ export class SwiftRuntime {
250270
kind: JavaScriptValueKind,
251271
payload1: number, payload2: number
252272
) => {
253-
const obj = this._heapValues[ref];
273+
const obj = referenceHeap(ref);
254274
Reflect.set(obj, index, decodeValue(kind, payload1, payload2))
255275
},
256276
swjs_get_subscript: (
257277
ref: ref, index: number,
258278
kind_ptr: pointer,
259279
payload1_ptr: pointer, payload2_ptr: pointer
260280
) => {
261-
const obj = this._heapValues[ref];
281+
const obj = referenceHeap(ref);
262282
const result = Reflect.get(obj, index);
263283
const { kind, payload1, payload2 } = encodeValue(result);
264284
writeUint32(kind_ptr, kind);
265285
writeUint32(payload1_ptr, payload1);
266286
writeUint32(payload2_ptr, payload2);
267287
},
268288
swjs_load_string: (ref: ref, buffer: pointer) => {
269-
const string = this._heapValues[ref];
289+
const string = referenceHeap(ref);
270290
writeString(buffer, string);
271291
},
272292
swjs_call_function: (
273293
ref: ref, argv: pointer, argc: number,
274294
kind_ptr: pointer,
275295
payload1_ptr: pointer, payload2_ptr: pointer
276296
) => {
277-
const func = this._heapValues[ref]
297+
const func = referenceHeap(ref)
278298
const result = Reflect.apply(func, undefined, decodeValues(argv, argc))
279299
const { kind, payload1, payload2 } = encodeValue(result);
280300
writeUint32(kind_ptr, kind);
@@ -287,8 +307,8 @@ export class SwiftRuntime {
287307
kind_ptr: pointer,
288308
payload1_ptr: pointer, payload2_ptr: pointer
289309
) => {
290-
const obj = this._heapValues[obj_ref]
291-
const func = this._heapValues[func_ref]
310+
const obj = referenceHeap(obj_ref)
311+
const func = referenceHeap(func_ref)
292312
const result = Reflect.apply(func, obj, decodeValues(argv, argc))
293313
const { kind, payload1, payload2 } = encodeValue(result);
294314
writeUint32(kind_ptr, kind);
@@ -299,7 +319,7 @@ export class SwiftRuntime {
299319
host_func_id: number,
300320
func_ref_ptr: pointer,
301321
) => {
302-
const func_ref = allocValue(function() {
322+
const func_ref = allocHeap(function() {
303323
return callHostFunction(host_func_id, Array.prototype.slice.call(arguments))
304324
})
305325
writeUint32(func_ref_ptr, func_ref)
@@ -308,12 +328,15 @@ export class SwiftRuntime {
308328
ref: ref, argv: pointer, argc: number,
309329
result_obj: pointer
310330
) => {
311-
const obj = this._heapValues[ref]
331+
const obj = referenceHeap(ref)
312332
const result = Reflect.construct(obj, decodeValues(argv, argc))
313333
if (typeof result != "object")
314334
throw Error(`Invalid result type of object constructor of "${obj}": "${result}"`)
315-
writeUint32(result_obj, allocValue(result));
335+
writeUint32(result_obj, allocHeap(result));
316336
},
337+
swjs_destroy_ref: (ref: ref) => {
338+
freeHeap(ref)
339+
}
317340
}
318341
}
319342
}

Diff for: test/JavaScriptKitExec/Sources/JavaScriptKitExec/UnitTestUtils.swift

+22-13
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,62 @@ import JavaScriptKit
22

33
struct MessageError: Error {
44
let message: String
5-
init(_ message: String) {
5+
let file: StaticString
6+
let line: UInt
7+
let column: UInt
8+
init(_ message: String, file: StaticString, line: UInt, column: UInt) {
69
self.message = message
10+
self.file = file
11+
self.line = line
12+
self.column = column
713
}
814
}
915

10-
func expectEqual<T: Equatable>(_ lhs: T, _ rhs: T) throws {
16+
func expectEqual<T: Equatable>(
17+
_ lhs: T, _ rhs: T,
18+
file: StaticString = #file, line: UInt = #line, column: UInt = #column
19+
) throws {
1120
if lhs != rhs {
12-
throw MessageError("Expect to be equal \"\(lhs)\" and \"\(rhs)\"")
21+
throw MessageError("Expect to be equal \"\(lhs)\" and \"\(rhs)\"", file: file, line: line, column: column)
1322
}
1423
}
1524

16-
func expectObject(_ value: JSValue) throws -> JSObjectRef {
25+
func expectObject(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSObjectRef {
1726
switch value {
1827
case .object(let ref): return ref
1928
default:
20-
throw MessageError("Type of \(value) should be \"object\"")
29+
throw MessageError("Type of \(value) should be \"object\"", file: file, line: line, column: column)
2130
}
2231
}
2332

24-
func expectFunction(_ value: JSValue) throws -> JSFunctionRef {
33+
func expectFunction(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSFunctionRef {
2534
switch value {
2635
case .function(let ref): return ref
2736
default:
28-
throw MessageError("Type of \(value) should be \"function\"")
37+
throw MessageError("Type of \(value) should be \"function\"", file: file, line: line, column: column)
2938
}
3039
}
3140

32-
func expectBoolean(_ value: JSValue) throws -> Bool {
41+
func expectBoolean(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Bool {
3342
switch value {
3443
case .boolean(let bool): return bool
3544
default:
36-
throw MessageError("Type of \(value) should be \"boolean\"")
45+
throw MessageError("Type of \(value) should be \"boolean\"", file: file, line: line, column: column)
3746
}
3847
}
3948

40-
func expectNumber(_ value: JSValue) throws -> Int32 {
49+
func expectNumber(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Int32 {
4150
switch value {
4251
case .number(let number): return number
4352
default:
44-
throw MessageError("Type of \(value) should be \"number\"")
53+
throw MessageError("Type of \(value) should be \"number\"", file: file, line: line, column: column)
4554
}
4655
}
4756

48-
func expectString(_ value: JSValue) throws -> String {
57+
func expectString(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> String {
4958
switch value {
5059
case .string(let string): return string
5160
default:
52-
throw MessageError("Type of \(value) should be \"string\"")
61+
throw MessageError("Type of \(value) should be \"string\"", file: file, line: line, column: column)
5362
}
5463
}

Diff for: test/JavaScriptKitExec/Sources/JavaScriptKitExec/main.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import JavaScriptKit
22

33
Literal_Conversion: do {
4-
let global = JSObjectRef.global()
4+
let global = JSObjectRef.global
55
let inputs: [JSValue] = [
66
.boolean(true),
77
.boolean(false),
@@ -40,7 +40,7 @@ Object_Conversion: do {
4040
// ```
4141
//
4242

43-
let globalObject1 = getJSValue(this: .global(), name: "globalObject1")
43+
let globalObject1 = getJSValue(this: .global, name: "globalObject1")
4444
let globalObject1Ref = try expectObject(globalObject1)
4545
let prop_1 = getJSValue(this: globalObject1Ref, name: "prop_1")
4646
let prop_1Ref = try expectObject(prop_1)
@@ -88,7 +88,7 @@ Function_Call: do {
8888
//
8989

9090
// Notes: If the size of `RawJSValue` is updated, these test suites will fail.
91-
let globalObject1 = getJSValue(this: .global(), name: "globalObject1")
91+
let globalObject1 = getJSValue(this: .global, name: "globalObject1")
9292
let globalObject1Ref = try expectObject(globalObject1)
9393
let prop_5 = getJSValue(this: globalObject1Ref, name: "prop_5")
9494
let prop_5Ref = try expectObject(prop_5)
@@ -125,7 +125,7 @@ Host_Function_Registration: do {
125125
// }
126126
// }
127127
// ```
128-
let globalObject1 = getJSValue(this: .global(), name: "globalObject1")
128+
let globalObject1 = getJSValue(this: .global, name: "globalObject1")
129129
let globalObject1Ref = try expectObject(globalObject1)
130130
let prop_6 = getJSValue(this: globalObject1Ref, name: "prop_6")
131131
let prop_6Ref = try expectObject(prop_6)
@@ -169,7 +169,7 @@ New_Object_Construction: do {
169169
// }
170170
// }
171171
// ```
172-
let objectConstructor = try expectFunction(getJSValue(this: .global(), name: "Animal"))
172+
let objectConstructor = try expectFunction(getJSValue(this: .global, name: "Animal"))
173173
let cat1 = objectConstructor.new("Tama", 3, true)
174174
try expectEqual(getJSValue(this: cat1, name: "name"), .string("Tama"))
175175
try expectEqual(getJSValue(this: cat1, name: "age"), .number(3))
@@ -198,7 +198,7 @@ Call_Function_With_This: do {
198198
// }
199199
// }
200200
// ```
201-
let objectConstructor = try expectFunction(getJSValue(this: .global(), name: "Animal"))
201+
let objectConstructor = try expectFunction(getJSValue(this: .global, name: "Animal"))
202202
let cat1 = objectConstructor.new("Tama", 3, true)
203203
let getIsCat = try expectFunction(getJSValue(this: cat1, name: "getIsCat"))
204204

0 commit comments

Comments
 (0)