diff --git a/gs/reflect/type.ts b/gs/reflect/type.ts index ed71e4079..856a0eb88 100644 --- a/gs/reflect/type.ts +++ b/gs/reflect/type.ts @@ -300,6 +300,10 @@ function convertValue(x: ReflectValue, ft: Type, t: Type): ReflectValue { } function internType(t: Type): Type { + // A canonical type interns to itself, so it needs no identity key. + if (internedTypes.has(t)) { + return t + } const key = typeIdentityKey(t) $.markReflectTypeIdentity(t, key) const existing = canonicalTypes.get(key) @@ -309,9 +313,13 @@ function internType(t: Type): Type { return existing } canonicalTypes.set(key, t) + internedTypes.add(t) return t } +// internedTypes holds the canonical types, which internType returned. +const internedTypes = new WeakSet() + function mergeTypeMetadata(target: Type, source: Type): void { if (target instanceof BasicType && source instanceof BasicType) { target.mergeMethodSignatures(source.methodSignatures()) @@ -805,14 +813,15 @@ export class Value { } public clone(): Value { - const cloned = new Value( - this._value, - this._type, - this._parentVarRef, - this._parentStruct, - this._fieldName, - this._interfaceValue, - ) + // The fields of a constructed Value are already normalized, so the copy + // takes them as they are rather than checking them again. + const cloned = new Value() + cloned._value = this._value + cloned._type = this._type + cloned._parentVarRef = this._parentVarRef + cloned._parentStruct = this._parentStruct + cloned._fieldName = this._fieldName + cloned._interfaceValue = this._interfaceValue cloned._addressable = this._addressable return cloned } @@ -5575,13 +5584,29 @@ export function ArrayOf(length: number, elem: Type): Type { return internType(new ArrayType(elem, length)) } +// sliceTypes and pointerTypes cache the slice and pointer type of each +// type, which SliceOf and PointerTo would otherwise intern again on every +// call, as reflect.New does. +const sliceTypes = new WeakMap() +const pointerTypes = new WeakMap() + export function SliceOf(t: Type): Type { - return internType(new SliceType(t)) + let st = sliceTypes.get(t) + if (st === undefined) { + st = internType(new SliceType(t)) + sliceTypes.set(t, st) + } + return st } export function PointerTo(t: Type | null): Type | null { if (t === null) return null - return internType(new PointerType(t)) + let pt = pointerTypes.get(t) + if (pt === undefined) { + pt = internType(new PointerType(t)) + pointerTypes.set(t, pt) + } + return pt } export function PtrTo(t: Type | null): Type | null { diff --git a/gs/reflect/value.ts b/gs/reflect/value.ts index 07bd90723..c829e0d38 100644 --- a/gs/reflect/value.ts +++ b/gs/reflect/value.ts @@ -109,22 +109,32 @@ function newStructPointer(typ: Type): ReflectValue { // newStructValue returns a new zero struct of the type typ, which has no // registered constructor, such as an anonymous struct type. function newStructValue(typ: Type): ReflectValue { - type StructValueInstance = Record + const StructValue = structValueClass(typ) + return new StructValue() as ReflectValue +} - // The fields are own properties of the struct object, starting at zero. - const initFields = (value: StructValueInstance): void => { - for (let i = 0; i < typ.NumField(); i++) { - const field = typ.Field(i) - const key = structFieldStorageKey(typ, i) - value[key] = zeroReflectValue(field.Type!) - } +type StructValueInstance = Record + +// structValueClasses caches the class of the values of each struct type +// without a registered constructor, so its values share one shape. +const structValueClasses = new WeakMap StructValueInstance>() + +// structValueClass returns the class of the values of the struct type typ. +// Like a generated struct class, its constructor makes a zero value. +function structValueClass(typ: Type): new () => StructValueInstance { + const cached = structValueClasses.get(typ) + if (cached !== undefined) { + return cached } // A clone copies each field. const StructValue = class { + constructor() { + initStructFields(typ, this as unknown as StructValueInstance) + } + public clone(): unknown { const cloned = new StructValue() as unknown as StructValueInstance - initFields(cloned) for (const key of Object.keys(this)) { cloned[key] = (this as unknown as StructValueInstance)[key] } @@ -135,10 +145,19 @@ function newStructValue(typ: Type): ReflectValue { Object.defineProperty(StructValue, '__typeInfo', { value: typeInfoFromReflectType(typ), }) + const ctor = StructValue as unknown as new () => StructValueInstance + structValueClasses.set(typ, ctor) + return ctor +} - const value = new StructValue() as unknown as StructValueInstance - initFields(value) - return value as ReflectValue +// initStructFields sets the fields of the struct value of type typ to zero, +// as own properties of the struct object. +function initStructFields(typ: Type, value: StructValueInstance): void { + for (let i = 0; i < typ.NumField(); i++) { + const field = typ.Field(i) + const key = structFieldStorageKey(typ, i) + value[key] = zeroReflectValue(field.Type!) + } } // Copy copies the contents of src to dst until either dst has been filled