diff --git a/gs/builtin/builtin.ts b/gs/builtin/builtin.ts index 8f5537a97..228bfc72d 100644 --- a/gs/builtin/builtin.ts +++ b/gs/builtin/builtin.ts @@ -255,22 +255,29 @@ export function genericPointerRef( * variable's address. */ export function pointerValue(value: T | VarRef | null | undefined): T { + // A variable reference holds its value, and a struct that is not a boxed + // pointer is its own target. These property reads are much faster than + // the type and 'in' checks of pointerValueSlow, on the many kinds of + // objects seen here, and keep this function small enough to be inlined. + if (value !== null && value !== undefined) { + if ((value as any).__isVarRef === true) { + return (value as VarRef).value + } + if ((value as any).__goValue === undefined) { + return value as T + } + } + return pointerValueSlow(value) +} + +// pointerValueSlow is pointerValue for nil and boxed pointers. +function pointerValueSlow(value: T | VarRef | null | undefined): T { if (value === null || value === undefined) { runtimePanic( 'runtime error: invalid memory address or nil pointer dereference', ) } - // A variable reference holds its value, and a struct that is not a boxed - // pointer is its own target. These property reads are much faster than - // the type and 'in' checks below, on the many kinds of objects seen here. - if ((value as any).__isVarRef === true) { - return (value as VarRef).value - } - if ((value as any).__goValue === undefined) { - return value as T - } - // A boxed pointer is unwrapped to its target. const boxed = typeof value === 'object' && value !== null && '__goValue' in value ? diff --git a/gs/builtin/slice.ts b/gs/builtin/slice.ts index 6a34952b9..fcbcf7c2d 100644 --- a/gs/builtin/slice.ts +++ b/gs/builtin/slice.ts @@ -123,6 +123,22 @@ export function sliceRange( ): SliceRange export function sliceRange( slice: Slice | TypedSlice | null | undefined, +): SliceRange | SliceRange { + // A window object is its own window, and a plain array is a window over + // itself. They are handled here, in a function small enough for engines + // to inline, which also lets them avoid allocating the window of an array. + if (slice instanceof GoSlice) { + return slice + } + if (Array.isArray(slice) && (slice as any).__meta__ === undefined) { + return { backing: slice, offset: 0, length: slice.length } + } + return sliceRangeSlow(slice) +} + +// sliceRangeSlow is sliceRange for nil, typed and proxied slices. +function sliceRangeSlow( + slice: Slice | TypedSlice | null | undefined, ): SliceRange | SliceRange { // A nil slice has no elements. if (slice === null || slice === undefined) { @@ -1325,25 +1341,29 @@ export function arrayToSlice( export function sliceLen( slice: Slice | unknown[] | TypedSlice | null | undefined, ): number { - // A nil slice has no elements. - if (slice === null || slice === undefined) { - return 0 - } - - // A window object and a plain array are the common cases, tested first - // without reading __meta__, which is slow on the many kinds of objects - // seen here. An array proxy (a window viewed as a Go array) has __meta__. + // A window object, a plain array and a typed slice are the common cases, + // tested here without reading __meta__, which is slow on the many kinds of + // objects seen here, in a function small enough for engines to inline. An + // array proxy (a window viewed as a Go array) has __meta__. if (slice instanceof GoSlice) { return slice.length } + if (ArrayBuffer.isView(slice)) { + return (slice as TypedSlice).length + } if (Array.isArray(slice) && (slice as any).__meta__ === undefined) { return slice.length } + return sliceLenSlow(slice) +} - // A typed slice is a view of exactly its elements, whose window, if any, - // has the same length. - if (isTypedSlice(slice)) { - return slice.length +// sliceLenSlow is sliceLen for nil slices, proxies and windowed arrays. +function sliceLenSlow( + slice: Slice | unknown[] | TypedSlice | null | undefined, +): number { + // A nil slice has no elements. + if (slice === null || slice === undefined) { + return 0 } // A slice proxy knows its length. @@ -2332,6 +2352,36 @@ export function arrayIndex< | TypedSlice | null | undefined, +>(collection: C, index: number): ArrayIndexValue { + // An index in range of a window object, a typed slice or a plain array is + // read here, in a function small enough for engines to inline into hot + // loops; arrayIndexSlow handles the other representations and panics. + if (collection instanceof GoSlice) { + if (index >= 0 && index < collection.length) { + return collection.backing[collection.offset + index] as ArrayIndexValue + } + } else if (ArrayBuffer.isView(collection)) { + if (index >= 0 && index < (collection as TypedSlice).length) { + return (collection as TypedSlice)[index] as ArrayIndexValue + } + } else if (Array.isArray(collection) && (collection as any).__meta__ === undefined) { + if (index >= 0 && index < collection.length) { + return collection[index] as ArrayIndexValue + } + } + return arrayIndexSlow(collection, index) +} + +// arrayIndexSlow is arrayIndex for the representations and indexes that its +// fast path does not handle. +function arrayIndexSlow< + C extends + | Slice + | SliceProxy + | unknown[] + | TypedSlice + | null + | undefined, >(collection: C, index: number): ArrayIndexValue { if (collection === null || collection === undefined) { outOfRangeIndex(index, 0) diff --git a/gs/builtin/type.ts b/gs/builtin/type.ts index b953c48d2..31fd9f796 100644 --- a/gs/builtin/type.ts +++ b/gs/builtin/type.ts @@ -1948,10 +1948,16 @@ function markClonedStructValue(copy: T): T { // value unchanged. A marked struct with no clone method is returned unchanged // rather than panicking. export function copyElement(value: T): T { - // A value that is not an object is copied by value. + // A value that is not an object is copied by value, here in a function + // small enough for engines to inline; copyObjectElement copies the others. if (typeof value !== 'object' || value === null) { return value } + return copyObjectElement(value) +} + +// copyObjectElement is copyElement for a value that is an object. +function copyObjectElement(value: T & object): T { // A struct value of a class is copied as its class copies it. if ((value as any)[structValueMarker] === true) {