From 32061e8ba3c5ad4e3cf16b213ba0d9c56131be03 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 17 Oct 2026 22:45:32 -0500 Subject: [PATCH 1/4] Add Float16Array Float16Array is a native typed-array kind with IEEE 754 binary16 storage. Stores round the number to nearest-even in one step from the double, as Node does, including overflow to Infinity, subnormals, -0 and NaN; reads widen exactly. The runtime converts with integer operations, so no target needs half-precision hardware or compiler-rt support. Construction, element reads and writes, ArrayBuffer views, fill, set, subarray, slice, both sort orders, constructor values and checked-dynamic storage work as for the other typed arrays. Math.f16round, DataView.prototype.getFloat16 and setFloat16, and util.types.isFloat16Array compile. --- .../arraybuffers-and-typed-arrays.mdx | 2 +- .../ambient/scriptc-node-fallback.d.ts | 1 + .../compiler/src/backend/llvm/byte-numbers.ts | 11 +- packages/compiler/src/backend/llvm/common.ts | 1 + .../compiler/src/backend/llvm/expr-bytes.ts | 29 ++++- .../src/backend/llvm/lib-filesystem.ts | 10 ++ .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../lowering/containers/array-pipelines.ts | 1 + .../src/frontend/lowering/containers/bytes.ts | 6 +- .../src/frontend/lowering/lower-assert.ts | 1 + .../src/frontend/lowering/nan-coded-reads.ts | 1 + .../frontend/lowering/narrowing-stability.ts | 1 + .../src/frontend/lowering/surfaces.ts | 3 + packages/compiler/src/frontend/type-mapper.ts | 1 + packages/compiler/src/ir/analysis.ts | 1 + packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 2 + packages/compiler/src/ir/byte-numbers.ts | 3 + packages/compiler/src/ir/int32-slots.ts | 1 + packages/compiler/src/ir/ir.ts | 24 +++- .../tests/corpus/5900-float16-array.ts.json | 6 + .../corpus/5901-float16-array-js.js.json | 6 + packages/runtime/src/scr_array.c | 37 +++++- packages/runtime/src/scr_assert.c | 3 +- packages/runtime/src/scr_bytes.c | 79 +++++++++++- packages/runtime/src/scr_bytes.h | 15 ++- packages/runtime/src/scr_dyn_invoke.c | 6 +- packages/runtime/src/scr_json.c | 18 +-- tests/corpus/5900-float16-array.ts | 94 ++++++++++++++ tests/corpus/5901-float16-array-js.js | 95 ++++++++++++++ tests/diagnostics/dataview.ts | 4 +- tests/diagnostics/es2025-fences.ts | 13 +- tests/harness/__snapshots__/dataview.ts.txt | 19 +-- .../__snapshots__/es2025-fences.ts.txt | 118 ++++++------------ tests/harness/corpus-costs.json | 4 + 35 files changed, 489 insertions(+), 129 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5900-float16-array.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5901-float16-array-js.js.json create mode 100644 tests/corpus/5900-float16-array.ts create mode 100644 tests/corpus/5901-float16-array-js.js diff --git a/docs/content/limitations/builtin-apis/arraybuffers-and-typed-arrays.mdx b/docs/content/limitations/builtin-apis/arraybuffers-and-typed-arrays.mdx index 81dfded25..59a61518d 100644 --- a/docs/content/limitations/builtin-apis/arraybuffers-and-typed-arrays.mdx +++ b/docs/content/limitations/builtin-apis/arraybuffers-and-typed-arrays.mdx @@ -1 +1 @@ -- **ArrayBuffers and typed arrays:** Fixed-length ArrayBuffers support shared typed-array and DataView storage. Resizing, transferring, Float16 arrays, and BigInt arrays are unsupported. Numeric typed arrays and Buffers preserve their kind and shared storage through `unknown`, including when nested in records and arrays. Numeric typed-array constructors can be stored and used with `new`, including constructors read from an array's `constructor` property. JavaScript numeric-array parameters and returns preserve typed-array storage. `Buffer.from`, `Buffer.allocUnsafe`, and `Buffer.concat` return views into a per-thread pool of `Buffer.poolSize` bytes (64 KiB by default) when the size is below `Buffer.poolSize >>> 1`, as in Node; a pool slab stays in memory while any of its Buffers does. Node's own startup code allocates from the first slab, so the `byteOffset` of Buffers in that slab differs from Node's. Buffers returned by other Node APIs, such as `fs.readFileSync` and `zlib`, own their `ArrayBuffer`, where Node allocates small ones from the pool. +- **ArrayBuffers and typed arrays:** Fixed-length ArrayBuffers support shared typed-array and DataView storage. Resizing, transferring, and BigInt arrays are unsupported. `Float16Array` stores IEEE 754 half-precision values, rounding each stored number to nearest-even as Node does; `Math.f16round`, `DataView.prototype.getFloat16` and `setFloat16`, and `util.types.isFloat16Array` compile. Numeric typed arrays and Buffers preserve their kind and shared storage through `unknown`, including when nested in records and arrays. Numeric typed-array constructors can be stored and used with `new`, including constructors read from an array's `constructor` property. JavaScript numeric-array parameters and returns preserve typed-array storage. `Buffer.from`, `Buffer.allocUnsafe`, and `Buffer.concat` return views into a per-thread pool of `Buffer.poolSize` bytes (64 KiB by default) when the size is below `Buffer.poolSize >>> 1`, as in Node; a pool slab stays in memory while any of its Buffers does. Node's own startup code allocates from the first slab, so the `byteOffset` of Buffers in that slab differs from Node's. Buffers returned by other Node APIs, such as `fs.readFileSync` and `zlib`, own their `ArrayBuffer`, where Node allocates small ones from the pool. diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index d87172f61..064dccc77 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -3250,6 +3250,7 @@ declare module "util/types" { export function isInt8Array(value: unknown): value is Int8Array; export function isInt16Array(value: unknown): value is Int16Array; export function isInt32Array(value: unknown): value is Int32Array; + export function isFloat16Array(value: unknown): value is Float16Array; export function isFloat32Array(value: unknown): value is Float32Array; export function isFloat64Array(value: unknown): value is Float64Array; export function isMap(value: unknown): value is Map; diff --git a/packages/compiler/src/backend/llvm/byte-numbers.ts b/packages/compiler/src/backend/llvm/byte-numbers.ts index d12ef08b9..c51d03744 100644 --- a/packages/compiler/src/backend/llvm/byte-numbers.ts +++ b/packages/compiler/src/backend/llvm/byte-numbers.ts @@ -182,7 +182,11 @@ export function emitByteNumber( const integerType = bits <= 32 ? "i32" : "i64"; if (spec.write) { let raw: string; - if (spec.floating) { + if (spec.floating && bits === 16) { + host.declare(`declare zeroext i16 @scr_f16_from_f64(double)`); + raw = B.tmp(); + B.line(`${raw} = call zeroext i16 @scr_f16_from_f64(double ${value!.name})`); + } else if (spec.floating) { let stored = value!.name; if (bits === 32) { stored = B.tmp(); @@ -228,7 +232,10 @@ export function emitByteNumber( const raw = endianBits(host, loaded, spec.width, little); fastResult = B.tmp(); if (spec.floating) { - if (bits === 32) { + if (bits === 16) { + host.declare(`declare double @scr_f16_to_f64(i16 zeroext)`); + B.line(`${fastResult} = call double @scr_f16_to_f64(i16 zeroext ${raw})`); + } else if (bits === 32) { const float = B.tmp(); B.line(`${float} = bitcast i32 ${raw} to float`); B.line(`${fastResult} = fpext float ${float} to double`); diff --git a/packages/compiler/src/backend/llvm/common.ts b/packages/compiler/src/backend/llvm/common.ts index 0a473e65b..8a8778919 100644 --- a/packages/compiler/src/backend/llvm/common.ts +++ b/packages/compiler/src/backend/llvm/common.ts @@ -159,6 +159,7 @@ export const BYTES_ELEM_NUM: Record = { u16: 6, i16: 7, u8c: 8, + f16: 9, }; /** The payload marker of an ABSENT record field slot (IR fieldAbsent): the diff --git a/packages/compiler/src/backend/llvm/expr-bytes.ts b/packages/compiler/src/backend/llvm/expr-bytes.ts index 89a482b52..ac025df09 100644 --- a/packages/compiler/src/backend/llvm/expr-bytes.ts +++ b/packages/compiler/src/backend/llvm/expr-bytes.ts @@ -44,6 +44,7 @@ const DV_GET_KIND: Record = { dvGetFloat64: 7, dvGetBigUint64Number: 8, dvGetBigInt64Number: 9, + dvGetFloat16: 10, }; const DV_SET_KIND: Record = { @@ -55,6 +56,7 @@ const DV_SET_KIND: Record = { dvSetInt32: 5, dvSetFloat32: 6, dvSetFloat64: 7, + dvSetFloat16: 10, }; export function emitIntegerLoopIndex(host: LlvmEmitterContext, expr: IrExpr): string | null { @@ -273,7 +275,7 @@ function emitSharedOrLocalLoad( idx: string, ): LlValue { const B = host.B; - const floating = elem === "f32" || elem === "f64"; + const floating = elem === "f16" || elem === "f32" || elem === "f64"; const fn = floating ? "scr_bytes_shared_get_f64" : "scr_bytes_shared_get_int"; const ret = floating ? "double" : "i32"; host.declare(`declare ${ret} @${fn}(ptr, ${host.sizeType})`); @@ -314,6 +316,16 @@ function emitBytesLoadAt( const B = host.B; const data = host.emitBytesData(receiver); const p = B.tmp(); + if (elem === "f16") { + // Half-precision bits widen exactly; the runtime converts them. + host.declare(`declare double @scr_f16_to_f64(i16 zeroext)`); + const raw = B.tmp(); + const out = B.tmp(); + B.line(`${p} = getelementptr inbounds i16, ptr ${data}, ${host.sizeType} ${idx}`); + B.line(`${raw} = load i16, ptr ${p}, align 1`); + B.line(`${out} = call double @scr_f16_to_f64(i16 zeroext ${raw})`); + return { name: out, type: F64 }; + } if (BYTES_ELEMENT_SIZE[elem] < 4) { const bits = BYTES_ELEMENT_SIZE[elem] * 8; const signed = elem === "i8" || elem === "i16"; @@ -472,10 +484,19 @@ export function emitBytesSet( B.line(`${stored} = fptoui double ${clamped} to i32`); } else stored = - elem === "f32" || elem === "f64" ? null : host.emitToUint32(value.name, expr, value.uint32); + elem === "f16" || elem === "f32" || elem === "f64" + ? null + : host.emitToUint32(value.name, expr, value.uint32); const data = host.emitBytesData(receiver); const p = B.tmp(); - if (BYTES_ELEMENT_SIZE[elem] < 4) { + if (elem === "f16") { + // One round-to-nearest-even step from the double, in the runtime. + host.declare(`declare zeroext i16 @scr_f16_from_f64(double)`); + const narrowed = B.tmp(); + B.line(`${narrowed} = call zeroext i16 @scr_f16_from_f64(double ${value.name})`); + B.line(`${p} = getelementptr inbounds i16, ptr ${data}, ${host.sizeType} ${idx}`); + B.line(`store i16 ${narrowed}, ptr ${p}, align 1`); + } else if (BYTES_ELEMENT_SIZE[elem] < 4) { const bits = BYTES_ELEMENT_SIZE[elem] * 8; const byte = B.tmp(); B.line(`${byte} = trunc i32 ${stored!} to i${bits}`); @@ -882,6 +903,7 @@ export function emitBytesIntrinsic( case "dvGetInt16": case "dvGetUint32": case "dvGetInt32": + case "dvGetFloat16": case "dvGetFloat32": case "dvGetFloat64": case "dvGetBigUint64Number": @@ -912,6 +934,7 @@ export function emitBytesIntrinsic( case "dvSetInt16": case "dvSetUint32": case "dvSetInt32": + case "dvSetFloat16": case "dvSetFloat32": case "dvSetFloat64": { // DataView setters: [offset, value, littleEndian?] — void; throw diff --git a/packages/compiler/src/backend/llvm/lib-filesystem.ts b/packages/compiler/src/backend/llvm/lib-filesystem.ts index 1083c69d5..d15bf39e7 100644 --- a/packages/compiler/src/backend/llvm/lib-filesystem.ts +++ b/packages/compiler/src/backend/llvm/lib-filesystem.ts @@ -1037,6 +1037,16 @@ export function emitPrimitiveLibCall(host: LlvmEmitterContext, e: LibCallExpr): B.line(`${t} = sitofp i32 ${product} to double`); return { name: t, type: e.type, uint32: product }; } + if (e.fn === "math.f16round") { + const v = host.emitExpr(e.args[0]!); + host.declare(`declare zeroext i16 @scr_f16_from_f64(double)`); + host.declare(`declare double @scr_f16_to_f64(i16 zeroext)`); + const narrowed = B.tmp(); + const t = B.tmp(); + B.line(`${narrowed} = call zeroext i16 @scr_f16_from_f64(double ${v.name})`); + B.line(`${t} = call double @scr_f16_to_f64(i16 zeroext ${narrowed})`); + return { name: t, type: e.type }; + } if (e.fn === "math.fround") { const v = host.emitExpr(e.args[0]!); const narrowed = B.tmp(); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 9232cbd03..7b3566383 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -65,6 +65,7 @@ export const LIB_FN_SYMS: Record = { "arrayBuffer.viewU8": "scr_array_buffer_view_u8", "arrayBuffer.viewU32": "scr_array_buffer_view_u32", "arrayBuffer.viewI32": "scr_array_buffer_view_i32", + "arrayBuffer.viewF16": "scr_array_buffer_view_f16", "arrayBuffer.viewF32": "scr_array_buffer_view_f32", "arrayBuffer.viewF64": "scr_array_buffer_view_f64", "arrayBuffer.viewDV": "scr_array_buffer_view_dv", diff --git a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts index 0ceb4d677..cf45a7575 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts @@ -108,6 +108,7 @@ const PURE_MATH = new Set([ "math.exp", "math.expm1", "math.floor", + "math.f16round", "math.fround", "math.imul", "math.log", diff --git a/packages/compiler/src/frontend/lowering/containers/bytes.ts b/packages/compiler/src/frontend/lowering/containers/bytes.ts index b9400cbbd..7a19927de 100644 --- a/packages/compiler/src/frontend/lowering/containers/bytes.ts +++ b/packages/compiler/src/frontend/lowering/containers/bytes.ts @@ -151,7 +151,7 @@ function lowerBytesSortCall( /* ── typed arrays / Buffer ─────────────────────────────────────────────── */ /** The typed-array constructors with a runtime representation, by lib - * interface name. Other flavors (Float16Array, BigInt64Array, ...) fall through + * interface name. Other flavors (BigInt64Array, BigUint64Array) fall through * to the generic stdlib-constructor fence. DataView is handled separately. */ const BYTES_CTORS: Record = { Uint8Array: "u8", @@ -161,6 +161,7 @@ const BYTES_CTORS: Record = { Int16Array: "i16", Uint32Array: "u32", Int32Array: "i32", + Float16Array: "f16", Float32Array: "f32", Float64Array: "f64", }; @@ -397,6 +398,7 @@ function lowerArrayBufferView( u8: "arrayBuffer.viewU8", u32: "arrayBuffer.viewU32", i32: "arrayBuffer.viewI32", + f16: "arrayBuffer.viewF16", f32: "arrayBuffer.viewF32", f64: "arrayBuffer.viewF64", dv: "arrayBuffer.viewDV", @@ -1220,6 +1222,7 @@ const DV_GETTERS: Record = new Set([ "Int8Array", "Int16Array", "Int32Array", + "Float16Array", "Float32Array", "Float64Array", "BigInt64Array", diff --git a/packages/compiler/src/frontend/lowering/nan-coded-reads.ts b/packages/compiler/src/frontend/lowering/nan-coded-reads.ts index 2a537d6de..f7023cc52 100644 --- a/packages/compiler/src/frontend/lowering/nan-coded-reads.ts +++ b/packages/compiler/src/frontend/lowering/nan-coded-reads.ts @@ -191,6 +191,7 @@ const TYPED_ARRAYS = new Set([ "Uint16Array", "Int32Array", "Uint32Array", + "Float16Array", "Float32Array", "Float64Array", ]); diff --git a/packages/compiler/src/frontend/lowering/narrowing-stability.ts b/packages/compiler/src/frontend/lowering/narrowing-stability.ts index 9c7850ceb..4ae8eec67 100644 --- a/packages/compiler/src/frontend/lowering/narrowing-stability.ts +++ b/packages/compiler/src/frontend/lowering/narrowing-stability.ts @@ -834,6 +834,7 @@ const INERT_CONSTRUCTORS = new Set([ "Int16Array", "Uint32Array", "Int32Array", + "Float16Array", "Float32Array", "Float64Array", "BigInt64Array", diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 4c74e8b6a..91059ce0d 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -664,6 +664,7 @@ export const STDLIB_SURFACE = { cosh: ISL_N1, exp: ISL_N1, expm1: ISL_N1, + f16round: ISL_N1, fround: ISL_N1, hypot: ISL_N2, imul: ISL_N2, @@ -751,6 +752,7 @@ export const STATIC_MATH_FNS: Record([ "math.sign", "math.exp", "math.expm1", + "math.f16round", "math.fround", "math.log", "math.log1p", diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 2381413f8..5dd4f9956 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -73,6 +73,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "arrayBuffer.viewU8", "arrayBuffer.viewU32", "arrayBuffer.viewI32", + "arrayBuffer.viewF16", "arrayBuffer.viewF32", "arrayBuffer.viewF64", "arrayBuffer.viewDV", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 5e219ecf6..a2849a244 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -644,6 +644,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "math.sign": { argTypes: [F64], result: F64 }, "math.exp": { argTypes: [F64], result: F64 }, "math.expm1": { argTypes: [F64], result: F64 }, + "math.f16round": { argTypes: [F64], result: F64 }, "math.fround": { argTypes: [F64], result: F64 }, "math.sqrt": { argTypes: [F64], result: F64 }, "math.log": { argTypes: [F64], result: F64 }, @@ -3159,6 +3160,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "arrayBuffer.viewU8": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, "arrayBuffer.viewU32": { argTypes: [DYN, DYN, DYN], result: bytesOf("u32") }, "arrayBuffer.viewI32": { argTypes: [DYN, DYN, DYN], result: bytesOf("i32") }, + "arrayBuffer.viewF16": { argTypes: [DYN, DYN, DYN], result: bytesOf("f16") }, "arrayBuffer.viewF32": { argTypes: [DYN, DYN, DYN], result: bytesOf("f32") }, "arrayBuffer.viewF64": { argTypes: [DYN, DYN, DYN], result: bytesOf("f64") }, "arrayBuffer.viewDV": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, diff --git a/packages/compiler/src/ir/byte-numbers.ts b/packages/compiler/src/ir/byte-numbers.ts index a6a079af6..89ffaaa98 100644 --- a/packages/compiler/src/ir/byte-numbers.ts +++ b/packages/compiler/src/ir/byte-numbers.ts @@ -25,6 +25,7 @@ const FORMATS: Readonly> = { i16: { width: 2, signed: true, floating: false }, u32: { width: 4, signed: false, floating: false }, i32: { width: 4, signed: true, floating: false }, + f16: { width: 2, signed: false, floating: true }, f32: { width: 4, signed: false, floating: true }, f64: { width: 8, signed: false, floating: true }, u64: { width: 8, signed: false, floating: false }, @@ -38,6 +39,7 @@ const VIEW_FORMATS: Readonly> = { dvGetInt16: "i16", dvGetUint32: "u32", dvGetInt32: "i32", + dvGetFloat16: "f16", dvGetFloat32: "f32", dvGetFloat64: "f64", dvGetBigUint64Number: "u64", @@ -48,6 +50,7 @@ const VIEW_FORMATS: Readonly> = { dvSetInt16: "i16", dvSetUint32: "u32", dvSetInt32: "i32", + dvSetFloat16: "f16", dvSetFloat32: "f32", dvSetFloat64: "f64", }; diff --git a/packages/compiler/src/ir/int32-slots.ts b/packages/compiler/src/ir/int32-slots.ts index 5e868793c..a6a10eaa7 100644 --- a/packages/compiler/src/ir/int32-slots.ts +++ b/packages/compiler/src/ir/int32-slots.ts @@ -336,6 +336,7 @@ const BOUNDARY_KINDS = new Set([ * property, except literal-keyed `dyn.keySet`/`dyn.keyDelete`. */ const DYN_READ_ONLY = new Set([ "arrayBuffer.new", + "arrayBuffer.viewF16", "arrayBuffer.viewF32", "arrayBuffer.viewF64", "arrayBuffer.viewI16", diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index bfaa13c0e..90c9bd618 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -26,9 +26,19 @@ export interface SrcLoc { /* ── types ─────────────────────────────────────────────────────────────── */ -/** Numeric typed-array element kinds with a native representation. Float16 - * and BigInt typed arrays remain frontend-fenced. */ -export type IrBytesElem = "u8" | "u8c" | "i8" | "u16" | "i16" | "u32" | "i32" | "f32" | "f64"; +/** Numeric typed-array element kinds with a native representation. BigInt + * typed arrays remain frontend-fenced. */ +export type IrBytesElem = + | "u8" + | "u8c" + | "i8" + | "u16" + | "i16" + | "u32" + | "i32" + | "f16" + | "f32" + | "f64"; export const BYTES_ELEMENT_SIZE: Record = { u8: 1, @@ -38,13 +48,14 @@ export const BYTES_ELEMENT_SIZE: Record = { i16: 2, u32: 4, i32: 4, + f16: 2, f32: 4, f64: 8, }; /** Integer element kinds: a valid element read is never NaN. */ export function isIntegerBytesElem(elem: IrBytesElem): boolean { - return elem !== "f32" && elem !== "f64"; + return elem !== "f16" && elem !== "f32" && elem !== "f64"; } export const BYTES_ELEMENT_NAME: Record = { @@ -55,6 +66,7 @@ export const BYTES_ELEMENT_NAME: Record = { i16: "Int16Array", u32: "Uint32Array", i32: "Int32Array", + f16: "Float16Array", f32: "Float32Array", f64: "Float64Array", }; @@ -2227,6 +2239,7 @@ export type IrBytesIntrinsicMethod = | "dvGetInt16" | "dvGetUint32" | "dvGetInt32" + | "dvGetFloat16" | "dvGetFloat32" | "dvGetFloat64" | "dvGetBigUint64Number" @@ -2237,6 +2250,7 @@ export type IrBytesIntrinsicMethod = | "dvSetInt16" | "dvSetUint32" | "dvSetInt32" + | "dvSetFloat16" | "dvSetFloat32" | "dvSetFloat64"; @@ -2268,6 +2282,7 @@ export const MAY_THROW_BYTES_METHODS: ReadonlySet = new "dvGetInt16", "dvGetUint32", "dvGetInt32", + "dvGetFloat16", "dvGetFloat32", "dvGetFloat64", "dvGetBigUint64Number", @@ -2278,6 +2293,7 @@ export const MAY_THROW_BYTES_METHODS: ReadonlySet = new "dvSetInt16", "dvSetUint32", "dvSetInt32", + "dvSetFloat16", "dvSetFloat32", "dvSetFloat64", ]); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5900-float16-array.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5900-float16-array.ts.json new file mode 100644 index 000000000..47cf8723f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5900-float16-array.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5900-float16-array.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5901-float16-array-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5901-float16-array-js.js.json new file mode 100644 index 000000000..148849dca --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5901-float16-array-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5901-float16-array-js.js" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index eba0367f7..ef64be56d 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -3111,6 +3111,11 @@ static double scr_bytes_sort_load(const ScrBytes *b, size_t i) { memcpy(&v, at, sizeof v); return v; } + case SCR_BYTES_F16: { + uint16_t v; + memcpy(&v, at, sizeof v); + return scr_f16_to_f64(v); + } case SCR_BYTES_F32: { float v; memcpy(&v, at, sizeof v); @@ -3151,6 +3156,11 @@ static void scr_bytes_sort_store(ScrBytes *b, size_t i, double value) { memcpy(at, &v, sizeof v); break; } + case SCR_BYTES_F16: { + uint16_t v = scr_f16_from_f64(value); + memcpy(at, &v, sizeof v); + break; + } case SCR_BYTES_F32: { float v = (float)value; memcpy(at, &v, sizeof v); @@ -3263,6 +3273,18 @@ static int scr_bytes_f32_key(const uint8_t *at, int32_t order, uint32_t *key) { return 0; } +/* Float16 keys use the low 16 bits of a 32-bit key; a descending order + * complements all 32, which keeps the order of keys. */ +static int scr_bytes_f16_key(const uint8_t *at, int32_t order, uint32_t *key) { + uint16_t bits; + memcpy(&bits, at, sizeof bits); + if ((bits & 0x7fff) > 0x7c00) return order ? 2 : 1; + if (order && bits == 0x8000) return 2; + uint32_t k = (bits & 0x8000) ? (uint16_t)~bits : (uint32_t)bits | 0x8000; + *key = k ^ (order < 0 ? ~UINT32_C(0) : 0); + return 0; +} + static int scr_bytes_f64_key(const uint8_t *at, int32_t order, uint64_t *key) { double v; uint64_t bits; @@ -3323,6 +3345,9 @@ static bool scr_bytes_sort_refused(const ScrBytes *b, int32_t order) { if (b->elem == SCR_BYTES_F32) { uint32_t key; if (scr_bytes_f32_key(data + i * 4, order, &key)) return true; + } else if (b->elem == SCR_BYTES_F16) { + uint32_t key; + if (scr_bytes_f16_key(data + i * 2, order, &key)) return true; } else if (b->elem == SCR_BYTES_F64) { uint64_t key; if (scr_bytes_f64_key(data + i * 8, order, &key)) return true; @@ -3353,7 +3378,7 @@ bool scr_bytes_sort_numeric(ScrBytes *b, int32_t order) { size_t width = scr_bytes_elem_size(b->elem); uint8_t *data = b->data; /* Integer elements have no NaN or -0: every order is a value order. */ - if (width == 1 || (width == 2 && n >= SCR_BYTES_COUNT_SORT)) { + if (width == 1 || (width == 2 && b->elem != SCR_BYTES_F16 && n >= SCR_BYTES_COUNT_SORT)) { scr_bytes_count_sort(b, order < 0); return true; } @@ -3392,13 +3417,23 @@ bool scr_bytes_sort_numeric(ScrBytes *b, int32_t order) { uint32_t bits_ = (k_ & SCR_SORT_SIGN32) ? k_ & ~SCR_SORT_SIGN32 : ~k_; \ memcpy(data + (i) * 4, &bits_, 4); \ } while (0) +#define SCR_BYTES_READ32_F16(i, key) scr_bytes_f16_key(data + (i) * 2, order, (key)) +#define SCR_BYTES_WRITE32_F16(i, key) \ + do { \ + uint16_t k_ = (uint16_t)((key) ^ flip32); \ + uint16_t bits_ = (k_ & 0x8000) ? (uint16_t)(k_ & 0x7fff) : (uint16_t)~k_; \ + memcpy(data + (i) * 2, &bits_, 2); \ + } while (0) if (b->elem == SCR_BYTES_F32) SCR_BYTES_KEYS32(F32); + else if (b->elem == SCR_BYTES_F16) SCR_BYTES_KEYS32(F16); else SCR_BYTES_KEYS32(INT); #undef SCR_BYTES_KEYS32 #undef SCR_BYTES_READ32_INT #undef SCR_BYTES_WRITE32_INT #undef SCR_BYTES_READ32_F32 #undef SCR_BYTES_WRITE32_F32 +#undef SCR_BYTES_READ32_F16 +#undef SCR_BYTES_WRITE32_F16 free(keys); return true; } diff --git a/packages/runtime/src/scr_assert.c b/packages/runtime/src/scr_assert.c index 9e2e79418..5a4769038 100644 --- a/packages/runtime/src/scr_assert.c +++ b/packages/runtime/src/scr_assert.c @@ -791,7 +791,7 @@ static bool scr_assert_loose_error(const ScrDyn *a, const ScrDyn *b) { } static bool scr_assert_loose_float_elem(ScrBytesElem elem) { - return elem == SCR_BYTES_F32 || elem == SCR_BYTES_F64; + return elem == SCR_BYTES_F16 || elem == SCR_BYTES_F32 || elem == SCR_BYTES_F64; } /* Typed arrays and Buffers: the same tag (a Buffer is a Uint8Array here), @@ -1305,6 +1305,7 @@ static const char *scr_assert_cf_bytes_class(ScrBytesElem elem) { switch (elem) { case SCR_BYTES_U8: return "Uint8Array"; case SCR_BYTES_U32: return "Uint32Array"; + case SCR_BYTES_F16: return "Float16Array"; case SCR_BYTES_F32: return "Float32Array"; case SCR_BYTES_I32: return "Int32Array"; case SCR_BYTES_F64: return "Float64Array"; diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index df5b8a3c0..f8b9f4479 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -87,10 +87,60 @@ static void scr_bytes_oom(void) { scr_trap("scriptc: out of memory\n"); } +uint16_t scr_f16_from_f64(double value) { + uint64_t bits; + memcpy(&bits, &value, sizeof bits); + uint16_t sign = (uint16_t)((bits >> 48) & 0x8000); + uint64_t magnitude = bits & UINT64_C(0x7fffffffffffffff); + if (magnitude >= UINT64_C(0x7ff0000000000000)) { + if (magnitude == UINT64_C(0x7ff0000000000000)) return sign | 0x7c00; + return sign | 0x7e00 | (uint16_t)((magnitude >> 42) & 0x3ff); + } + int exponent = (int)(magnitude >> 52) - 1023; + if (exponent > 15) return sign | 0x7c00; + uint64_t fraction = magnitude & ((UINT64_C(1) << 52) - 1); + uint64_t significand = exponent == -1023 ? fraction : fraction | (UINT64_C(1) << 52); + if (exponent == -1023) exponent = -1022; + int biased = exponent + 15; + /* Normal results keep 10 of the 52 fraction bits; subnormal results + * shift further, by how far the exponent is below the normal range. */ + int shift = biased >= 1 ? 42 : 42 + (1 - biased); + if (shift >= 54) return sign; + uint64_t q = significand >> shift; + uint64_t rest = significand & ((UINT64_C(1) << shift) - 1); + uint64_t half = UINT64_C(1) << (shift - 1); + if (rest > half || (rest == half && (q & 1))) q++; + if (biased < 1) return sign | (uint16_t)q; /* a carry into 0x400 is the smallest normal */ + if (q == 2048) { + q = 1024; + biased++; + } + if (biased >= 31) return sign | 0x7c00; + return sign | (uint16_t)(biased << 10) | (uint16_t)(q & 0x3ff); +} + +double scr_f16_to_f64(uint16_t h) { + uint64_t sign = (uint64_t)(h >> 15) << 63; + unsigned exponent = (h >> 10) & 0x1f, fraction = h & 0x3ff; + uint64_t bits; + if (exponent == 0) { + double v = (double)fraction * 5.9604644775390625e-8; /* fraction * 2^-24, exact */ + memcpy(&bits, &v, sizeof bits); + bits |= sign; + } else if (exponent == 31) { + bits = sign | UINT64_C(0x7ff0000000000000) | ((uint64_t)fraction << 42); + } else { + bits = sign | ((uint64_t)(exponent - 15 + 1023) << 52) | ((uint64_t)fraction << 42); + } + double out; + memcpy(&out, &bits, sizeof out); + return out; +} + size_t scr_bytes_elem_size(ScrBytesElem elem) { switch (elem) { case SCR_BYTES_U8: case SCR_BYTES_U8C: case SCR_BYTES_I8: return 1; - case SCR_BYTES_U16: case SCR_BYTES_I16: return 2; + case SCR_BYTES_U16: case SCR_BYTES_I16: case SCR_BYTES_F16: return 2; case SCR_BYTES_U32: case SCR_BYTES_I32: case SCR_BYTES_F32: return 4; case SCR_BYTES_F64: return 8; } @@ -106,6 +156,7 @@ const char *scr_bytes_elem_name(ScrBytesElem elem) { case SCR_BYTES_I16: return "Int16Array"; case SCR_BYTES_U32: return "Uint32Array"; case SCR_BYTES_I32: return "Int32Array"; + case SCR_BYTES_F16: return "Float16Array"; case SCR_BYTES_F32: return "Float32Array"; case SCR_BYTES_F64: return "Float64Array"; } @@ -329,6 +380,11 @@ static double scr_bytes_decode(ScrBytesElem elem, const uint8_t *p) { memcpy(&v, p, 4); return (double)v; } + case SCR_BYTES_F16: { + uint16_t v; + memcpy(&v, p, 2); + return scr_f16_to_f64(v); + } case SCR_BYTES_F32: { float v; memcpy(&v, p, 4); @@ -368,6 +424,11 @@ static void scr_bytes_encode(ScrBytesElem elem, uint8_t *p, double v) { memcpy(p, &u, 4); break; } + case SCR_BYTES_F16: { + uint16_t h = scr_f16_from_f64(v); + memcpy(p, &h, 2); + break; + } case SCR_BYTES_F32: { float f = (float)v; /* round-to-nearest-even, exactly Float32Array */ memcpy(p, &f, 4); @@ -511,6 +572,13 @@ static void scr_bytes_read_numbers(double *out, const uint8_t *src, ScrBytesElem SCR_READ_NUMBERS(SCR_BYTES_I32, int32_t); SCR_READ_NUMBERS(SCR_BYTES_F32, float); SCR_READ_NUMBERS(SCR_BYTES_F64, double); + case SCR_BYTES_F16: + for (size_t i = 0; i < count; i++) { + uint16_t value; + memcpy(&value, src + i * sizeof value, sizeof value); + out[i] = scr_f16_to_f64(value); + } + return; } #undef SCR_READ_NUMBERS } @@ -535,6 +603,9 @@ static void scr_bytes_write_numbers(uint8_t *dst, ScrBytesElem elem, const doubl case SCR_BYTES_U32: case SCR_BYTES_I32: { SCR_WRITE_NUMBERS(uint32_t, scr_numeric_to_u32(src[i])); } + case SCR_BYTES_F16: { + SCR_WRITE_NUMBERS(uint16_t, scr_f16_from_f64(src[i])); + } case SCR_BYTES_F32: { SCR_WRITE_NUMBERS(float, (float)src[i]); } @@ -936,6 +1007,7 @@ static size_t scr_dataview_get_size(ScrDataViewGet kind) { return 1; case SCR_DV_U16: case SCR_DV_I16: + case SCR_DV_F16: return 2; case SCR_DV_U32: case SCR_DV_I32: @@ -974,6 +1046,8 @@ double scr_dataview_get(const ScrBytes *b, double byte_off, ScrDataViewGet kind, return (double)(int16_t)u; case SCR_DV_I32: return (double)(int32_t)u; + case SCR_DV_F16: + return scr_f16_to_f64((uint16_t)u); case SCR_DV_F32: { uint32_t bits = (uint32_t)u; float f; @@ -1018,6 +1092,9 @@ void scr_dataview_set(ScrBytes *b, double byte_off, double value, ScrDataViewGet case SCR_DV_I32: u = (uint64_t)scr_bytes_to_u32(value); break; + case SCR_DV_F16: + u = scr_f16_from_f64(value); + break; case SCR_DV_F32: { float f = (float)value; uint32_t bits; diff --git a/packages/runtime/src/scr_bytes.h b/packages/runtime/src/scr_bytes.h index b56f9477d..93a80a7eb 100644 --- a/packages/runtime/src/scr_bytes.h +++ b/packages/runtime/src/scr_bytes.h @@ -20,7 +20,8 @@ * Elements are scalars only and the backing edge is acyclic by * construction (owners point at nothing): never part of a cycle, no * trace. Element reads widen to double; writes coerce JS-exactly (ToUint8 - * / ToUint32 modular truncation, double→float rounding for f32). + * / ToUint32 modular truncation, double→float rounding for f32, one + * double→binary16 rounding for f16). * Out-of-bounds typed ELEMENT reads trap like arrays; invalid writes are * ignored as in JavaScript. The Node-shaped operations * (construction lengths, set(), the read/write numeric families, the @@ -37,7 +38,16 @@ typedef enum ScrBytesElem { SCR_BYTES_U16, /* Uint16Array */ SCR_BYTES_I16, /* Int16Array */ SCR_BYTES_U8C, /* Uint8ClampedArray */ + SCR_BYTES_F16, /* Float16Array (IEEE 754 binary16 storage) */ } ScrBytesElem; +#define SCR_BYTES_ELEM_COUNT 10 + +/* Float16Array's element conversions: one round-to-nearest-even step from + * the double (Float16Array stores, Math.f16round and DataView.setFloat16 + * all round this way), and the exact widening back. NaN keeps its sign and + * top payload bits with the quiet bit set. */ +uint16_t scr_f16_from_f64(double value); +double scr_f16_to_f64(uint16_t bits); typedef struct ScrSharedBytes ScrSharedBytes; @@ -148,6 +158,7 @@ ScrBytes *scr_array_buffer_view_i16(ScrDyn *, ScrDyn *, ScrDyn *); ScrBytes *scr_array_buffer_view_u8(ScrDyn *, ScrDyn *, ScrDyn *); ScrBytes *scr_array_buffer_view_u32(ScrDyn *, ScrDyn *, ScrDyn *); ScrBytes *scr_array_buffer_view_i32(ScrDyn *, ScrDyn *, ScrDyn *); +ScrBytes *scr_array_buffer_view_f16(ScrDyn *, ScrDyn *, ScrDyn *); ScrBytes *scr_array_buffer_view_f32(ScrDyn *, ScrDyn *, ScrDyn *); ScrBytes *scr_array_buffer_view_f64(ScrDyn *, ScrDyn *, ScrDyn *); ScrBytes *scr_array_buffer_view_dv(ScrDyn *, ScrDyn *, ScrDyn *); @@ -205,7 +216,7 @@ ScrBytes *scr_dataview_new(ScrBytes *src, double byte_off, bool has_len, double * with the exception pending). */ typedef enum ScrDataViewGet { SCR_DV_U8, SCR_DV_I8, SCR_DV_U16, SCR_DV_I16, SCR_DV_U32, SCR_DV_I32, - SCR_DV_F32, SCR_DV_F64, SCR_DV_BIGU64, SCR_DV_BIGI64, + SCR_DV_F32, SCR_DV_F64, SCR_DV_BIGU64, SCR_DV_BIGI64, SCR_DV_F16, } ScrDataViewGet; double scr_dataview_get(const ScrBytes *b, double byte_off, ScrDataViewGet kind, bool le); diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index 72d277271..59cf34a10 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -1520,7 +1520,7 @@ static ScrDyn *scr_dyn_invoke_impl( if (recv->kind == SCR_DYN_BYTES) { ScrBytes *bytes = recv->v.bytes; if (bytes->is_data_view) { - static const char *const kinds[] = { "Uint8", "Int8", "Uint16", "Int16", "Uint32", "Int32", "Float32", "Float64", "BigUint64", "BigInt64" }; + static const char *const kinds[] = { "Uint8", "Int8", "Uint16", "Int16", "Uint32", "Int32", "Float32", "Float64", "BigUint64", "BigInt64", "Float16" }; const bool get = !strncmp(method, "get", 3); const bool set = !strncmp(method, "set", 3); for (size_t kind = 0; (get || set) && kind < sizeof kinds / sizeof kinds[0]; kind++) { @@ -1537,7 +1537,7 @@ static ScrDyn *scr_dyn_invoke_impl( } const bool le = argc > (get ? 1 : 2) && scr_dyn_truthy(args[get ? 1 : 2]); if (get) { - if (kind >= SCR_DV_BIGU64) { + if (kind == SCR_DV_BIGU64 || kind == SCR_DV_BIGI64) { ScrBigInt *value = scr_bigint_dataview_get(bytes, offset, kind == SCR_DV_BIGI64, le); if (scr_exc_pending()) return NULL; ScrDyn *result = scr_dyn_new_bigint(value); @@ -1547,7 +1547,7 @@ static ScrDyn *scr_dyn_invoke_impl( double value = scr_dataview_get(bytes, offset, (ScrDataViewGet)kind, le); return scr_exc_pending() ? NULL : scr_dyn_new_num(value); } - if (kind >= SCR_DV_BIGU64) { + if (kind == SCR_DV_BIGU64 || kind == SCR_DV_BIGI64) { ScrBigInt *value = scr_dyn_bigint_coerce(argc > 1 ? args[1] : scr_dyn_undefined()); if (scr_exc_pending()) return NULL; scr_bigint_dataview_set(bytes, offset, value, le); diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index cdfb3d0f9..b5ae62668 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -3113,6 +3113,7 @@ bool scr_dyn_util_type_is(const ScrDyn *value, const ScrStr *probe) { if (PROBE("isInt8Array")) return scr_dyn_typed_array_is(value, SCR_BYTES_I8); if (PROBE("isInt16Array")) return scr_dyn_typed_array_is(value, SCR_BYTES_I16); if (PROBE("isInt32Array")) return scr_dyn_typed_array_is(value, SCR_BYTES_I32); + if (PROBE("isFloat16Array")) return scr_dyn_typed_array_is(value, SCR_BYTES_F16); if (PROBE("isFloat32Array")) return scr_dyn_typed_array_is(value, SCR_BYTES_F32); if (PROBE("isFloat64Array")) return scr_dyn_typed_array_is(value, SCR_BYTES_F64); if (PROBE("isMap")) return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_MAP; @@ -3139,7 +3140,7 @@ bool scr_dyn_util_type_is(const ScrDyn *value, const ScrStr *probe) { * anonymous with length zero. Keep their native closure identity intact. */ ScrDyn *scr_dyn_util_type_value(const ScrDyn *value, const ScrStr *probe) { static const char *const named[] = { "isDataView", "isTypedArray", "isUint8Array", "isUint8ClampedArray", "isUint16Array", "isUint32Array", - "isInt8Array", "isInt16Array", "isInt32Array", "isFloat32Array", "isFloat64Array" }; + "isInt8Array", "isInt16Array", "isInt32Array", "isFloat16Array", "isFloat32Array", "isFloat64Array" }; const char *name = ""; if (probe->len == 17 && memcmp(probe->data, "isArrayBufferView", 17) == 0) name = "isView"; for (size_t i = 0; i < sizeof named / sizeof named[0]; i++) @@ -3158,16 +3159,16 @@ ScrBytes *scr_dyn_bytes_unbox(const ScrDyn *d) { /* Numeric typed-array constructor values share native function identity. * The cache owns one reference until runtime cleanup; arbitrary strings or * functions cannot impersonate one of these constructors. */ -static SCR_TL ScrDyn *scr_bytes_ctors[9]; +static SCR_TL ScrDyn *scr_bytes_ctors[SCR_BYTES_ELEM_COUNT]; static void scr_bytes_ctors_cleanup(void) { - for (size_t i = 0; i < 9; i++) { + for (size_t i = 0; i < SCR_BYTES_ELEM_COUNT; i++) { scr_dyn_release(scr_bytes_ctors[i]); scr_bytes_ctors[i] = NULL; } } static ScrDyn *scr_bytes_ctor_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)args; (void)argc; - for (size_t i = 0; i < 9; i++) { + for (size_t i = 0; i < SCR_BYTES_ELEM_COUNT; i++) { if (!scr_bytes_ctors[i] || scr_bytes_ctors[i]->v.fn.clo != closure) continue; ScrJsonBuf message; scr_jb_init(&message); @@ -3183,7 +3184,7 @@ static ScrDyn *scr_dyn_fn_properties(const ScrDyn *function); static ScrDyn *scr_bytes_constructor_for(ScrBytesElem elem) { if (!scr_bytes_ctors[elem]) { bool initialized = false; - for (size_t i = 0; i < 9; i++) if (scr_bytes_ctors[i]) initialized = true; + for (size_t i = 0; i < SCR_BYTES_ELEM_COUNT; i++) if (scr_bytes_ctors[i]) initialized = true; if (!initialized) scr_atexit(scr_bytes_ctors_cleanup); scr_bytes_ctors[elem] = scr_dyn_new_func(scr_closure_new(NULL, 0), scr_bytes_ctor_call, 3, "typed-array-constructor", scr_bytes_elem_name(elem)); @@ -3196,7 +3197,7 @@ static ScrDyn *scr_bytes_constructor_for(ScrBytesElem elem) { return scr_dyn_retain(scr_bytes_ctors[elem]); } ScrDyn *scr_bytes_constructor(const ScrStr *name) { - for (size_t i = 0; i < 9; i++) { + for (size_t i = 0; i < SCR_BYTES_ELEM_COUNT; i++) { const char *candidate = scr_bytes_elem_name((ScrBytesElem)i); if (strlen(candidate) == name->len && memcmp(candidate, name->data, name->len) == 0) return scr_bytes_constructor_for((ScrBytesElem)i); @@ -3263,7 +3264,7 @@ bool scr_bytes_instanceof(const ScrDyn *value, const ScrDyn *callee) { } if (scr_array_buffer_ctor && callee->v.fn.clo == scr_array_buffer_ctor->v.fn.clo) return scr_array_buffer_is(value); if (scr_array_ctor && callee->v.fn.clo == scr_array_ctor->v.fn.clo) return value->kind == SCR_DYN_ARR || scr_dyn_ref_is_array(value); - for (size_t i = 0; i < 9; i++) { + for (size_t i = 0; i < SCR_BYTES_ELEM_COUNT; i++) { if (scr_bytes_ctors[i] && callee->v.fn.clo == scr_bytes_ctors[i]->v.fn.clo) return scr_dyn_typed_array_is(value, (int)i); } @@ -3276,7 +3277,7 @@ ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrS return scr_array_ctor_call(callee->v.fn.clo, args->v.arr.items, args->v.arr.len); if (callee->kind == SCR_DYN_FUNC && scr_array_buffer_ctor && callee->v.fn.clo == scr_array_buffer_ctor->v.fn.clo) return scr_array_buffer_new(args->v.arr.len ? args->v.arr.items[0] : scr_dyn_undefined()); - for (size_t i = 0; i < 9; i++) { + for (size_t i = 0; i < SCR_BYTES_ELEM_COUNT; i++) { if (callee->kind != SCR_DYN_FUNC || !scr_bytes_ctors[i] || callee->v.fn.clo != scr_bytes_ctors[i]->v.fn.clo) continue; const ScrDyn *input = args->v.arr.len > 0 ? args->v.arr.items[0] : scr_dyn_undefined(); const ScrDyn *offset = args->v.arr.len > 1 ? args->v.arr.items[1] : scr_dyn_undefined(); @@ -12245,6 +12246,7 @@ SCR_ARRAY_BUFFER_VIEW(i16, SCR_BYTES_I16) SCR_ARRAY_BUFFER_VIEW(u8, SCR_BYTES_U8) SCR_ARRAY_BUFFER_VIEW(u32, SCR_BYTES_U32) SCR_ARRAY_BUFFER_VIEW(i32, SCR_BYTES_I32) +SCR_ARRAY_BUFFER_VIEW(f16, SCR_BYTES_F16) SCR_ARRAY_BUFFER_VIEW(f32, SCR_BYTES_F32) SCR_ARRAY_BUFFER_VIEW(f64, SCR_BYTES_F64) #undef SCR_ARRAY_BUFFER_VIEW diff --git a/tests/corpus/5900-float16-array.ts b/tests/corpus/5900-float16-array.ts new file mode 100644 index 000000000..bcf57ea8d --- /dev/null +++ b/tests/corpus/5900-float16-array.ts @@ -0,0 +1,94 @@ +// Float16Array: construction from lengths, arrays, typed arrays and ArrayBuffer +// views; round-to-nearest-even stores with overflow, NaN, -0 and subnormals; +// fill, set, subarray, slice and both sort orders; DataView getFloat16 and +// setFloat16 in both byte orders; Math.f16round; util.types and instanceof; +// structuredClone, assert and Buffer views. Node.js prints the same. +import { types } from "node:util"; +import assert from "node:assert"; +const show = (label: string, a: Float16Array): void => console.log(label, a.length, Array.from(a).join(",")); +const a = new Float16Array(6); +a[0] = 1.5; +a[1] = 65519; +a[2] = 65520; +a[3] = 0.1; +a[4] = -0; +a[5] = 5.960464477539063e-8 / 2; +show("rounding", a); +const b = new Float16Array([NaN, Infinity, -Infinity, 1e-8, 2049, 2051, 4097, -1e6]); +show("specials", b); +console.log(Object.is(a[4], -0), a[6], a.length, a.byteLength, Number.isNaN(b[0])); +const c = Float16Array.from([3.14159, 2.71828, 1.41421]); +show("from", c); +const e = new Float16Array(c); +e[0] = 100; +show("copy", e); +show("source untouched", c); +const f32 = new Float32Array([1 / 3, 2 / 3]); +show("from f32", new Float16Array(f32)); +const buf = new ArrayBuffer(8); +const view = new Float16Array(buf, 2, 2); +view[0] = 1; +view[1] = -2; +console.log("view bytes", Array.from(new Uint8Array(buf)).join(","), new Uint16Array(buf)[1]); +const g = new Float16Array(5); +g.fill(0.3); +show("fill", g); +g.set([1.1, 2.2], 3); +show("set", g); +show("subarray", g.subarray(1, 4)); +show("slice", g.slice(-2)); +const h = new Float16Array([3, -0, 0, NaN, -1, 2.5, -Infinity, 0.1]); +show("sort", h.slice().sort()); +show("sort desc", h.slice().sort((x, y) => y - x)); +show("toSorted", h.toSorted()); +let sum = 0; +for (const v of c) sum += v; +console.log("for-of", sum, h.join("|"), String(c), Object.prototype.toString.call(c)); +const dv = new DataView(new ArrayBuffer(8)); +dv.setFloat16(0, 3.14159); +dv.setFloat16(2, 3.14159, true); +dv.setFloat16(4, -65504); +dv.setFloat16(6, NaN, true); +console.log("dataview", dv.getFloat16(0), dv.getFloat16(2, true), dv.getFloat16(4), dv.getFloat16(6, true), dv.getUint16(0), dv.getUint16(2)); +try { + dv.getFloat16(7); +} catch (err) { + console.log((err as Error).name, (err as Error).message); +} +console.log("f16round", Math.f16round(1.337), Math.f16round(65520), Math.f16round(-0), Math.f16round(5e-8), Math.f16round(2 ** -25), Math.f16round(1.0009765625 + 2 ** -12)); +console.log("types", types.isFloat16Array(a), types.isFloat32Array(a), types.isTypedArray(a), a instanceof Float16Array); +const u: unknown = a; +console.log("unknown", u instanceof Float16Array, u instanceof Float32Array); +const big = new Float16Array(1000); +for (let i = 0; i < big.length; i++) big[i] = Math.sin(i) * 1000; +let acc = 0; +for (let i = 0; i < big.length; i++) acc += big[i]!; +console.log("loop", acc, big[999]); +const f = new Float16Array([1.5, NaN, -0, 65504]); +const clone = structuredClone(f); +clone[0] = 2; +console.log(clone[0], f[0], clone.length); +assert.deepStrictEqual(new Float16Array([NaN, 1]), new Float16Array([NaN, 1])); +try { + assert.deepStrictEqual(new Float16Array([0]), new Float16Array([-0])); +} catch (e) { + console.log("deepStrictEqual -0", (e as Error).name); +} +assert.deepEqual(new Float16Array([0]), new Float16Array([-0])); +const raw = Buffer.from(f.buffer); +console.log(raw.length, raw.readUInt16LE(0), raw.readUInt16LE(6)); +const fromBuf = new Float16Array(f.buffer, 2, 1); +console.log(Number.isNaN(fromBuf[0])); +const list: Float16Array[] = [f, new Float16Array(2)]; +const m = new Map([["a", f]]); +console.log(list.length, m.get("a")![3], list[1]!.length); +const Ctor = Float16Array; +const x = new Ctor(3); +x[1] = 0.2; +console.log(x.length, x[1]); +const y = new Float16Array([1, 2]); +const D = y.constructor as Float16ArrayConstructor; +const z = new D(2); +console.log(z.length, z instanceof Float16Array); +const unknownView: unknown = new Float16Array([7]); +console.log(unknownView instanceof Float16Array); diff --git a/tests/corpus/5901-float16-array-js.js b/tests/corpus/5901-float16-array-js.js new file mode 100644 index 000000000..8c0f6f7bc --- /dev/null +++ b/tests/corpus/5901-float16-array-js.js @@ -0,0 +1,95 @@ +// Float16Array from JavaScript: construction from lengths, arrays, typed +// arrays and ArrayBuffer views; round-to-nearest-even stores with overflow, +// NaN, -0 and subnormals; fill, set, subarray, slice and both sort orders; +// DataView getFloat16 and setFloat16 in both byte orders; Math.f16round; +// util.types and instanceof; structuredClone, assert and Buffer views. +// Node.js prints the same. +import { types } from "node:util"; +import assert from "node:assert"; +const show = (label , a ) => console.log(label, a.length, Array.from(a).join(",")); +const a = new Float16Array(6); +a[0] = 1.5; +a[1] = 65519; +a[2] = 65520; +a[3] = 0.1; +a[4] = -0; +a[5] = 5.960464477539063e-8 / 2; +show("rounding", a); +const b = new Float16Array([NaN, Infinity, -Infinity, 1e-8, 2049, 2051, 4097, -1e6]); +show("specials", b); +console.log(Object.is(a[4], -0), a[6], a.length, a.byteLength, Number.isNaN(b[0])); +const c = Float16Array.from([3.14159, 2.71828, 1.41421]); +show("from", c); +const e = new Float16Array(c); +e[0] = 100; +show("copy", e); +show("source untouched", c); +const f32 = new Float32Array([1 / 3, 2 / 3]); +show("from f32", new Float16Array(f32)); +const buf = new ArrayBuffer(8); +const view = new Float16Array(buf, 2, 2); +view[0] = 1; +view[1] = -2; +console.log("view bytes", Array.from(new Uint8Array(buf)).join(","), new Uint16Array(buf)[1]); +const g = new Float16Array(5); +g.fill(0.3); +show("fill", g); +g.set([1.1, 2.2], 3); +show("set", g); +show("subarray", g.subarray(1, 4)); +show("slice", g.slice(-2)); +const h = new Float16Array([3, -0, 0, NaN, -1, 2.5, -Infinity, 0.1]); +show("sort", h.slice().sort()); +show("sort desc", h.slice().sort((x, y) => y - x)); +show("toSorted", h.toSorted()); +let sum = 0; +for (const v of c) sum += v; +console.log("for-of", sum, h.join("|"), String(c), Object.prototype.toString.call(c)); +const dv = new DataView(new ArrayBuffer(8)); +dv.setFloat16(0, 3.14159); +dv.setFloat16(2, 3.14159, true); +dv.setFloat16(4, -65504); +dv.setFloat16(6, NaN, true); +console.log("dataview", dv.getFloat16(0), dv.getFloat16(2, true), dv.getFloat16(4), dv.getFloat16(6, true), dv.getUint16(0), dv.getUint16(2)); +try { + dv.getFloat16(7); +} catch (err) { + console.log((err ).name, (err ).message); +} +console.log("f16round", Math.f16round(1.337), Math.f16round(65520), Math.f16round(-0), Math.f16round(5e-8), Math.f16round(2 ** -25), Math.f16round(1.0009765625 + 2 ** -12)); +console.log("types", types.isFloat16Array(a), types.isFloat32Array(a), types.isTypedArray(a), a instanceof Float16Array); +const u = a; +console.log("unknown", u instanceof Float16Array, u instanceof Float32Array); +const big = new Float16Array(1000); +for (let i = 0; i < big.length; i++) big[i] = Math.sin(i) * 1000; +let acc = 0; +for (let i = 0; i < big.length; i++) acc += big[i] ; +console.log("loop", acc, big[999]); +const f = new Float16Array([1.5, NaN, -0, 65504]); +const clone = structuredClone(f); +clone[0] = 2; +console.log(clone[0], f[0], clone.length); +assert.deepStrictEqual(new Float16Array([NaN, 1]), new Float16Array([NaN, 1])); +try { + assert.deepStrictEqual(new Float16Array([0]), new Float16Array([-0])); +} catch (e) { + console.log("deepStrictEqual -0", (e ).name); +} +assert.deepEqual(new Float16Array([0]), new Float16Array([-0])); +const raw = Buffer.from(f.buffer); +console.log(raw.length, raw.readUInt16LE(0), raw.readUInt16LE(6)); +const fromBuf = new Float16Array(f.buffer, 2, 1); +console.log(Number.isNaN(fromBuf[0])); +const list = [f, new Float16Array(2)]; +const m = new Map ([["a", f]]); +console.log(list.length, m.get("a") [3], list[1] .length); +const Ctor = Float16Array; +const x = new Ctor(3); +x[1] = 0.2; +console.log(x.length, x[1]); +const y = new Float16Array([1, 2]); +const D = y.constructor ; +const z = new D(2); +console.log(z.length, z instanceof Float16Array); +const unknownView = new Float16Array([7]); +console.log(unknownView instanceof Float16Array); diff --git a/tests/diagnostics/dataview.ts b/tests/diagnostics/dataview.ts index 01255b47c..c20193279 100644 --- a/tests/diagnostics/dataview.ts +++ b/tests/diagnostics/dataview.ts @@ -1,6 +1,8 @@ -// Stored ArrayBuffers and DataViews lower; Float16 accessors remain unsupported. +// Stored ArrayBuffers and DataViews lower, Float16 accessors included; +// detached accessor methods remain unsupported. const buffer = new ArrayBuffer(16); const view = new DataView(buffer); view.setFloat16(0, 1.5); console.log(view.getFloat16(0)); +const getter = view.getFloat16; // End of the diagnostic fixture. diff --git a/tests/diagnostics/es2025-fences.ts b/tests/diagnostics/es2025-fences.ts index 2df12226f..067da192e 100644 --- a/tests/diagnostics/es2025-fences.ts +++ b/tests/diagnostics/es2025-fences.ts @@ -1,9 +1,8 @@ // The es2025-lib surface WITHOUT a lowering fences by name: resizable // ArrayBuffers and transfer, SharedArrayBuffer.grow, Atomics.waitAsync, -// Float16Array and its Math/DataView companions, first-class iterator -// objects, and the unlowered forms of the implemented statics -// (Promise.try with fewer arguments than parameters, groupBy's -// literal-union keys, a set-like non-Set argument). +// first-class iterator objects, and the unlowered forms of the +// implemented statics (Promise.try with fewer arguments than parameters, +// groupBy's literal-union keys, a set-like non-Set argument). // Resizable ArrayBuffer + transfer (ES2024). const ab = new ArrayBuffer(8, { maxByteLength: 16 }); @@ -22,12 +21,6 @@ sab.grow(16); const ia = new Int32Array(4); const waited = Atomics.waitAsync(ia, 0, 0, 1); -// Float16 (ES2025): the typed array, Math.f16round, DataView accessors. -const f16 = new Float16Array(4); -console.log(Math.f16round(1.337)); -const dv = new DataView(new Uint8Array(8).buffer); -dv.setFloat16(0, 1.5); - // First-class iterator objects. const it = Iterator.from([1, 2, 3].values()); diff --git a/tests/harness/__snapshots__/dataview.ts.txt b/tests/harness/__snapshots__/dataview.ts.txt index f09051126..4f1401ca3 100644 --- a/tests/harness/__snapshots__/dataview.ts.txt +++ b/tests/harness/__snapshots__/dataview.ts.txt @@ -1,17 +1,8 @@ -dataview.ts:4:1 - error SC2020: 'DataView.setFloat16' is part of the standard library types but has no scriptc lowering yet +dataview.ts:7:16 - error SC2020: 'DataView.getFloat16' is part of the standard library types but has no scriptc lowering yet - 3 | const view = new DataView(buffer); - 4 | view.setFloat16(0, 1.5); - | ^~~~~~~~~~~~~~~ - 5 | console.log(view.getFloat16(0)); - - hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) - -dataview.ts:5:13 - error SC2020: 'DataView.getFloat16' is part of the standard library types but has no scriptc lowering yet - - 4 | view.setFloat16(0, 1.5); - 5 | console.log(view.getFloat16(0)); - | ^~~~~~~~~~~~~~~ - 6 | // End of the diagnostic fixture. + 6 | console.log(view.getFloat16(0)); + 7 | const getter = view.getFloat16; + | ^~~~~~~~~~~~~~~ + 8 | // End of the diagnostic fixture. hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) \ No newline at end of file diff --git a/tests/harness/__snapshots__/es2025-fences.ts.txt b/tests/harness/__snapshots__/es2025-fences.ts.txt index e1761f73a..e2dc5f40f 100644 --- a/tests/harness/__snapshots__/es2025-fences.ts.txt +++ b/tests/harness/__snapshots__/es2025-fences.ts.txt @@ -1,125 +1,89 @@ -es2025-fences.ts:9:12 - error SC2020: 'resizable ArrayBuffer construction' is part of the standard library types but has no scriptc lowering yet +es2025-fences.ts:8:12 - error SC2020: 'resizable ArrayBuffer construction' is part of the standard library types but has no scriptc lowering yet - 8 | // Resizable ArrayBuffer + transfer (ES2024). - 9 | const ab = new ArrayBuffer(8, { maxByteLength: 16 }); - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 10 | ab.resize(16); + 7 | // Resizable ArrayBuffer + transfer (ES2024). + 8 | const ab = new ArrayBuffer(8, { maxByteLength: 16 }); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 9 | ab.resize(16); hint: fixed-length ArrayBuffers accept a byte length; resizable storage is not supported yet -es2025-fences.ts:15:27 - error SC2020: 'resizable ArrayBuffer construction' is part of the standard library types but has no scriptc lowering yet +es2025-fences.ts:14:27 - error SC2020: 'resizable ArrayBuffer construction' is part of the standard library types but has no scriptc lowering yet - 14 | // fixed-length new Uint8Array(new ArrayBuffer(8)) lowers). - 15 | const rz = new Uint8Array(new ArrayBuffer(8, { maxByteLength: 16 })); + 13 | // fixed-length new Uint8Array(new ArrayBuffer(8)) lowers). + 14 | const rz = new Uint8Array(new ArrayBuffer(8, { maxByteLength: 16 })); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 16 | + 15 | hint: fixed-length ArrayBuffers accept a byte length; resizable storage is not supported yet -es2025-fences.ts:18:13 - error SC2020: 'resizable ArrayBuffer construction' is part of the standard library types but has no scriptc lowering yet +es2025-fences.ts:17:13 - error SC2020: 'resizable ArrayBuffer construction' is part of the standard library types but has no scriptc lowering yet - 17 | // SharedArrayBuffer growth. - 18 | const sab = new SharedArrayBuffer(8, { maxByteLength: 16 }); + 16 | // SharedArrayBuffer growth. + 17 | const sab = new SharedArrayBuffer(8, { maxByteLength: 16 }); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 19 | sab.grow(16); + 18 | sab.grow(16); hint: fixed-length ArrayBuffers accept a byte length; resizable storage is not supported yet -es2025-fences.ts:23:7 - error SC2009: values of type '{ async: false; value: "not-equal" | "timed-out"; } | { async: true; value: Promise<"ok" | "timed-out">; }' cannot be compiled: the union shape is supported, but its arm '{ async: false; value: "not-equal" | "timed-out"; }' does not compile +es2025-fences.ts:22:7 - error SC2009: values of type '{ async: false; value: "not-equal" | "timed-out"; } | { async: true; value: Promise<"ok" | "timed-out">; }' cannot be compiled: the union shape is supported, but its arm '{ async: false; value: "not-equal" | "timed-out"; }' does not compile - 22 | const ia = new Int32Array(4); - 23 | const waited = Atomics.waitAsync(ia, 0, 0, 1); + 21 | const ia = new Int32Array(4); + 22 | const waited = Atomics.waitAsync(ia, 0, 0, 1); | ^~~~~~ - 24 | + 23 | hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') -es2025-fences.ts:23:16 - error SC2020: 'Atomics.waitAsync' is part of the standard library types but has no scriptc lowering yet +es2025-fences.ts:22:16 - error SC2020: 'Atomics.waitAsync' is part of the standard library types but has no scriptc lowering yet - 22 | const ia = new Int32Array(4); - 23 | const waited = Atomics.waitAsync(ia, 0, 0, 1); + 21 | const ia = new Int32Array(4); + 22 | const waited = Atomics.waitAsync(ia, 0, 0, 1); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 24 | + 23 | hint: use integer load, store, exchange, compareExchange, add, sub, and, or, xor, wait or notify -es2025-fences.ts:26:7 - error SC2020: 'Float16Array' is part of the standard library types but has no scriptc lowering yet +es2025-fences.ts:25:7 - error SC2020: 'IteratorObject' is part of the standard library types but has no scriptc lowering yet - 25 | // Float16 (ES2025): the typed array, Math.f16round, DataView accessors. - 26 | const f16 = new Float16Array(4); - | ^~~ - 27 | console.log(Math.f16round(1.337)); - - hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) - -es2025-fences.ts:26:13 - error SC2020: 'new Float16Array' is part of the standard library types but has no scriptc lowering yet - - 25 | // Float16 (ES2025): the typed array, Math.f16round, DataView accessors. - 26 | const f16 = new Float16Array(4); - | ^~~~~~~~~~~~~~~~~~~ - 27 | console.log(Math.f16round(1.337)); - - hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) - -es2025-fences.ts:27:13 - error SC2020: 'Math.f16round' is part of the standard library types but has no scriptc lowering yet - - 26 | const f16 = new Float16Array(4); - 27 | console.log(Math.f16round(1.337)); - | ^~~~~~~~~~~~~ - 28 | const dv = new DataView(new Uint8Array(8).buffer); - - hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) - -es2025-fences.ts:29:1 - error SC2020: 'DataView.setFloat16' is part of the standard library types but has no scriptc lowering yet - - 28 | const dv = new DataView(new Uint8Array(8).buffer); - 29 | dv.setFloat16(0, 1.5); - | ^~~~~~~~~~~~~ - 30 | - - hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) - -es2025-fences.ts:32:7 - error SC2020: 'IteratorObject' is part of the standard library types but has no scriptc lowering yet - - 31 | // First-class iterator objects. - 32 | const it = Iterator.from([1, 2, 3].values()); + 24 | // First-class iterator objects. + 25 | const it = Iterator.from([1, 2, 3].values()); | ^~ - 33 | + 26 | hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) -es2025-fences.ts:32:12 - error SC2020: 'Iterator.from' is part of the standard library types but has no scriptc lowering yet +es2025-fences.ts:25:12 - error SC2020: 'Iterator.from' is part of the standard library types but has no scriptc lowering yet - 31 | // First-class iterator objects. - 32 | const it = Iterator.from([1, 2, 3].values()); + 24 | // First-class iterator objects. + 25 | const it = Iterator.from([1, 2, 3].values()); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 33 | + 26 | hint: first-class iterator objects have no lowering — iterator helpers compile as one chain on an array iterator, consumed in place: arr.values().map(f).take(n).toArray() -es2025-fences.ts:35:27 - error SC2020: 'Promise.try over a callback of type '(a: number, b?: number | undefined) => number' with 1 argument' is part of the standard library types but has no scriptc lowering yet +es2025-fences.ts:28:27 - error SC2020: 'Promise.try over a callback of type '(a: number, b?: number | undefined) => number' with 1 argument' is part of the standard library types but has no scriptc lowering yet - 34 | // Promise.try with fewer arguments than the callback's parameters. - 35 | const tried = Promise.try((a: number, b?: number) => a + (b ?? 0), 1); + 27 | // Promise.try with fewer arguments than the callback's parameters. + 28 | const tried = Promise.try((a: number, b?: number) => a + (b ?? 0), 1); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 36 | + 29 | hint: pass one argument per callback parameter: Promise.try((a, b) => a + b, 1, 2) -es2025-fences.ts:38:17 - error SC2020: 'Object.groupBy at this result type' is part of the standard library types but has no scriptc lowering yet +es2025-fences.ts:31:17 - error SC2020: 'Object.groupBy at this result type' is part of the standard library types but has no scriptc lowering yet - 37 | // Object.groupBy at a literal-union key type (a fixed-field record). - 38 | const grouped = Object.groupBy([1, 2, 3], (n) => (n % 2 === 0 ? "even" : "odd")); + 30 | // Object.groupBy at a literal-union key type (a fixed-field record). + 31 | const grouped = Object.groupBy([1, 2, 3], (n) => (n % 2 === 0 ? "even" : "odd")); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 39 | + 32 | hint: a string-keyed result (Partial>) is the supported form — a literal-union key type makes a fixed-field record: widen the callback's key type to string (numbers stringify like JS property keys) -es2025-fences.ts:43:19 - error SC2020: 'Set.prototype.union over a set-like argument of type '{ size: number; has: (x: number) => x is 1; keys: () => ArrayIterator; }'' is part of the standard library types but has no scriptc lowering yet +es2025-fences.ts:36:19 - error SC2020: 'Set.prototype.union over a set-like argument of type '{ size: number; has: (x: number) => x is 1; keys: () => ArrayIterator; }'' is part of the standard library types but has no scriptc lowering yet - 42 | const like = { size: 1, has: (x: number) => x === 1, keys: () => [1].values() }; - 43 | const u = s.union(like); + 35 | const like = { size: 1, has: (x: number) => x === 1, keys: () => [1].values() }; + 36 | const u = s.union(like); | ^~~~ - 44 | + 37 | hint: only a real Set or Map lowers (the argument's has/size/keys must be the builtins) — build one first: new Set(...) \ No newline at end of file diff --git a/tests/harness/corpus-costs.json b/tests/harness/corpus-costs.json index ecd7f5d9b..2b83e3de9 100644 --- a/tests/harness/corpus-costs.json +++ b/tests/harness/corpus-costs.json @@ -1841,6 +1841,8 @@ "5648-dns-lookup-forms.ts": 2992, "5649-dns-service-servers.ts": 2712, "5650-dns-lookup-errors-untyped.cjs": 2822, + "5900-float16-array.ts": 3000, + "5901-float16-array-js.js": 2900, "5970-net-connect-host-name.ts": 2400, "5971-buffer-chunks-to-string.ts": 2700, "5972-net-set-encoding-listeners.ts": 2550, @@ -4468,6 +4470,8 @@ "5648-dns-lookup-forms.ts": 3416, "5649-dns-service-servers.ts": 4916, "5650-dns-lookup-errors-untyped.cjs": 3889, + "5900-float16-array.ts": 3750, + "5901-float16-array-js.js": 3700, "5970-net-connect-host-name.ts": 2800, "5971-buffer-chunks-to-string.ts": 3100, "5972-net-set-encoding-listeners.ts": 3000, From 044b7ae42cecc719e3b4968123d34137be126c56 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 17 Oct 2026 23:42:51 -0500 Subject: [PATCH 2/4] Regenerate the surface manifest --- packages/compiler/surface-manifest.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index 63efde378..9067e29b2 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -2507,6 +2507,12 @@ "name": "util/types.isDataView", "status": "static" }, + { + "id": "node-builtin.util.types.isFloat16Array", + "kind": "node-builtin", + "name": "util/types.isFloat16Array", + "status": "static" + }, { "id": "node-builtin.util.types.isFloat32Array", "kind": "node-builtin", @@ -4500,6 +4506,13 @@ "status": "static", "note": "compiles statically at arity 1; other declared call shapes are refused with SC2012" }, + { + "id": "stdlib.math.f16round", + "kind": "stdlib", + "name": "Math.f16round", + "status": "static", + "note": "compiles statically at arity 1; other declared call shapes are refused with SC2012" + }, { "id": "stdlib.math.floor", "kind": "stdlib", From d8f707a35eeb1926fc751cc252cdfd699f15d363 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 18 Oct 2026 01:35:31 -0500 Subject: [PATCH 3/4] Drop the util.types.isFloat16Array entry main already adds --- packages/compiler/src/frontend/lowering/surfaces.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index c07d61906..5f8baa6fb 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -1750,7 +1750,6 @@ export const BUILTIN_MODULE_FNS: Record< isInt8Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, isInt16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, isInt32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isFloat16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, isFloat32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, isFloat64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, isMap: { fn: "util.typeIs", params: [DYN], result: BOOL }, From 24096c6e679d3a19947e3d83079c88b2285d55d7 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 18 Oct 2026 01:41:19 -0500 Subject: [PATCH 4/4] Remove the leftover always-false isFloat16Array probe The util.types brands answered isFloat16Array with false for every value. Float16Array now answers it from the value's storage kind, ahead of that probe, so this drops the unreachable entry and the declaration that returns boolean. --- packages/compiler/ambient/scriptc-node-fallback.d.ts | 1 - packages/runtime/src/scr_json.c | 8 +++----- tests/corpus/5831-util-types-brands.ts | 6 +++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 27dcf99c4..8d2eaab10 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -3280,7 +3280,6 @@ declare module "util/types" { export function isBoxedPrimitive(value: unknown): boolean; export function isBigInt64Array(value: unknown): value is BigInt64Array; export function isBigUint64Array(value: unknown): value is BigUint64Array; - export function isFloat16Array(value: unknown): boolean; export function isCryptoKey(value: unknown): boolean; export function isExternal(value: unknown): boolean; } diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index ce9a43d62..e5d6f8d6a 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -3163,13 +3163,11 @@ bool scr_dyn_util_type_is(const ScrDyn *value, const ScrStr *probe) { if (PROBE("isMapIterator")) return scr_native_iterator_over(value, 2); if (PROBE("isSetIterator")) return scr_native_iterator_over(value, 3); if (PROBE("isKeyObject")) return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_KEY_OBJECT; - /* Boxed primitives, BigInt64Array, BigUint64Array, Float16Array, CryptoKey - * and external values cannot be created here, so no value has these - * brands. */ + /* Boxed primitives, BigInt64Array, BigUint64Array, CryptoKey and external + * values cannot be created here, so no value has these brands. */ if (PROBE("isNumberObject") || PROBE("isStringObject") || PROBE("isBooleanObject") || PROBE("isBigIntObject") || PROBE("isSymbolObject") || PROBE("isBoxedPrimitive") || - PROBE("isBigInt64Array") || PROBE("isBigUint64Array") || PROBE("isFloat16Array") || - PROBE("isCryptoKey") || PROBE("isExternal")) + PROBE("isBigInt64Array") || PROBE("isBigUint64Array") || PROBE("isCryptoKey") || PROBE("isExternal")) return false; if (PROBE("isNativeError")) { ScrError *error = scr_errdyn_err_of(value); diff --git a/tests/corpus/5831-util-types-brands.ts b/tests/corpus/5831-util-types-brands.ts index 7deec967d..d38200914 100644 --- a/tests/corpus/5831-util-types-brands.ts +++ b/tests/corpus/5831-util-types-brands.ts @@ -1,9 +1,9 @@ // util.types brand checks over typed values: isDate, isSharedArrayBuffer, // isMapIterator, isSetIterator and isKeyObject answer from the value's // internal brand, and isBoxedPrimitive (with the per-kind boxed checks), -// isBigInt64Array, isBigUint64Array, isFloat16Array, isCryptoKey and -// isExternal answer false for every value a program here can create. A -// Date-typed argument answers from its static type. +// isBigInt64Array, isBigUint64Array, isCryptoKey and isExternal answer +// false for every value a program here can create. A Date-typed argument +// answers from its static type. import { types } from "node:util"; import * as direct from "node:util/types"; import { createSecretKey } from "node:crypto";