Skip to content

Commit 0b8922a

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. _swift_js_init_memory detects the string via typeof and writes UTF-8 directly into the WASM linear memory buffer using encodeInto(). It now returns the actual byte count written (Int32 instead of Void) so String(unsafeUninitializedCapacity:) uses the correct length. This eliminates the intermediate Uint8Array allocation for every string element in arrays and struct fields.
1 parent acf7d9e commit 0b8922a

53 files changed

Lines changed: 320 additions & 149 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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,18 @@ public struct BridgeJSLink {
396396
printer.write(
397397
"const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, bytesPtr);"
398398
)
399+
// When the stack ABI retains a JS string directly (instead of
400+
// a pre-encoded Uint8Array), use encodeInto() to write UTF-8
401+
// directly into the WASM buffer - no intermediate allocation.
402+
printer.write("if (typeof source === 'string') {")
403+
printer.indent {
404+
printer.write(
405+
"return \(JSGlueVariableScope.reservedTextEncoder).encodeInto(source, bytes).written;"
406+
)
407+
}
408+
printer.write("}")
399409
printer.write("bytes.set(source);")
410+
printer.write("return source.length;")
400411
}
401412
printer.write("}")
402413
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: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,11 @@ export async function createInstantiator(options, swift) {
7373
const source = swift.memory.getObject(sourceId);
7474
swift.memory.release(sourceId);
7575
const bytes = new Uint8Array(memory.buffer, bytesPtr);
76+
if (typeof source === 'string') {
77+
return textEncoder.encodeInto(source, bytes).written;
78+
}
7679
bytes.set(source);
80+
return source.length;
7781
}
7882
bjs["swift_js_make_js_string"] = function(ptr, len) {
7983
return swift.memory.retain(decodeString(ptr, len));
@@ -304,9 +308,8 @@ export async function createInstantiator(options, swift) {
304308
arrayResult.reverse();
305309
let ret = imports.importProcessStrings(arrayResult);
306310
for (const elem of ret) {
307-
const bytes = textEncoder.encode(elem);
308-
const id = swift.memory.retain(bytes);
309-
i32Stack.push(bytes.length);
311+
const id = swift.memory.retain(elem);
312+
i32Stack.push(elem.length * 3);
310313
i32Stack.push(id);
311314
}
312315
i32Stack.push(ret.length);
@@ -414,9 +417,8 @@ export async function createInstantiator(options, swift) {
414417
}
415418
i32Stack.push(nums.length);
416419
for (const elem1 of strs) {
417-
const bytes = textEncoder.encode(elem1);
418-
const id = swift.memory.retain(bytes);
419-
i32Stack.push(bytes.length);
420+
const id = swift.memory.retain(elem1);
421+
i32Stack.push(elem1.length * 3);
420422
i32Stack.push(id);
421423
}
422424
i32Stack.push(strs.length);
@@ -469,9 +471,8 @@ export async function createInstantiator(options, swift) {
469471
},
470472
processStringArray: function bjs_processStringArray(values) {
471473
for (const elem of values) {
472-
const bytes = textEncoder.encode(elem);
473-
const id = swift.memory.retain(bytes);
474-
i32Stack.push(bytes.length);
474+
const id = swift.memory.retain(elem);
475+
i32Stack.push(elem.length * 3);
475476
i32Stack.push(id);
476477
}
477478
i32Stack.push(values.length);
@@ -654,9 +655,8 @@ export async function createInstantiator(options, swift) {
654655
for (const elem of values) {
655656
const isSome = elem != null ? 1 : 0;
656657
if (isSome) {
657-
const bytes = textEncoder.encode(elem);
658-
const id = swift.memory.retain(bytes);
659-
i32Stack.push(bytes.length);
658+
const id = swift.memory.retain(elem);
659+
i32Stack.push(elem.length * 3);
660660
i32Stack.push(id);
661661
}
662662
i32Stack.push(isSome);
@@ -810,9 +810,8 @@ export async function createInstantiator(options, swift) {
810810
processNestedStringArray: function bjs_processNestedStringArray(values) {
811811
for (const elem of values) {
812812
for (const elem1 of elem) {
813-
const bytes = textEncoder.encode(elem1);
814-
const id = swift.memory.retain(bytes);
815-
i32Stack.push(bytes.length);
813+
const id = swift.memory.retain(elem1);
814+
i32Stack.push(elem1.length * 3);
816815
i32Stack.push(id);
817816
}
818817
i32Stack.push(elem.length);
@@ -979,9 +978,8 @@ export async function createInstantiator(options, swift) {
979978
}
980979
i32Stack.push(nums.length);
981980
for (const elem1 of strs) {
982-
const bytes = textEncoder.encode(elem1);
983-
const id = swift.memory.retain(bytes);
984-
i32Stack.push(bytes.length);
981+
const id = swift.memory.retain(elem1);
982+
i32Stack.push(elem1.length * 3);
985983
i32Stack.push(id);
986984
}
987985
i32Stack.push(strs.length);
@@ -1000,9 +998,8 @@ export async function createInstantiator(options, swift) {
1000998
const isSome1 = b != null;
1001999
if (isSome1) {
10021000
for (const elem1 of b) {
1003-
const bytes = textEncoder.encode(elem1);
1004-
const id = swift.memory.retain(bytes);
1005-
i32Stack.push(bytes.length);
1001+
const id = swift.memory.retain(elem1);
1002+
i32Stack.push(elem1.length * 3);
10061003
i32Stack.push(id);
10071004
}
10081005
i32Stack.push(b.length);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ export async function createInstantiator(options, swift) {
4848
const source = swift.memory.getObject(sourceId);
4949
swift.memory.release(sourceId);
5050
const bytes = new Uint8Array(memory.buffer, bytesPtr);
51+
if (typeof source === 'string') {
52+
return textEncoder.encodeInto(source, bytes).written;
53+
}
5154
bytes.set(source);
55+
return source.length;
5256
}
5357
bjs["swift_js_make_js_string"] = function(ptr, len) {
5458
return swift.memory.retain(decodeString(ptr, len));

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,11 @@ export async function createInstantiator(options, swift) {
163163
const source = swift.memory.getObject(sourceId);
164164
swift.memory.release(sourceId);
165165
const bytes = new Uint8Array(memory.buffer, bytesPtr);
166+
if (typeof source === 'string') {
167+
return textEncoder.encodeInto(source, bytes).written;
168+
}
166169
bytes.set(source);
170+
return source.length;
167171
}
168172
bjs["swift_js_make_js_string"] = function(ptr, len) {
169173
return swift.memory.retain(decodeString(ptr, len));

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,11 @@ export async function createInstantiator(options, swift) {
162162
const source = swift.memory.getObject(sourceId);
163163
swift.memory.release(sourceId);
164164
const bytes = new Uint8Array(memory.buffer, bytesPtr);
165+
if (typeof source === 'string') {
166+
return textEncoder.encodeInto(source, bytes).written;
167+
}
165168
bytes.set(source);
169+
return source.length;
166170
}
167171
bjs["swift_js_make_js_string"] = function(ptr, len) {
168172
return swift.memory.retain(decodeString(ptr, len));

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

Lines changed: 8 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);
@@ -90,7 +89,11 @@ export async function createInstantiator(options, swift) {
9089
const source = swift.memory.getObject(sourceId);
9190
swift.memory.release(sourceId);
9291
const bytes = new Uint8Array(memory.buffer, bytesPtr);
92+
if (typeof source === 'string') {
93+
return textEncoder.encodeInto(source, bytes).written;
94+
}
9395
bytes.set(source);
96+
return source.length;
9497
}
9598
bjs["swift_js_make_js_string"] = function(ptr, len) {
9699
return swift.memory.retain(decodeString(ptr, len));
@@ -563,9 +566,8 @@ export async function createInstantiator(options, swift) {
563566
},
564567
testStringArrayDefault: function bjs_testStringArrayDefault(names = ["a", "b", "c"]) {
565568
for (const elem of names) {
566-
const bytes = textEncoder.encode(elem);
567-
const id = swift.memory.retain(bytes);
568-
i32Stack.push(bytes.length);
569+
const id = swift.memory.retain(elem);
570+
i32Stack.push(elem.length * 3);
569571
i32Stack.push(id);
570572
}
571573
i32Stack.push(names.length);

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

Lines changed: 22 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) {
@@ -89,7 +87,11 @@ export async function createInstantiator(options, swift) {
8987
const source = swift.memory.getObject(sourceId);
9088
swift.memory.release(sourceId);
9189
const bytes = new Uint8Array(memory.buffer, bytesPtr);
90+
if (typeof source === 'string') {
91+
return textEncoder.encodeInto(source, bytes).written;
92+
}
9293
bytes.set(source);
94+
return source.length;
9395
}
9496
bjs["swift_js_make_js_string"] = function(ptr, len) {
9597
return swift.memory.retain(decodeString(ptr, len));
@@ -261,9 +263,8 @@ export async function createInstantiator(options, swift) {
261263
const entries = Object.entries(ret);
262264
for (const entry of entries) {
263265
const [key, value] = entry;
264-
const bytes = textEncoder.encode(key);
265-
const id = swift.memory.retain(bytes);
266-
i32Stack.push(bytes.length);
266+
const id = swift.memory.retain(key);
267+
i32Stack.push(key.length * 3);
267268
i32Stack.push(id);
268269
f64Stack.push(value);
269270
}
@@ -352,9 +353,8 @@ export async function createInstantiator(options, swift) {
352353
const entries = Object.entries(values);
353354
for (const entry of entries) {
354355
const [key, value] = entry;
355-
const bytes = textEncoder.encode(key);
356-
const id = swift.memory.retain(bytes);
357-
i32Stack.push(bytes.length);
356+
const id = swift.memory.retain(key);
357+
i32Stack.push(key.length * 3);
358358
i32Stack.push(id);
359359
i32Stack.push((value | 0));
360360
}
@@ -375,13 +375,11 @@ export async function createInstantiator(options, swift) {
375375
const entries = Object.entries(values);
376376
for (const entry of entries) {
377377
const [key, value] = entry;
378-
const bytes = textEncoder.encode(key);
379-
const id = swift.memory.retain(bytes);
380-
i32Stack.push(bytes.length);
378+
const id = swift.memory.retain(key);
379+
i32Stack.push(key.length * 3);
381380
i32Stack.push(id);
382-
const bytes1 = textEncoder.encode(value);
383-
const id1 = swift.memory.retain(bytes1);
384-
i32Stack.push(bytes1.length);
381+
const id1 = swift.memory.retain(value);
382+
i32Stack.push(value.length * 3);
385383
i32Stack.push(id1);
386384
}
387385
i32Stack.push(entries.length);
@@ -408,9 +406,8 @@ export async function createInstantiator(options, swift) {
408406
const entries = Object.entries(values);
409407
for (const entry of entries) {
410408
const [key, value] = entry;
411-
const bytes = textEncoder.encode(key);
412-
const id = swift.memory.retain(bytes);
413-
i32Stack.push(bytes.length);
409+
const id = swift.memory.retain(key);
410+
i32Stack.push(key.length * 3);
414411
i32Stack.push(id);
415412
for (const elem of value) {
416413
i32Stack.push((elem | 0));
@@ -438,9 +435,8 @@ export async function createInstantiator(options, swift) {
438435
const entries = Object.entries(boxes);
439436
for (const entry of entries) {
440437
const [key, value] = entry;
441-
const bytes = textEncoder.encode(key);
442-
const id = swift.memory.retain(bytes);
443-
i32Stack.push(bytes.length);
438+
const id = swift.memory.retain(key);
439+
i32Stack.push(key.length * 3);
444440
i32Stack.push(id);
445441
ptrStack.push(value.pointer);
446442
}
@@ -460,9 +456,8 @@ export async function createInstantiator(options, swift) {
460456
const entries = Object.entries(boxes);
461457
for (const entry of entries) {
462458
const [key, value] = entry;
463-
const bytes = textEncoder.encode(key);
464-
const id = swift.memory.retain(bytes);
465-
i32Stack.push(bytes.length);
459+
const id = swift.memory.retain(key);
460+
i32Stack.push(key.length * 3);
466461
i32Stack.push(id);
467462
const isSome = value != null ? 1 : 0;
468463
if (isSome) {

0 commit comments

Comments
 (0)