Skip to content

Commit acf7d9e

Browse files
committed
BridgeJS: Add LRU encoding cache for string parameter and return paths
Add a 4096-entry LRU cache (Map<string, Uint8Array>) in front of textEncoder.encode() for the ExportSwift parameter path and the ImportTS return path. Repeated strings skip encoding entirely on cache hit. The cache uses JS Map insertion-order semantics for O(1) LRU eviction. 4096 entries covers realistic vocabularies without pathological eviction churn that smaller caches exhibit. No BridgeType changes. No Swift-side changes.
1 parent 5e96639 commit acf7d9e

52 files changed

Lines changed: 259 additions & 100 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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,9 @@ public struct BridgeJSLink {
339339
"let \(JSGlueVariableScope.reservedStorageToReturnOptionalFloat);",
340340
"let \(JSGlueVariableScope.reservedStorageToReturnOptionalDouble);",
341341
"let \(JSGlueVariableScope.reservedStorageToReturnOptionalHeapObject);",
342+
"const \(JSGlueVariableScope.reservedStrEncCache) = new Map();",
343+
"const \(JSGlueVariableScope.reservedStrEncCacheMax) = 4096;",
344+
"function \(JSGlueVariableScope.reservedCachedEncode)(s) { let b = \(JSGlueVariableScope.reservedStrEncCache).get(s); if (b) { \(JSGlueVariableScope.reservedStrEncCache).delete(s); \(JSGlueVariableScope.reservedStrEncCache).set(s, b); return b; } b = \(JSGlueVariableScope.reservedTextEncoder).encode(s); if (\(JSGlueVariableScope.reservedStrEncCache).size >= \(JSGlueVariableScope.reservedStrEncCacheMax)) { \(JSGlueVariableScope.reservedStrEncCache).delete(\(JSGlueVariableScope.reservedStrEncCache).keys().next().value); } \(JSGlueVariableScope.reservedStrEncCache).set(s, b); return b; };",
342345
"let \(JSGlueVariableScope.reservedStringStack) = [];",
343346
"let \(JSGlueVariableScope.reservedI32Stack) = [];",
344347
"let \(JSGlueVariableScope.reservedI64Stack) = [];",

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ final class JSGlueVariableScope {
2424
static let reservedStorageToReturnOptionalHeapObject = "tmpRetOptionalHeapObject"
2525
static let reservedTextEncoder = "textEncoder"
2626
static let reservedTextDecoder = "textDecoder"
27+
static let reservedCachedEncode = "_cachedEncode"
28+
static let reservedStrEncCache = "_strEncCache"
29+
static let reservedStrEncCacheMax = "_strEncCacheMax"
2730
static let reservedStringStack = "strStack"
2831
static let reservedI32Stack = "i32Stack"
2932
static let reservedI64Stack = "i64Stack"
@@ -53,6 +56,9 @@ final class JSGlueVariableScope {
5356
reservedStorageToReturnOptionalHeapObject,
5457
reservedTextEncoder,
5558
reservedTextDecoder,
59+
reservedCachedEncode,
60+
reservedStrEncCache,
61+
reservedStrEncCacheMax,
5662
reservedStringStack,
5763
reservedI32Stack,
5864
reservedI64Stack,
@@ -263,7 +269,7 @@ struct IntrinsicJSFragment: Sendable {
263269
let argument = arguments[0]
264270
let bytesLabel = scope.variable("\(argument)Bytes")
265271
let bytesIdLabel = scope.variable("\(argument)Id")
266-
printer.write("const \(bytesLabel) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(argument));")
272+
printer.write("const \(bytesLabel) = \(JSGlueVariableScope.reservedCachedEncode)(\(argument));")
267273
printer.write("const \(bytesIdLabel) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesLabel));")
268274
return [bytesIdLabel, "\(bytesLabel).length"]
269275
}
@@ -296,7 +302,7 @@ struct IntrinsicJSFragment: Sendable {
296302
printCode: { arguments, context in
297303
let printer = context.printer
298304
printer.write(
299-
"\(JSGlueVariableScope.reservedStorageToReturnBytes) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(arguments[0]));"
305+
"\(JSGlueVariableScope.reservedStorageToReturnBytes) = \(JSGlueVariableScope.reservedCachedEncode)(\(arguments[0]));"
300306
)
301307
return ["\(JSGlueVariableScope.reservedStorageToReturnBytes).length"]
302308
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ export async function createInstantiator(options, swift) {
3232
let tmpRetOptionalFloat;
3333
let tmpRetOptionalDouble;
3434
let tmpRetOptionalHeapObject;
35+
const _strEncCache = new Map();
36+
const _strEncCacheMax = 4096;
37+
function _cachedEncode(s) { let b = _strEncCache.get(s); if (b) { _strEncCache.delete(s); _strEncCache.set(s, b); return b; } b = textEncoder.encode(s); if (_strEncCache.size >= _strEncCacheMax) { _strEncCache.delete(_strEncCache.keys().next().value); } _strEncCache.set(s, b); return b; };
3538
let strStack = [];
3639
let i32Stack = [];
3740
let i64Stack = [];
@@ -570,7 +573,7 @@ export async function createInstantiator(options, swift) {
570573
structHelpers.Point.lower(elem);
571574
}
572575
i32Stack.push(points.length);
573-
const matchingBytes = textEncoder.encode(matching);
576+
const matchingBytes = _cachedEncode(matching);
574577
const matchingId = swift.memory.retain(matchingBytes);
575578
instance.exports.bjs_findFirstPoint(matchingId, matchingBytes.length);
576579
const structValue = structHelpers.Point.lift();

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export async function createInstantiator(options, swift) {
1919
let tmpRetOptionalFloat;
2020
let tmpRetOptionalDouble;
2121
let tmpRetOptionalHeapObject;
22+
const _strEncCache = new Map();
23+
const _strEncCacheMax = 4096;
24+
function _cachedEncode(s) { let b = _strEncCache.get(s); if (b) { _strEncCache.delete(s); _strEncCache.set(s, b); return b; } b = textEncoder.encode(s); if (_strEncCache.size >= _strEncCacheMax) { _strEncCache.delete(_strEncCache.keys().next().value); } _strEncCache.set(s, b); return b; };
2225
let strStack = [];
2326
let i32Stack = [];
2427
let i64Stack = [];
@@ -216,7 +219,7 @@ export async function createInstantiator(options, swift) {
216219
return ret1;
217220
},
218221
asyncRoundTripString: function bjs_asyncRoundTripString(v) {
219-
const vBytes = textEncoder.encode(v);
222+
const vBytes = _cachedEncode(v);
220223
const vId = swift.memory.retain(vBytes);
221224
const ret = instance.exports.bjs_asyncRoundTripString(vId, vBytes.length);
222225
const ret1 = swift.memory.getObject(ret);

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export async function createInstantiator(options, swift) {
1919
let tmpRetOptionalFloat;
2020
let tmpRetOptionalDouble;
2121
let tmpRetOptionalHeapObject;
22+
const _strEncCache = new Map();
23+
const _strEncCacheMax = 4096;
24+
function _cachedEncode(s) { let b = _strEncCache.get(s); if (b) { _strEncCache.delete(s); _strEncCache.set(s, b); return b; } b = textEncoder.encode(s); if (_strEncCache.size >= _strEncCacheMax) { _strEncCache.delete(_strEncCache.keys().next().value); } _strEncCache.set(s, b); return b; };
2225
let strStack = [];
2326
let i32Stack = [];
2427
let i64Stack = [];
@@ -360,7 +363,7 @@ export async function createInstantiator(options, swift) {
360363
}
361364
bjs["make_swift_closure_TestModule_10TestModulesSS_y"] = function(boxPtr, file, line) {
362365
const lower_closure_TestModule_10TestModulesSS_y = function(param0) {
363-
const param0Bytes = textEncoder.encode(param0);
366+
const param0Bytes = _cachedEncode(param0);
364367
const param0Id = swift.memory.retain(param0Bytes);
365368
instance.exports.invoke_swift_closure_TestModule_10TestModulesSS_y(boxPtr, param0Id, param0Bytes.length);
366369
if (tmpRetException) {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export async function createInstantiator(options, swift) {
1919
let tmpRetOptionalFloat;
2020
let tmpRetOptionalDouble;
2121
let tmpRetOptionalHeapObject;
22+
const _strEncCache = new Map();
23+
const _strEncCacheMax = 4096;
24+
function _cachedEncode(s) { let b = _strEncCache.get(s); if (b) { _strEncCache.delete(s); _strEncCache.set(s, b); return b; } b = textEncoder.encode(s); if (_strEncCache.size >= _strEncCacheMax) { _strEncCache.delete(_strEncCache.keys().next().value); } _strEncCache.set(s, b); return b; };
2225
let strStack = [];
2326
let i32Stack = [];
2427
let i64Stack = [];

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

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export async function createInstantiator(options, swift) {
2525
let tmpRetOptionalFloat;
2626
let tmpRetOptionalDouble;
2727
let tmpRetOptionalHeapObject;
28+
const _strEncCache = new Map();
29+
const _strEncCacheMax = 4096;
30+
function _cachedEncode(s) { let b = _strEncCache.get(s); if (b) { _strEncCache.delete(s); _strEncCache.set(s, b); return b; } b = textEncoder.encode(s); if (_strEncCache.size >= _strEncCacheMax) { _strEncCache.delete(_strEncCache.keys().next().value); } _strEncCache.set(s, b); return b; };
2831
let strStack = [];
2932
let i32Stack = [];
3033
let i64Stack = [];
@@ -331,7 +334,7 @@ export async function createInstantiator(options, swift) {
331334
}
332335

333336
constructor(name) {
334-
const nameBytes = textEncoder.encode(name);
337+
const nameBytes = _cachedEncode(name);
335338
const nameId = swift.memory.retain(nameBytes);
336339
const ret = instance.exports.bjs_DefaultGreeter_init(nameId, nameBytes.length);
337340
return DefaultGreeter.__construct(ret);
@@ -343,7 +346,7 @@ export async function createInstantiator(options, swift) {
343346
return ret;
344347
}
345348
set name(value) {
346-
const valueBytes = textEncoder.encode(value);
349+
const valueBytes = _cachedEncode(value);
347350
const valueId = swift.memory.retain(valueBytes);
348351
instance.exports.bjs_DefaultGreeter_name_set(this.pointer, valueId, valueBytes.length);
349352
}
@@ -364,12 +367,12 @@ export async function createInstantiator(options, swift) {
364367
}
365368

366369
constructor(name = "Default", count = 42, enabled = true, status = StatusValues.Active, tag = null) {
367-
const nameBytes = textEncoder.encode(name);
370+
const nameBytes = _cachedEncode(name);
368371
const nameId = swift.memory.retain(nameBytes);
369372
const isSome = tag != null;
370373
let result, result1;
371374
if (isSome) {
372-
const tagBytes = textEncoder.encode(tag);
375+
const tagBytes = _cachedEncode(tag);
373376
const tagId = swift.memory.retain(tagBytes);
374377
result = tagId;
375378
result1 = tagBytes.length;
@@ -387,7 +390,7 @@ export async function createInstantiator(options, swift) {
387390
return ret;
388391
}
389392
set name(value) {
390-
const valueBytes = textEncoder.encode(value);
393+
const valueBytes = _cachedEncode(value);
391394
const valueId = swift.memory.retain(valueBytes);
392395
instance.exports.bjs_ConstructorDefaults_name_set(this.pointer, valueId, valueBytes.length);
393396
}
@@ -422,7 +425,7 @@ export async function createInstantiator(options, swift) {
422425
const isSome = value != null;
423426
let result, result1;
424427
if (isSome) {
425-
const valueBytes = textEncoder.encode(value);
428+
const valueBytes = _cachedEncode(value);
426429
const valueId = swift.memory.retain(valueBytes);
427430
result = valueId;
428431
result1 = valueBytes.length;
@@ -444,7 +447,7 @@ export async function createInstantiator(options, swift) {
444447
EmptyGreeter,
445448
ConstructorDefaults,
446449
testStringDefault: function bjs_testStringDefault(message = "Hello World") {
447-
const messageBytes = textEncoder.encode(message);
450+
const messageBytes = _cachedEncode(message);
448451
const messageId = swift.memory.retain(messageBytes);
449452
instance.exports.bjs_testStringDefault(messageId, messageBytes.length);
450453
const ret = tmpRetString;
@@ -471,7 +474,7 @@ export async function createInstantiator(options, swift) {
471474
const isSome = name != null;
472475
let result, result1;
473476
if (isSome) {
474-
const nameBytes = textEncoder.encode(name);
477+
const nameBytes = _cachedEncode(name);
475478
const nameId = swift.memory.retain(nameBytes);
476479
result = nameId;
477480
result1 = nameBytes.length;
@@ -488,7 +491,7 @@ export async function createInstantiator(options, swift) {
488491
const isSome = greeting != null;
489492
let result, result1;
490493
if (isSome) {
491-
const greetingBytes = textEncoder.encode(greeting);
494+
const greetingBytes = _cachedEncode(greeting);
492495
const greetingId = swift.memory.retain(greetingBytes);
493496
result = greetingId;
494497
result1 = greetingBytes.length;
@@ -502,7 +505,7 @@ export async function createInstantiator(options, swift) {
502505
return optResult;
503506
},
504507
testMultipleDefaults: function bjs_testMultipleDefaults(title = "Default Title", count = 10, enabled = false) {
505-
const titleBytes = textEncoder.encode(title);
508+
const titleBytes = _cachedEncode(title);
506509
const titleId = swift.memory.retain(titleBytes);
507510
instance.exports.bjs_testMultipleDefaults(titleId, titleBytes.length, count, enabled);
508511
const ret = tmpRetString;
@@ -622,7 +625,7 @@ export async function createInstantiator(options, swift) {
622625
return arrayResult;
623626
},
624627
testMixedWithArrayDefault: function bjs_testMixedWithArrayDefault(name = "test", values = [10, 20, 30], enabled = true) {
625-
const nameBytes = textEncoder.encode(name);
628+
const nameBytes = _cachedEncode(name);
626629
const nameId = swift.memory.retain(nameBytes);
627630
for (const elem of values) {
628631
i32Stack.push((elem | 0));

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export async function createInstantiator(options, swift) {
1919
let tmpRetOptionalFloat;
2020
let tmpRetOptionalDouble;
2121
let tmpRetOptionalHeapObject;
22+
const _strEncCache = new Map();
23+
const _strEncCacheMax = 4096;
24+
function _cachedEncode(s) { let b = _strEncCache.get(s); if (b) { _strEncCache.delete(s); _strEncCache.set(s, b); return b; } b = textEncoder.encode(s); if (_strEncCache.size >= _strEncCacheMax) { _strEncCache.delete(_strEncCache.keys().next().value); } _strEncCache.set(s, b); return b; };
2225
let strStack = [];
2326
let i32Stack = [];
2427
let i64Stack = [];

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ export async function createInstantiator(options, swift) {
100100
let tmpRetOptionalFloat;
101101
let tmpRetOptionalDouble;
102102
let tmpRetOptionalHeapObject;
103+
const _strEncCache = new Map();
104+
const _strEncCacheMax = 4096;
105+
function _cachedEncode(s) { let b = _strEncCache.get(s); if (b) { _strEncCache.delete(s); _strEncCache.set(s, b); return b; } b = textEncoder.encode(s); if (_strEncCache.size >= _strEncCacheMax) { _strEncCache.delete(_strEncCache.keys().next().value); } _strEncCache.set(s, b); return b; };
103106
let strStack = [];
104107
let i32Stack = [];
105108
let i64Stack = [];

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ export async function createInstantiator(options, swift) {
4343
let tmpRetOptionalFloat;
4444
let tmpRetOptionalDouble;
4545
let tmpRetOptionalHeapObject;
46+
const _strEncCache = new Map();
47+
const _strEncCacheMax = 4096;
48+
function _cachedEncode(s) { let b = _strEncCache.get(s); if (b) { _strEncCache.delete(s); _strEncCache.set(s, b); return b; } b = textEncoder.encode(s); if (_strEncCache.size >= _strEncCacheMax) { _strEncCache.delete(_strEncCache.keys().next().value); } _strEncCache.set(s, b); return b; };
4649
let strStack = [];
4750
let i32Stack = [];
4851
let i64Stack = [];

0 commit comments

Comments
 (0)