Skip to content

Commit 8ea0e68

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 8ea0e68

52 files changed

Lines changed: 922 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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,22 @@ 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)(str) {",
345+
" let encoded = \(JSGlueVariableScope.reservedStrEncCache).get(str);",
346+
" if (encoded) {",
347+
" \(JSGlueVariableScope.reservedStrEncCache).delete(str);",
348+
" \(JSGlueVariableScope.reservedStrEncCache).set(str, encoded);",
349+
" return encoded;",
350+
" }",
351+
" encoded = \(JSGlueVariableScope.reservedTextEncoder).encode(str);",
352+
" if (\(JSGlueVariableScope.reservedStrEncCache).size >= \(JSGlueVariableScope.reservedStrEncCacheMax)) {",
353+
" \(JSGlueVariableScope.reservedStrEncCache).delete(\(JSGlueVariableScope.reservedStrEncCache).keys().next().value);",
354+
" }",
355+
" \(JSGlueVariableScope.reservedStrEncCache).set(str, encoded);",
356+
" return encoded;",
357+
"}",
342358
"let \(JSGlueVariableScope.reservedStringStack) = [];",
343359
"let \(JSGlueVariableScope.reservedI32Stack) = [];",
344360
"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: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@ 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(str) {
38+
let encoded = _strEncCache.get(str);
39+
if (encoded) {
40+
_strEncCache.delete(str);
41+
_strEncCache.set(str, encoded);
42+
return encoded;
43+
}
44+
encoded = textEncoder.encode(str);
45+
if (_strEncCache.size >= _strEncCacheMax) {
46+
_strEncCache.delete(_strEncCache.keys().next().value);
47+
}
48+
_strEncCache.set(str, encoded);
49+
return encoded;
50+
}
3551
let strStack = [];
3652
let i32Stack = [];
3753
let i64Stack = [];
@@ -570,7 +586,7 @@ export async function createInstantiator(options, swift) {
570586
structHelpers.Point.lower(elem);
571587
}
572588
i32Stack.push(points.length);
573-
const matchingBytes = textEncoder.encode(matching);
589+
const matchingBytes = _cachedEncode(matching);
574590
const matchingId = swift.memory.retain(matchingBytes);
575591
instance.exports.bjs_findFirstPoint(matchingId, matchingBytes.length);
576592
const structValue = structHelpers.Point.lift();

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ 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(str) {
25+
let encoded = _strEncCache.get(str);
26+
if (encoded) {
27+
_strEncCache.delete(str);
28+
_strEncCache.set(str, encoded);
29+
return encoded;
30+
}
31+
encoded = textEncoder.encode(str);
32+
if (_strEncCache.size >= _strEncCacheMax) {
33+
_strEncCache.delete(_strEncCache.keys().next().value);
34+
}
35+
_strEncCache.set(str, encoded);
36+
return encoded;
37+
}
2238
let strStack = [];
2339
let i32Stack = [];
2440
let i64Stack = [];
@@ -216,7 +232,7 @@ export async function createInstantiator(options, swift) {
216232
return ret1;
217233
},
218234
asyncRoundTripString: function bjs_asyncRoundTripString(v) {
219-
const vBytes = textEncoder.encode(v);
235+
const vBytes = _cachedEncode(v);
220236
const vId = swift.memory.retain(vBytes);
221237
const ret = instance.exports.bjs_asyncRoundTripString(vId, vBytes.length);
222238
const ret1 = swift.memory.getObject(ret);

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ 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(str) {
25+
let encoded = _strEncCache.get(str);
26+
if (encoded) {
27+
_strEncCache.delete(str);
28+
_strEncCache.set(str, encoded);
29+
return encoded;
30+
}
31+
encoded = textEncoder.encode(str);
32+
if (_strEncCache.size >= _strEncCacheMax) {
33+
_strEncCache.delete(_strEncCache.keys().next().value);
34+
}
35+
_strEncCache.set(str, encoded);
36+
return encoded;
37+
}
2238
let strStack = [];
2339
let i32Stack = [];
2440
let i64Stack = [];
@@ -360,7 +376,7 @@ export async function createInstantiator(options, swift) {
360376
}
361377
bjs["make_swift_closure_TestModule_10TestModulesSS_y"] = function(boxPtr, file, line) {
362378
const lower_closure_TestModule_10TestModulesSS_y = function(param0) {
363-
const param0Bytes = textEncoder.encode(param0);
379+
const param0Bytes = _cachedEncode(param0);
364380
const param0Id = swift.memory.retain(param0Bytes);
365381
instance.exports.invoke_swift_closure_TestModule_10TestModulesSS_y(boxPtr, param0Id, param0Bytes.length);
366382
if (tmpRetException) {

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ 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(str) {
25+
let encoded = _strEncCache.get(str);
26+
if (encoded) {
27+
_strEncCache.delete(str);
28+
_strEncCache.set(str, encoded);
29+
return encoded;
30+
}
31+
encoded = textEncoder.encode(str);
32+
if (_strEncCache.size >= _strEncCacheMax) {
33+
_strEncCache.delete(_strEncCache.keys().next().value);
34+
}
35+
_strEncCache.set(str, encoded);
36+
return encoded;
37+
}
2238
let strStack = [];
2339
let i32Stack = [];
2440
let i64Stack = [];

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

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ 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(str) {
31+
let encoded = _strEncCache.get(str);
32+
if (encoded) {
33+
_strEncCache.delete(str);
34+
_strEncCache.set(str, encoded);
35+
return encoded;
36+
}
37+
encoded = textEncoder.encode(str);
38+
if (_strEncCache.size >= _strEncCacheMax) {
39+
_strEncCache.delete(_strEncCache.keys().next().value);
40+
}
41+
_strEncCache.set(str, encoded);
42+
return encoded;
43+
}
2844
let strStack = [];
2945
let i32Stack = [];
3046
let i64Stack = [];
@@ -331,7 +347,7 @@ export async function createInstantiator(options, swift) {
331347
}
332348

333349
constructor(name) {
334-
const nameBytes = textEncoder.encode(name);
350+
const nameBytes = _cachedEncode(name);
335351
const nameId = swift.memory.retain(nameBytes);
336352
const ret = instance.exports.bjs_DefaultGreeter_init(nameId, nameBytes.length);
337353
return DefaultGreeter.__construct(ret);
@@ -343,7 +359,7 @@ export async function createInstantiator(options, swift) {
343359
return ret;
344360
}
345361
set name(value) {
346-
const valueBytes = textEncoder.encode(value);
362+
const valueBytes = _cachedEncode(value);
347363
const valueId = swift.memory.retain(valueBytes);
348364
instance.exports.bjs_DefaultGreeter_name_set(this.pointer, valueId, valueBytes.length);
349365
}
@@ -364,12 +380,12 @@ export async function createInstantiator(options, swift) {
364380
}
365381

366382
constructor(name = "Default", count = 42, enabled = true, status = StatusValues.Active, tag = null) {
367-
const nameBytes = textEncoder.encode(name);
383+
const nameBytes = _cachedEncode(name);
368384
const nameId = swift.memory.retain(nameBytes);
369385
const isSome = tag != null;
370386
let result, result1;
371387
if (isSome) {
372-
const tagBytes = textEncoder.encode(tag);
388+
const tagBytes = _cachedEncode(tag);
373389
const tagId = swift.memory.retain(tagBytes);
374390
result = tagId;
375391
result1 = tagBytes.length;
@@ -387,7 +403,7 @@ export async function createInstantiator(options, swift) {
387403
return ret;
388404
}
389405
set name(value) {
390-
const valueBytes = textEncoder.encode(value);
406+
const valueBytes = _cachedEncode(value);
391407
const valueId = swift.memory.retain(valueBytes);
392408
instance.exports.bjs_ConstructorDefaults_name_set(this.pointer, valueId, valueBytes.length);
393409
}
@@ -422,7 +438,7 @@ export async function createInstantiator(options, swift) {
422438
const isSome = value != null;
423439
let result, result1;
424440
if (isSome) {
425-
const valueBytes = textEncoder.encode(value);
441+
const valueBytes = _cachedEncode(value);
426442
const valueId = swift.memory.retain(valueBytes);
427443
result = valueId;
428444
result1 = valueBytes.length;
@@ -444,7 +460,7 @@ export async function createInstantiator(options, swift) {
444460
EmptyGreeter,
445461
ConstructorDefaults,
446462
testStringDefault: function bjs_testStringDefault(message = "Hello World") {
447-
const messageBytes = textEncoder.encode(message);
463+
const messageBytes = _cachedEncode(message);
448464
const messageId = swift.memory.retain(messageBytes);
449465
instance.exports.bjs_testStringDefault(messageId, messageBytes.length);
450466
const ret = tmpRetString;
@@ -471,7 +487,7 @@ export async function createInstantiator(options, swift) {
471487
const isSome = name != null;
472488
let result, result1;
473489
if (isSome) {
474-
const nameBytes = textEncoder.encode(name);
490+
const nameBytes = _cachedEncode(name);
475491
const nameId = swift.memory.retain(nameBytes);
476492
result = nameId;
477493
result1 = nameBytes.length;
@@ -488,7 +504,7 @@ export async function createInstantiator(options, swift) {
488504
const isSome = greeting != null;
489505
let result, result1;
490506
if (isSome) {
491-
const greetingBytes = textEncoder.encode(greeting);
507+
const greetingBytes = _cachedEncode(greeting);
492508
const greetingId = swift.memory.retain(greetingBytes);
493509
result = greetingId;
494510
result1 = greetingBytes.length;
@@ -502,7 +518,7 @@ export async function createInstantiator(options, swift) {
502518
return optResult;
503519
},
504520
testMultipleDefaults: function bjs_testMultipleDefaults(title = "Default Title", count = 10, enabled = false) {
505-
const titleBytes = textEncoder.encode(title);
521+
const titleBytes = _cachedEncode(title);
506522
const titleId = swift.memory.retain(titleBytes);
507523
instance.exports.bjs_testMultipleDefaults(titleId, titleBytes.length, count, enabled);
508524
const ret = tmpRetString;
@@ -622,7 +638,7 @@ export async function createInstantiator(options, swift) {
622638
return arrayResult;
623639
},
624640
testMixedWithArrayDefault: function bjs_testMixedWithArrayDefault(name = "test", values = [10, 20, 30], enabled = true) {
625-
const nameBytes = textEncoder.encode(name);
641+
const nameBytes = _cachedEncode(name);
626642
const nameId = swift.memory.retain(nameBytes);
627643
for (const elem of values) {
628644
i32Stack.push((elem | 0));

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ 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(str) {
25+
let encoded = _strEncCache.get(str);
26+
if (encoded) {
27+
_strEncCache.delete(str);
28+
_strEncCache.set(str, encoded);
29+
return encoded;
30+
}
31+
encoded = textEncoder.encode(str);
32+
if (_strEncCache.size >= _strEncCacheMax) {
33+
_strEncCache.delete(_strEncCache.keys().next().value);
34+
}
35+
_strEncCache.set(str, encoded);
36+
return encoded;
37+
}
2238
let strStack = [];
2339
let i32Stack = [];
2440
let i64Stack = [];

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,22 @@ 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(str) {
106+
let encoded = _strEncCache.get(str);
107+
if (encoded) {
108+
_strEncCache.delete(str);
109+
_strEncCache.set(str, encoded);
110+
return encoded;
111+
}
112+
encoded = textEncoder.encode(str);
113+
if (_strEncCache.size >= _strEncCacheMax) {
114+
_strEncCache.delete(_strEncCache.keys().next().value);
115+
}
116+
_strEncCache.set(str, encoded);
117+
return encoded;
118+
}
103119
let strStack = [];
104120
let i32Stack = [];
105121
let i64Stack = [];

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,22 @@ 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(str) {
49+
let encoded = _strEncCache.get(str);
50+
if (encoded) {
51+
_strEncCache.delete(str);
52+
_strEncCache.set(str, encoded);
53+
return encoded;
54+
}
55+
encoded = textEncoder.encode(str);
56+
if (_strEncCache.size >= _strEncCacheMax) {
57+
_strEncCache.delete(_strEncCache.keys().next().value);
58+
}
59+
_strEncCache.set(str, encoded);
60+
return encoded;
61+
}
4662
let strStack = [];
4763
let i32Stack = [];
4864
let i64Stack = [];

0 commit comments

Comments
 (0)