Skip to content

Commit dfca76f

Browse files
committed
BridgeJS: Use direct string retain + encodeInto() for stack ABI paths
For arrays, struct fields, enum payloads, and dictionary entries, the stack ABI now retains the JS string directly (instead of encoding to a Uint8Array first) and passes string.length * 3 as worst-case buffer capacity. A new dedicated swift_js_init_memory_from_string import handles the string path - it encodes UTF-8 directly into the WASM buffer via encodeInto() and returns the actual byte count written. This avoids a runtime typeof check in the existing swift_js_init_memory, which remains unchanged (Uint8Array-only, Void return). This eliminates the intermediate Uint8Array allocation for every string element in arrays and struct fields.
1 parent acf7d9e commit dfca76f

54 files changed

Lines changed: 500 additions & 141 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,26 @@ public struct BridgeJSLink {
397397
"const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, bytesPtr);"
398398
)
399399
printer.write("bytes.set(source);")
400+
printer.write("return source.length;")
401+
}
402+
printer.write("}")
403+
// Dedicated string-to-WASM-memory path used by the stack ABI.
404+
// Encodes UTF-8 directly into the target buffer - no intermediate
405+
// Uint8Array allocation. Returns actual byte count written.
406+
printer.write("bjs[\"swift_js_init_memory_from_string\"] = function(stringId, bytesPtr) {")
407+
printer.indent {
408+
printer.write(
409+
"const str = \(JSGlueVariableScope.reservedSwift).\(JSGlueVariableScope.reservedMemory).getObject(stringId);"
410+
)
411+
printer.write(
412+
"\(JSGlueVariableScope.reservedSwift).\(JSGlueVariableScope.reservedMemory).release(stringId);"
413+
)
414+
printer.write(
415+
"const target = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, bytesPtr);"
416+
)
417+
printer.write(
418+
"return \(JSGlueVariableScope.reservedTextEncoder).encodeInto(str, target).written;"
419+
)
400420
}
401421
printer.write("}")
402422
printer.write("bjs[\"swift_js_make_js_string\"] = function(ptr, len) {")

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2122,15 +2122,14 @@ struct IntrinsicJSFragment: Sendable {
21222122
printCode: { arguments, context in
21232123
let (scope, printer) = (context.scope, context.printer)
21242124
let value = arguments[0]
2125-
let bytesVar = scope.variable("bytes")
21262125
let idVar = scope.variable("id")
2126+
// Retain the JS string directly - no textEncoder.encode() allocation.
2127+
// _swift_js_init_memory detects the string type and uses encodeInto()
2128+
// to write UTF-8 directly into WASM memory.
21272129
printer.write(
2128-
"const \(bytesVar) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(value));"
2129-
)
2130-
printer.write(
2131-
"const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));"
2130+
"const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));"
21322131
)
2133-
scope.emitPushI32Parameter("\(bytesVar).length", printer: printer)
2132+
scope.emitPushI32Parameter("\(value).length * 3", printer: printer)
21342133
scope.emitPushI32Parameter(idVar, printer: printer)
21352134
return []
21362135
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ export async function createInstantiator(options, swift) {
7474
swift.memory.release(sourceId);
7575
const bytes = new Uint8Array(memory.buffer, bytesPtr);
7676
bytes.set(source);
77+
return source.length;
78+
}
79+
bjs["swift_js_init_memory_from_string"] = function(stringId, bytesPtr) {
80+
const str = swift.memory.getObject(stringId);
81+
swift.memory.release(stringId);
82+
const target = new Uint8Array(memory.buffer, bytesPtr);
83+
return textEncoder.encodeInto(str, target).written;
7784
}
7885
bjs["swift_js_make_js_string"] = function(ptr, len) {
7986
return swift.memory.retain(decodeString(ptr, len));
@@ -304,9 +311,8 @@ export async function createInstantiator(options, swift) {
304311
arrayResult.reverse();
305312
let ret = imports.importProcessStrings(arrayResult);
306313
for (const elem of ret) {
307-
const bytes = textEncoder.encode(elem);
308-
const id = swift.memory.retain(bytes);
309-
i32Stack.push(bytes.length);
314+
const id = swift.memory.retain(elem);
315+
i32Stack.push(elem.length * 3);
310316
i32Stack.push(id);
311317
}
312318
i32Stack.push(ret.length);
@@ -414,9 +420,8 @@ export async function createInstantiator(options, swift) {
414420
}
415421
i32Stack.push(nums.length);
416422
for (const elem1 of strs) {
417-
const bytes = textEncoder.encode(elem1);
418-
const id = swift.memory.retain(bytes);
419-
i32Stack.push(bytes.length);
423+
const id = swift.memory.retain(elem1);
424+
i32Stack.push(elem1.length * 3);
420425
i32Stack.push(id);
421426
}
422427
i32Stack.push(strs.length);
@@ -469,9 +474,8 @@ export async function createInstantiator(options, swift) {
469474
},
470475
processStringArray: function bjs_processStringArray(values) {
471476
for (const elem of values) {
472-
const bytes = textEncoder.encode(elem);
473-
const id = swift.memory.retain(bytes);
474-
i32Stack.push(bytes.length);
477+
const id = swift.memory.retain(elem);
478+
i32Stack.push(elem.length * 3);
475479
i32Stack.push(id);
476480
}
477481
i32Stack.push(values.length);
@@ -654,9 +658,8 @@ export async function createInstantiator(options, swift) {
654658
for (const elem of values) {
655659
const isSome = elem != null ? 1 : 0;
656660
if (isSome) {
657-
const bytes = textEncoder.encode(elem);
658-
const id = swift.memory.retain(bytes);
659-
i32Stack.push(bytes.length);
661+
const id = swift.memory.retain(elem);
662+
i32Stack.push(elem.length * 3);
660663
i32Stack.push(id);
661664
}
662665
i32Stack.push(isSome);
@@ -810,9 +813,8 @@ export async function createInstantiator(options, swift) {
810813
processNestedStringArray: function bjs_processNestedStringArray(values) {
811814
for (const elem of values) {
812815
for (const elem1 of elem) {
813-
const bytes = textEncoder.encode(elem1);
814-
const id = swift.memory.retain(bytes);
815-
i32Stack.push(bytes.length);
816+
const id = swift.memory.retain(elem1);
817+
i32Stack.push(elem1.length * 3);
816818
i32Stack.push(id);
817819
}
818820
i32Stack.push(elem.length);
@@ -979,9 +981,8 @@ export async function createInstantiator(options, swift) {
979981
}
980982
i32Stack.push(nums.length);
981983
for (const elem1 of strs) {
982-
const bytes = textEncoder.encode(elem1);
983-
const id = swift.memory.retain(bytes);
984-
i32Stack.push(bytes.length);
984+
const id = swift.memory.retain(elem1);
985+
i32Stack.push(elem1.length * 3);
985986
i32Stack.push(id);
986987
}
987988
i32Stack.push(strs.length);
@@ -1000,9 +1001,8 @@ export async function createInstantiator(options, swift) {
10001001
const isSome1 = b != null;
10011002
if (isSome1) {
10021003
for (const elem1 of b) {
1003-
const bytes = textEncoder.encode(elem1);
1004-
const id = swift.memory.retain(bytes);
1005-
i32Stack.push(bytes.length);
1004+
const id = swift.memory.retain(elem1);
1005+
i32Stack.push(elem1.length * 3);
10061006
i32Stack.push(id);
10071007
}
10081008
i32Stack.push(b.length);

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ export async function createInstantiator(options, swift) {
4949
swift.memory.release(sourceId);
5050
const bytes = new Uint8Array(memory.buffer, bytesPtr);
5151
bytes.set(source);
52+
return source.length;
53+
}
54+
bjs["swift_js_init_memory_from_string"] = function(stringId, bytesPtr) {
55+
const str = swift.memory.getObject(stringId);
56+
swift.memory.release(stringId);
57+
const target = new Uint8Array(memory.buffer, bytesPtr);
58+
return textEncoder.encodeInto(str, target).written;
5259
}
5360
bjs["swift_js_make_js_string"] = function(ptr, len) {
5461
return swift.memory.retain(decodeString(ptr, len));

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ export async function createInstantiator(options, swift) {
164164
swift.memory.release(sourceId);
165165
const bytes = new Uint8Array(memory.buffer, bytesPtr);
166166
bytes.set(source);
167+
return source.length;
168+
}
169+
bjs["swift_js_init_memory_from_string"] = function(stringId, bytesPtr) {
170+
const str = swift.memory.getObject(stringId);
171+
swift.memory.release(stringId);
172+
const target = new Uint8Array(memory.buffer, bytesPtr);
173+
return textEncoder.encodeInto(str, target).written;
167174
}
168175
bjs["swift_js_make_js_string"] = function(ptr, len) {
169176
return swift.memory.retain(decodeString(ptr, len));

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,13 @@ export async function createInstantiator(options, swift) {
163163
swift.memory.release(sourceId);
164164
const bytes = new Uint8Array(memory.buffer, bytesPtr);
165165
bytes.set(source);
166+
return source.length;
167+
}
168+
bjs["swift_js_init_memory_from_string"] = function(stringId, bytesPtr) {
169+
const str = swift.memory.getObject(stringId);
170+
swift.memory.release(stringId);
171+
const target = new Uint8Array(memory.buffer, bytesPtr);
172+
return textEncoder.encodeInto(str, target).written;
166173
}
167174
bjs["swift_js_make_js_string"] = function(ptr, len) {
168175
return swift.memory.retain(decodeString(ptr, len));

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,8 @@ export async function createInstantiator(options, swift) {
4141
let bjs = null;
4242
const __bjs_createConfigHelpers = () => ({
4343
lower: (value) => {
44-
const bytes = textEncoder.encode(value.name);
45-
const id = swift.memory.retain(bytes);
46-
i32Stack.push(bytes.length);
44+
const id = swift.memory.retain(value.name);
45+
i32Stack.push(value.name.length * 3);
4746
i32Stack.push(id);
4847
i32Stack.push((value.value | 0));
4948
i32Stack.push(value.enabled ? 1 : 0);
@@ -91,6 +90,13 @@ export async function createInstantiator(options, swift) {
9190
swift.memory.release(sourceId);
9291
const bytes = new Uint8Array(memory.buffer, bytesPtr);
9392
bytes.set(source);
93+
return source.length;
94+
}
95+
bjs["swift_js_init_memory_from_string"] = function(stringId, bytesPtr) {
96+
const str = swift.memory.getObject(stringId);
97+
swift.memory.release(stringId);
98+
const target = new Uint8Array(memory.buffer, bytesPtr);
99+
return textEncoder.encodeInto(str, target).written;
94100
}
95101
bjs["swift_js_make_js_string"] = function(ptr, len) {
96102
return swift.memory.retain(decodeString(ptr, len));
@@ -563,9 +569,8 @@ export async function createInstantiator(options, swift) {
563569
},
564570
testStringArrayDefault: function bjs_testStringArrayDefault(names = ["a", "b", "c"]) {
565571
for (const elem of names) {
566-
const bytes = textEncoder.encode(elem);
567-
const id = swift.memory.retain(bytes);
568-
i32Stack.push(bytes.length);
572+
const id = swift.memory.retain(elem);
573+
i32Stack.push(elem.length * 3);
569574
i32Stack.push(id);
570575
}
571576
i32Stack.push(names.length);

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,14 @@ export async function createInstantiator(options, swift) {
3535
let bjs = null;
3636
const __bjs_createCountersHelpers = () => ({
3737
lower: (value) => {
38-
const bytes = textEncoder.encode(value.name);
39-
const id = swift.memory.retain(bytes);
40-
i32Stack.push(bytes.length);
38+
const id = swift.memory.retain(value.name);
39+
i32Stack.push(value.name.length * 3);
4140
i32Stack.push(id);
4241
const entries = Object.entries(value.counts);
4342
for (const entry of entries) {
4443
const [key, value] = entry;
45-
const bytes1 = textEncoder.encode(key);
46-
const id1 = swift.memory.retain(bytes1);
47-
i32Stack.push(bytes1.length);
44+
const id1 = swift.memory.retain(key);
45+
i32Stack.push(key.length * 3);
4846
i32Stack.push(id1);
4947
const isSome = value != null ? 1 : 0;
5048
if (isSome) {
@@ -90,6 +88,13 @@ export async function createInstantiator(options, swift) {
9088
swift.memory.release(sourceId);
9189
const bytes = new Uint8Array(memory.buffer, bytesPtr);
9290
bytes.set(source);
91+
return source.length;
92+
}
93+
bjs["swift_js_init_memory_from_string"] = function(stringId, bytesPtr) {
94+
const str = swift.memory.getObject(stringId);
95+
swift.memory.release(stringId);
96+
const target = new Uint8Array(memory.buffer, bytesPtr);
97+
return textEncoder.encodeInto(str, target).written;
9398
}
9499
bjs["swift_js_make_js_string"] = function(ptr, len) {
95100
return swift.memory.retain(decodeString(ptr, len));
@@ -261,9 +266,8 @@ export async function createInstantiator(options, swift) {
261266
const entries = Object.entries(ret);
262267
for (const entry of entries) {
263268
const [key, value] = entry;
264-
const bytes = textEncoder.encode(key);
265-
const id = swift.memory.retain(bytes);
266-
i32Stack.push(bytes.length);
269+
const id = swift.memory.retain(key);
270+
i32Stack.push(key.length * 3);
267271
i32Stack.push(id);
268272
f64Stack.push(value);
269273
}
@@ -352,9 +356,8 @@ export async function createInstantiator(options, swift) {
352356
const entries = Object.entries(values);
353357
for (const entry of entries) {
354358
const [key, value] = entry;
355-
const bytes = textEncoder.encode(key);
356-
const id = swift.memory.retain(bytes);
357-
i32Stack.push(bytes.length);
359+
const id = swift.memory.retain(key);
360+
i32Stack.push(key.length * 3);
358361
i32Stack.push(id);
359362
i32Stack.push((value | 0));
360363
}
@@ -375,13 +378,11 @@ export async function createInstantiator(options, swift) {
375378
const entries = Object.entries(values);
376379
for (const entry of entries) {
377380
const [key, value] = entry;
378-
const bytes = textEncoder.encode(key);
379-
const id = swift.memory.retain(bytes);
380-
i32Stack.push(bytes.length);
381+
const id = swift.memory.retain(key);
382+
i32Stack.push(key.length * 3);
381383
i32Stack.push(id);
382-
const bytes1 = textEncoder.encode(value);
383-
const id1 = swift.memory.retain(bytes1);
384-
i32Stack.push(bytes1.length);
384+
const id1 = swift.memory.retain(value);
385+
i32Stack.push(value.length * 3);
385386
i32Stack.push(id1);
386387
}
387388
i32Stack.push(entries.length);
@@ -408,9 +409,8 @@ export async function createInstantiator(options, swift) {
408409
const entries = Object.entries(values);
409410
for (const entry of entries) {
410411
const [key, value] = entry;
411-
const bytes = textEncoder.encode(key);
412-
const id = swift.memory.retain(bytes);
413-
i32Stack.push(bytes.length);
412+
const id = swift.memory.retain(key);
413+
i32Stack.push(key.length * 3);
414414
i32Stack.push(id);
415415
for (const elem of value) {
416416
i32Stack.push((elem | 0));
@@ -438,9 +438,8 @@ export async function createInstantiator(options, swift) {
438438
const entries = Object.entries(boxes);
439439
for (const entry of entries) {
440440
const [key, value] = entry;
441-
const bytes = textEncoder.encode(key);
442-
const id = swift.memory.retain(bytes);
443-
i32Stack.push(bytes.length);
441+
const id = swift.memory.retain(key);
442+
i32Stack.push(key.length * 3);
444443
i32Stack.push(id);
445444
ptrStack.push(value.pointer);
446445
}
@@ -460,9 +459,8 @@ export async function createInstantiator(options, swift) {
460459
const entries = Object.entries(boxes);
461460
for (const entry of entries) {
462461
const [key, value] = entry;
463-
const bytes = textEncoder.encode(key);
464-
const id = swift.memory.retain(bytes);
465-
i32Stack.push(bytes.length);
462+
const id = swift.memory.retain(key);
463+
i32Stack.push(key.length * 3);
466464
i32Stack.push(id);
467465
const isSome = value != null ? 1 : 0;
468466
if (isSome) {

0 commit comments

Comments
 (0)