@@ -216,12 +216,23 @@ enum ContainerCodecJS {
216216 private static let primitiveCodecIntrinsicName = " containerPrimitiveCodecs "
217217
218218 /// The single description of each container shape's stack ABI.
219+ ///
220+ /// The combinators memoize per element codec object. Statically known
221+ /// compositions are hoisted into module-scope `const`s and so instantiate a
222+ /// combinator only once, but a generic call site resolves its element codec
223+ /// from a runtime type ID and cannot be hoisted; memoizing keeps those call
224+ /// sites from allocating a fresh codec on every call.
219225 static func combinatorDeclarations( ) -> [ String ] {
220226 let i32 = JSGlueVariableScope . reservedI32Stack
221227 let stringCodec = JSGlueVariableScope . reservedStringCodec
222228 return [
229+ " const \( arrayCodec) Cache = new WeakMap(); " ,
223230 " function \( arrayCodec) (elementCodec) { " ,
224- " return { " ,
231+ " let codec = \( arrayCodec) Cache.get(elementCodec); " ,
232+ " if (codec !== undefined) { " ,
233+ " return codec; " ,
234+ " } " ,
235+ " codec = { " ,
225236 " lower(value) { " ,
226237 " for (let i = 0; i < value.length; i++) { " ,
227238 " elementCodec.lower(value[i]); " ,
@@ -240,11 +251,22 @@ enum ContainerCodecJS {
240251 " return result; " ,
241252 " }, " ,
242253 " }; " ,
254+ " \( arrayCodec) Cache.set(elementCodec, codec); " ,
255+ " return codec; " ,
243256 " } " ,
244257 // `isUndefinedOr` selects the `JSUndefinedOr` flavor: `null` is then a
245258 // present value and absence surfaces as `undefined` instead of `null`.
259+ // The two flavors are cached separately because they differ in
260+ // behavior, not just in the element codec.
261+ " const \( optionalCodec) Cache = new WeakMap(); " ,
262+ " const \( optionalCodec) UndefinedOrCache = new WeakMap(); " ,
246263 " function \( optionalCodec) (elementCodec, isUndefinedOr = false) { " ,
247- " return { " ,
264+ " const cache = isUndefinedOr ? \( optionalCodec) UndefinedOrCache : \( optionalCodec) Cache; " ,
265+ " let codec = cache.get(elementCodec); " ,
266+ " if (codec !== undefined) { " ,
267+ " return codec; " ,
268+ " } " ,
269+ " codec = { " ,
248270 " lower(value) { " ,
249271 " const isSome = isUndefinedOr ? value !== undefined : value != null; " ,
250272 " if (isSome) { " ,
@@ -261,9 +283,16 @@ enum ContainerCodecJS {
261283 " return elementCodec.lift(); " ,
262284 " }, " ,
263285 " }; " ,
286+ " cache.set(elementCodec, codec); " ,
287+ " return codec; " ,
264288 " } " ,
289+ " const \( dictCodec) Cache = new WeakMap(); " ,
265290 " function \( dictCodec) (valueCodec) { " ,
266- " return { " ,
291+ " let codec = \( dictCodec) Cache.get(valueCodec); " ,
292+ " if (codec !== undefined) { " ,
293+ " return codec; " ,
294+ " } " ,
295+ " codec = { " ,
267296 " lower(value) { " ,
268297 " const keys = Object.keys(value); " ,
269298 " for (let i = 0; i < keys.length; i++) { " ,
@@ -283,6 +312,8 @@ enum ContainerCodecJS {
283312 " return result; " ,
284313 " }, " ,
285314 " }; " ,
315+ " \( dictCodec) Cache.set(valueCodec, codec); " ,
316+ " return codec; " ,
286317 " } " ,
287318 ]
288319 }
0 commit comments