From f3196f886db26476d3a185ef705da5d321570761 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Wed, 25 Oct 2023 22:08:07 -0700 Subject: [PATCH] Make test params readonly so they can't be accidentally permanently modified (#3097) This should hopefully categorically prevent bugs like the one fixed in https://github.com/gpuweb/cts/pull/3096 --- src/common/framework/params_builder.ts | 16 +- src/common/internal/test_group.ts | 7 +- src/common/util/types.ts | 39 ++ src/common/util/util.ts | 4 +- src/unittests/floating_point.spec.ts | 30 +- src/unittests/maths.spec.ts | 4 +- .../render_pipeline/inter_stage.spec.ts | 4 +- src/webgpu/format_info.ts | 2 +- .../expression/binary/af_addition.spec.ts | 4 +- .../expression/binary/af_division.spec.ts | 4 +- .../binary/af_multiplication.spec.ts | 4 +- .../expression/binary/af_remainder.spec.ts | 4 +- .../expression/binary/af_subtraction.spec.ts | 4 +- .../expression/binary/f16_addition.spec.ts | 4 +- .../expression/binary/f16_division.spec.ts | 4 +- .../binary/f16_multiplication.spec.ts | 4 +- .../expression/binary/f16_remainder.spec.ts | 4 +- .../expression/binary/f16_subtraction.spec.ts | 4 +- .../expression/binary/f32_addition.spec.ts | 4 +- .../expression/binary/f32_division.spec.ts | 4 +- .../binary/f32_multiplication.spec.ts | 4 +- .../expression/binary/f32_remainder.spec.ts | 4 +- .../expression/binary/f32_subtraction.spec.ts | 4 +- .../expression/call/builtin/bitcast.spec.ts | 4 +- .../expression/call/builtin/clamp.spec.ts | 2 +- .../call/builtin/faceForward.spec.ts | 13 +- .../expression/call/builtin/frexp.spec.ts | 28 +- .../expression/call/builtin/modf.spec.ts | 4 +- .../expression/call/builtin/refract.spec.ts | 11 +- .../shader/execution/expression/expression.ts | 62 ++- src/webgpu/shader/execution/zero_init.spec.ts | 2 +- src/webgpu/util/conversion.ts | 17 +- src/webgpu/util/floating_point.ts | 415 ++++++++++-------- src/webgpu/util/math.ts | 76 ++-- 34 files changed, 449 insertions(+), 351 deletions(-) diff --git a/src/common/framework/params_builder.ts b/src/common/framework/params_builder.ts index 4947245a3251..845d1cd2e92a 100644 --- a/src/common/framework/params_builder.ts +++ b/src/common/framework/params_builder.ts @@ -1,6 +1,7 @@ import { Merged, mergeParams, mergeParamsChecked } from '../internal/params_utils.js'; import { comparePublicParamsPaths, Ordering } from '../internal/query/compare.js'; import { stringifyPublicParams } from '../internal/query/stringify_params.js'; +import { DeepReadonly } from '../util/types.js'; import { assert, mapLazy, objectEquals } from '../util/util.js'; import { TestParams } from './fixture.js'; @@ -98,7 +99,7 @@ export type ParamTypeOf< * - `[case params, undefined]` if not. */ export type CaseSubcaseIterable = Iterable< - readonly [CaseP, Iterable | undefined] + readonly [DeepReadonly, Iterable> | undefined] >; /** @@ -143,7 +144,7 @@ export function builderIterateCasesWithSubcases( */ export class CaseParamsBuilder extends ParamsBuilderBase - implements Iterable, ParamsBuilder { + implements Iterable>, ParamsBuilder { *iterateCasesWithSubcases(caseFilter: TestParams | null): CaseSubcaseIterable { for (const caseP of this.cases(caseFilter)) { if (caseFilter) { @@ -155,12 +156,12 @@ export class CaseParamsBuilder } } - yield [caseP, undefined]; + yield [caseP as DeepReadonly, undefined]; } } - [Symbol.iterator](): Iterator { - return this.cases(null); + [Symbol.iterator](): Iterator> { + return this.cases(null) as Iterator>; } /** @inheritDoc */ @@ -302,7 +303,10 @@ export class SubcaseParamsBuilder const subcases = Array.from(this.subcases(caseP)); if (subcases.length) { - yield [caseP, subcases]; + yield [ + caseP as DeepReadonly, + subcases as DeepReadonly[], + ]; } } } diff --git a/src/common/internal/test_group.ts b/src/common/internal/test_group.ts index c7dc38d06bbc..6e13fbf47458 100644 --- a/src/common/internal/test_group.ts +++ b/src/common/internal/test_group.ts @@ -26,6 +26,7 @@ import { stringifyPublicParamsUniquely, } from '../internal/query/stringify_params.js'; import { validQueryPart } from '../internal/query/validQueryPart.js'; +import { DeepReadonly } from '../util/types.js'; import { assert, unreachable } from '../util/util.js'; import { logToWebsocket } from './websocket_logger.js'; @@ -82,9 +83,11 @@ export function makeTestGroupForUnitTesting( /** Parameter name for batch number (see also TestBuilder.batch). */ const kBatchParamName = 'batch__'; -type TestFn = (t: F & { params: P }) => Promise | void; +type TestFn = ( + t: F & { params: DeepReadonly

} +) => Promise | void; type BeforeAllSubcasesFn = ( - s: S & { params: P } + s: S & { params: DeepReadonly

} ) => Promise | void; export class TestGroup implements TestGroupBuilder { diff --git a/src/common/util/types.ts b/src/common/util/types.ts index dfd5e4b5eab5..e677cbb6c78a 100644 --- a/src/common/util/types.ts +++ b/src/common/util/types.ts @@ -13,6 +13,45 @@ export type TypeEqual = (() => T extends X ? 1 : 2) extends () => T /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ export function assertTypeTrue() {} +/** `ReadonlyArray` of `ReadonlyArray`s. */ +export type ROArrayArray = ReadonlyArray>; +/** `ReadonlyArray` of `ReadonlyArray`s of `ReadonlyArray`s. */ +export type ROArrayArrayArray = ReadonlyArray>>; + +/** + * Deep version of the Readonly<> type, with support for tuples (up to length 7). + * + */ +export type DeepReadonly = T extends [infer A] + ? DeepReadonlyObject<[A]> + : T extends [infer A, infer B] + ? DeepReadonlyObject<[A, B]> + : T extends [infer A, infer B, infer C] + ? DeepReadonlyObject<[A, B, C]> + : T extends [infer A, infer B, infer C, infer D] + ? DeepReadonlyObject<[A, B, C, D]> + : T extends [infer A, infer B, infer C, infer D, infer E] + ? DeepReadonlyObject<[A, B, C, D, E]> + : T extends [infer A, infer B, infer C, infer D, infer E, infer F] + ? DeepReadonlyObject<[A, B, C, D, E, F]> + : T extends [infer A, infer B, infer C, infer D, infer E, infer F, infer G] + ? DeepReadonlyObject<[A, B, C, D, E, F, G]> + : T extends Map + ? ReadonlyMap, DeepReadonlyObject> + : T extends Set + ? ReadonlySet> + : T extends Promise + ? Promise> + : T extends Primitive + ? T + : T extends (infer A)[] + ? DeepReadonlyArray + : DeepReadonlyObject; + +type Primitive = string | number | boolean | undefined | null | Function | symbol; +type DeepReadonlyArray = ReadonlyArray>; +type DeepReadonlyObject = { readonly [P in keyof T]: DeepReadonly }; + /** * Computes the intersection of a set of types, given the union of those types. * diff --git a/src/common/util/util.ts b/src/common/util/util.ts index 876851f100c3..be109fc9d422 100644 --- a/src/common/util/util.ts +++ b/src/common/util/util.ts @@ -346,7 +346,7 @@ interface TypedArrayMap { type TypedArrayParam = { type: K; - data: number[]; + data: readonly number[]; }; /** @@ -387,7 +387,7 @@ export function typedArrayParam( export function createTypedArray( type: K, - data: number[] + data: readonly number[] ): TypedArrayMap[K] { return new kTypedArrayBufferViews[type](data) as TypedArrayMap[K]; } diff --git a/src/unittests/floating_point.spec.ts b/src/unittests/floating_point.spec.ts index d016980a13f1..7eae72447624 100644 --- a/src/unittests/floating_point.spec.ts +++ b/src/unittests/floating_point.spec.ts @@ -2582,8 +2582,7 @@ g.test('atanInterval') return ulp_error * trait.oneULP(n); }; - t.params.expected = applyError(t.params.expected, error); - const expected = trait.toInterval(t.params.expected); + const expected = trait.toInterval(applyError(t.params.expected, error)); const got = trait.atanInterval(t.params.input); t.expect( @@ -2760,8 +2759,7 @@ g.test('cosInterval') return t.params.trait === 'f32' ? 2 ** -11 : 2 ** -7; }; - t.params.expected = applyError(t.params.expected, error); - const expected = trait.toInterval(t.params.expected); + const expected = trait.toInterval(applyError(t.params.expected, error)); const got = trait.cosInterval(t.params.input); t.expect( @@ -2941,8 +2939,7 @@ g.test('expInterval') return ulp_error * trait.oneULP(x); }; - t.params.expected = applyError(t.params.expected, error); - const expected = trait.toInterval(t.params.expected); + const expected = trait.toInterval(applyError(t.params.expected, error)); const got = trait.expInterval(t.params.input); t.expect( @@ -3001,8 +2998,7 @@ g.test('exp2Interval') return ulp_error * trait.oneULP(x); }; - t.params.expected = applyError(t.params.expected, error); - const expected = trait.toInterval(t.params.expected); + const expected = trait.toInterval(applyError(t.params.expected, error)); const got = trait.exp2Interval(t.params.input); t.expect( @@ -3197,8 +3193,7 @@ g.test('inverseSqrtInterval') return 2 * trait.oneULP(n); }; - t.params.expected = applyError(t.params.expected, error); - const expected = trait.toInterval(t.params.expected); + const expected = trait.toInterval(applyError(t.params.expected, error)); const got = trait.inverseSqrtInterval(t.params.input); t.expect( @@ -3322,8 +3317,7 @@ g.test('logInterval') return 3 * trait.oneULP(n); }; - t.params.expected = applyError(t.params.expected, error); - const expected = trait.toInterval(t.params.expected); + const expected = trait.toInterval(applyError(t.params.expected, error)); const got = trait.logInterval(t.params.input); t.expect( @@ -3373,8 +3367,7 @@ g.test('log2Interval') return 3 * trait.oneULP(n); }; - t.params.expected = applyError(t.params.expected, error); - const expected = trait.toInterval(t.params.expected); + const expected = trait.toInterval(applyError(t.params.expected, error)); const got = trait.log2Interval(t.params.input); t.expect( @@ -3720,8 +3713,7 @@ g.test('sinInterval') return t.params.trait === 'f32' ? 2 ** -11 : 2 ** -7; }; - t.params.expected = applyError(t.params.expected, error); - const expected = trait.toInterval(t.params.expected); + const expected = trait.toInterval(applyError(t.params.expected, error)); const got = trait.sinInterval(t.params.input); t.expect( @@ -3855,8 +3847,7 @@ g.test('sqrtInterval') return 2.5 * trait.oneULP(n); }; - t.params.expected = applyError(t.params.expected, error); - const expected = trait.toInterval(t.params.expected); + const expected = trait.toInterval(applyError(t.params.expected, error)); const got = trait.sqrtInterval(t.params.input); t.expect( @@ -4429,10 +4420,9 @@ g.test('divisionInterval') }; const [x, y] = t.params.input; - t.params.expected = applyError(t.params.expected, error); // Do not swizzle here, so the correct implementation under test is called. - const expected = FP[t.params.trait].toInterval(t.params.expected); + const expected = FP[t.params.trait].toInterval(applyError(t.params.expected, error)); const got = FP[t.params.trait].divisionInterval(x, y); t.expect( objectEquals(expected, got), diff --git a/src/unittests/maths.spec.ts b/src/unittests/maths.spec.ts index 1c37e436fbd5..357c574281f3 100644 --- a/src/unittests/maths.spec.ts +++ b/src/unittests/maths.spec.ts @@ -72,8 +72,8 @@ function withinOneULPF32(got: number, expected: number, mode: FlushMode): boolea * FTZ occur during comparison **/ function compareArrayOfNumbersF32( - got: Array, - expect: Array, + got: readonly number[], + expect: readonly number[], mode: FlushMode = 'flush' ): boolean { return ( diff --git a/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts b/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts index afcb5ace1309..91aabb0ab8c9 100644 --- a/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts +++ b/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts @@ -12,7 +12,7 @@ function getVarName(i: number) { } class InterStageMatchingValidationTest extends CreateRenderPipelineValidationTest { - getVertexStateWithOutputs(outputs: string[]): GPUVertexState { + getVertexStateWithOutputs(outputs: readonly string[]): GPUVertexState { return { module: this.device.createShaderModule({ code: ` @@ -32,7 +32,7 @@ class InterStageMatchingValidationTest extends CreateRenderPipelineValidationTes } getFragmentStateWithInputs( - inputs: string[], + inputs: readonly string[], hasBuiltinPosition: boolean = false ): GPUFragmentState { return { diff --git a/src/webgpu/format_info.ts b/src/webgpu/format_info.ts index 242549a8b6c9..9333afed1352 100644 --- a/src/webgpu/format_info.ts +++ b/src/webgpu/format_info.ts @@ -1255,7 +1255,7 @@ export const kFeaturesForFormats = getFeaturesForFormats(kTextureFormats); /** * Given an array of texture formats return the number of bytes per sample. */ -export function computeBytesPerSampleFromFormats(formats: GPUTextureFormat[]) { +export function computeBytesPerSampleFromFormats(formats: readonly GPUTextureFormat[]) { let bytesPerSample = 0; for (const format of formats) { const info = kTextureFormatInfo[format]; diff --git a/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts b/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts index 1765ce3d95cb..0f703f088970 100644 --- a/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts @@ -12,11 +12,11 @@ import { onlyConstInputSource, run } from '../expression.js'; import { abstractBinary } from './binary.js'; -const additionVectorScalarInterval = (v: number[], s: number): FPVector => { +const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.abstract.toVector(v.map(e => FP.abstract.additionInterval(e, s))); }; -const additionScalarVectorInterval = (s: number, v: number[]): FPVector => { +const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.abstract.toVector(v.map(e => FP.abstract.additionInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/af_division.spec.ts b/src/webgpu/shader/execution/expression/binary/af_division.spec.ts index e473acb20d8f..4c1765d20337 100644 --- a/src/webgpu/shader/execution/expression/binary/af_division.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/af_division.spec.ts @@ -12,11 +12,11 @@ import { onlyConstInputSource, run } from '../expression.js'; import { abstractBinary } from './binary.js'; -const divisionVectorScalarInterval = (v: number[], s: number): FPVector => { +const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.abstract.toVector(v.map(e => FP.abstract.divisionInterval(e, s))); }; -const divisionScalarVectorInterval = (s: number, v: number[]): FPVector => { +const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.abstract.toVector(v.map(e => FP.abstract.divisionInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts b/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts index 83d60aaa9a23..6b1581270346 100644 --- a/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts @@ -12,11 +12,11 @@ import { onlyConstInputSource, run } from '../expression.js'; import { abstractBinary } from './binary.js'; -const multiplicationVectorScalarInterval = (v: number[], s: number): FPVector => { +const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.abstract.toVector(v.map(e => FP.abstract.multiplicationInterval(e, s))); }; -const multiplicationScalarVectorInterval = (s: number, v: number[]): FPVector => { +const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.abstract.toVector(v.map(e => FP.abstract.multiplicationInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts b/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts index f6f343a57621..b4ce930bdb25 100644 --- a/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts @@ -12,11 +12,11 @@ import { onlyConstInputSource, run } from '../expression.js'; import { abstractBinary } from './binary.js'; -const remainderVectorScalarInterval = (v: number[], s: number): FPVector => { +const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.abstract.toVector(v.map(e => FP.abstract.remainderInterval(e, s))); }; -const remainderScalarVectorInterval = (s: number, v: number[]): FPVector => { +const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.abstract.toVector(v.map(e => FP.abstract.remainderInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts b/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts index c2d4cd90941c..00dc66feb951 100644 --- a/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts @@ -12,11 +12,11 @@ import { onlyConstInputSource, run } from '../expression.js'; import { abstractBinary } from './binary.js'; -const subtractionVectorScalarInterval = (v: number[], s: number): FPVector => { +const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.abstract.toVector(v.map(e => FP.abstract.subtractionInterval(e, s))); }; -const subtractionScalarVectorInterval = (s: number, v: number[]): FPVector => { +const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.abstract.toVector(v.map(e => FP.abstract.subtractionInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts b/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts index e285277b5cf4..8948f9049963 100644 --- a/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts @@ -12,11 +12,11 @@ import { allInputSources, run } from '../expression.js'; import { binary, compoundBinary } from './binary.js'; -const additionVectorScalarInterval = (v: number[], s: number): FPVector => { +const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.f16.toVector(v.map(e => FP.f16.additionInterval(e, s))); }; -const additionScalarVectorInterval = (s: number, v: number[]): FPVector => { +const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.f16.toVector(v.map(e => FP.f16.additionInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts b/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts index 346c38499df2..c3b8fc04dbb7 100644 --- a/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts @@ -12,11 +12,11 @@ import { allInputSources, run } from '../expression.js'; import { binary, compoundBinary } from './binary.js'; -const divisionVectorScalarInterval = (v: number[], s: number): FPVector => { +const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.f16.toVector(v.map(e => FP.f16.divisionInterval(e, s))); }; -const divisionScalarVectorInterval = (s: number, v: number[]): FPVector => { +const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.f16.toVector(v.map(e => FP.f16.divisionInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts b/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts index a2985ec0303f..10041fbc173f 100644 --- a/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts @@ -12,11 +12,11 @@ import { allInputSources, run } from '../expression.js'; import { binary, compoundBinary } from './binary.js'; -const multiplicationVectorScalarInterval = (v: number[], s: number): FPVector => { +const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.f16.toVector(v.map(e => FP.f16.multiplicationInterval(e, s))); }; -const multiplicationScalarVectorInterval = (s: number, v: number[]): FPVector => { +const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.f16.toVector(v.map(e => FP.f16.multiplicationInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts b/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts index 5a9de5d84e1c..801b84904b84 100644 --- a/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts @@ -12,11 +12,11 @@ import { allInputSources, run } from '../expression.js'; import { binary, compoundBinary } from './binary.js'; -const remainderVectorScalarInterval = (v: number[], s: number): FPVector => { +const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.f16.toVector(v.map(e => FP.f16.remainderInterval(e, s))); }; -const remainderScalarVectorInterval = (s: number, v: number[]): FPVector => { +const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.f16.toVector(v.map(e => FP.f16.remainderInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts b/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts index 81c0d94845c6..a64d5568375f 100644 --- a/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts @@ -12,11 +12,11 @@ import { allInputSources, run } from '../expression.js'; import { binary, compoundBinary } from './binary.js'; -const subtractionVectorScalarInterval = (v: number[], s: number): FPVector => { +const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.f16.toVector(v.map(e => FP.f16.subtractionInterval(e, s))); }; -const subtractionScalarVectorInterval = (s: number, v: number[]): FPVector => { +const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.f16.toVector(v.map(e => FP.f16.subtractionInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts b/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts index 53051b29e364..65739f67ca6c 100644 --- a/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts @@ -12,11 +12,11 @@ import { allInputSources, run } from '../expression.js'; import { binary, compoundBinary } from './binary.js'; -const additionVectorScalarInterval = (v: number[], s: number): FPVector => { +const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.f32.toVector(v.map(e => FP.f32.additionInterval(e, s))); }; -const additionScalarVectorInterval = (s: number, v: number[]): FPVector => { +const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.f32.toVector(v.map(e => FP.f32.additionInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts b/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts index f2d5b6a1c70a..bd3793bf8a66 100644 --- a/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts @@ -12,11 +12,11 @@ import { allInputSources, run } from '../expression.js'; import { binary, compoundBinary } from './binary.js'; -const divisionVectorScalarInterval = (v: number[], s: number): FPVector => { +const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.f32.toVector(v.map(e => FP.f32.divisionInterval(e, s))); }; -const divisionScalarVectorInterval = (s: number, v: number[]): FPVector => { +const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.f32.toVector(v.map(e => FP.f32.divisionInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts b/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts index 406642fcb553..38da08fd3e77 100644 --- a/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts @@ -12,11 +12,11 @@ import { allInputSources, run } from '../expression.js'; import { binary, compoundBinary } from './binary.js'; -const multiplicationVectorScalarInterval = (v: number[], s: number): FPVector => { +const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.f32.toVector(v.map(e => FP.f32.multiplicationInterval(e, s))); }; -const multiplicationScalarVectorInterval = (s: number, v: number[]): FPVector => { +const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.f32.toVector(v.map(e => FP.f32.multiplicationInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts b/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts index 44058939b83a..390a7f34266c 100644 --- a/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts @@ -12,11 +12,11 @@ import { allInputSources, run } from '../expression.js'; import { binary, compoundBinary } from './binary.js'; -const remainderVectorScalarInterval = (v: number[], s: number): FPVector => { +const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.f32.toVector(v.map(e => FP.f32.remainderInterval(e, s))); }; -const remainderScalarVectorInterval = (s: number, v: number[]): FPVector => { +const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.f32.toVector(v.map(e => FP.f32.remainderInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts b/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts index 0fbb2e8d7107..91e06b7de8c1 100644 --- a/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts +++ b/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts @@ -12,11 +12,11 @@ import { allInputSources, run } from '../expression.js'; import { binary, compoundBinary } from './binary.js'; -const subtractionVectorScalarInterval = (v: number[], s: number): FPVector => { +const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => { return FP.f32.toVector(v.map(e => FP.f32.subtractionInterval(e, s))); }; -const subtractionScalarVectorInterval = (s: number, v: number[]): FPVector => { +const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => { return FP.f32.toVector(v.map(e => FP.f32.subtractionInterval(s, e))); }; diff --git a/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts index 7f84f016683b..390129f2c73a 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts @@ -123,7 +123,7 @@ const f16ZerosInterval: FPInterval = new FPInterval('f16', -0.0, 0.0); * @returns an u32 whose lower and higher 16bits are the two elements of the * given array of two u16 respectively, in little-endian. */ -function u16x2ToU32(u16x2: number[]): number { +function u16x2ToU32(u16x2: readonly number[]): number { assert(u16x2.length === 2); // Create a DataView with 4 bytes buffer. const buffer = new ArrayBuffer(4); @@ -531,7 +531,7 @@ function possible32BitScalarIntervalsFromF16x2( } const possibleU16Bits = f16x2InU16x2.map(possibleBitsInU16FromFiniteF16InU16); const possibleExpectations = cartesianProduct(...possibleU16Bits).flatMap( - (possibleBitsU16x2: number[]) => { + (possibleBitsU16x2: readonly number[]) => { assert(possibleBitsU16x2.length === 2); return expectationsForValue(reinterpretFromU32(u16x2ToU32(possibleBitsU16x2))); } diff --git a/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts index 47aa4604498f..0113fd656f8d 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts @@ -97,7 +97,7 @@ function generateIntegerTestCases( } function generateFloatTestCases( - test_values: Array, + test_values: readonly number[], trait: 'f32' | 'f16' | 'abstract', stage: 'const' | 'non-const' ): Array { diff --git a/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts index f2a6b9584e96..6b6794fb9ff0 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts @@ -7,6 +7,7 @@ Returns e1 if dot(e2,e3) is negative, and -e1 otherwise. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { ROArrayArray } from '../../../../../../common/util/types.js'; import { GPUTest } from '../../../../../gpu_test.js'; import { anyOf } from '../../../../../util/compare.js'; import { toVector, TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js'; @@ -41,9 +42,9 @@ export const g = makeTestGroup(GPUTest); * */ function makeCase( kind: FPKind, - x: number[], - y: number[], - z: number[], + x: readonly number[], + y: readonly number[], + z: readonly number[], check: IntervalFilter ): Case | undefined { const fp = FP[kind]; @@ -81,9 +82,9 @@ function makeCase( */ function generateCases( kind: FPKind, - xs: number[][], - ys: number[][], - zs: number[][], + xs: ROArrayArray, + ys: ROArrayArray, + zs: ROArrayArray, check: IntervalFilter ): Case[] { // Cannot use `cartesianProduct` here due to heterogeneous param types diff --git a/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts index 3d74fc354799..ffe672b08cf9 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts @@ -56,19 +56,19 @@ function expBuilder(): ShaderBuilder { } /* @returns a fract Case for a given scalar or vector input */ -function makeVectorCaseFract(v: number | number[], trait: 'f32' | 'f16'): Case { +function makeVectorCaseFract(v: number | readonly number[], trait: 'f32' | 'f16'): Case { const fp = FP[trait]; - let toInput: (n: number[]) => Scalar | Vector; - let toOutput: (n: number[]) => Scalar | Vector; + let toInput: (n: readonly number[]) => Scalar | Vector; + let toOutput: (n: readonly number[]) => Scalar | Vector; if (v instanceof Array) { // Input is vector - toInput = (n: number[]) => toVector(n, fp.scalarBuilder); - toOutput = (n: number[]) => toVector(n, fp.scalarBuilder); + toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder); + toOutput = (n: readonly number[]) => toVector(n, fp.scalarBuilder); } else { // Input is scalar, also wrap it in an array. v = [v]; - toInput = (n: number[]) => fp.scalarBuilder(n[0]); - toOutput = (n: number[]) => fp.scalarBuilder(n[0]); + toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]); + toOutput = (n: readonly number[]) => fp.scalarBuilder(n[0]); } v = v.map(fp.quantize); @@ -84,19 +84,19 @@ function makeVectorCaseFract(v: number | number[], trait: 'f32' | 'f16'): Case { } /* @returns an exp Case for a given scalar or vector input */ -function makeVectorCaseExp(v: number | number[], trait: 'f32' | 'f16'): Case { +function makeVectorCaseExp(v: number | readonly number[], trait: 'f32' | 'f16'): Case { const fp = FP[trait]; - let toInput: (n: number[]) => Scalar | Vector; - let toOutput: (n: number[]) => Scalar | Vector; + let toInput: (n: readonly number[]) => Scalar | Vector; + let toOutput: (n: readonly number[]) => Scalar | Vector; if (v instanceof Array) { // Input is vector - toInput = (n: number[]) => toVector(n, fp.scalarBuilder); - toOutput = (n: number[]) => toVector(n, i32); + toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder); + toOutput = (n: readonly number[]) => toVector(n, i32); } else { // Input is scalar, also wrap it in an array. v = [v]; - toInput = (n: number[]) => fp.scalarBuilder(n[0]); - toOutput = (n: number[]) => i32(n[0]); + toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]); + toOutput = (n: readonly number[]) => i32(n[0]); } v = v.map(fp.quantize); diff --git a/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts index 5bc4a54ab7d0..1a3d8a285091 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts @@ -86,7 +86,7 @@ function makeScalarCaseWhole(kind: FPKind, n: number): Case { } /** @returns a fract Case for a given vector input */ -function makeVectorCaseFract(kind: FPKind, v: number[]): Case { +function makeVectorCaseFract(kind: FPKind, v: readonly number[]): Case { const fp = FP[kind]; v = v.map(fp.quantize); const fs = v.map(e => { @@ -97,7 +97,7 @@ function makeVectorCaseFract(kind: FPKind, v: number[]): Case { } /** @returns a whole Case for a given vector input */ -function makeVectorCaseWhole(kind: FPKind, v: number[]): Case { +function makeVectorCaseWhole(kind: FPKind, v: readonly number[]): Case { const fp = FP[kind]; v = v.map(fp.quantize); const ws = v.map(e => { diff --git a/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts index 9cc726276b42..be1a76b4372f 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts @@ -11,6 +11,7 @@ vector e3*e1- (e3* dot(e2,e1) + sqrt(k)) *e2. `; import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; +import { ROArrayArray } from '../../../../../../common/util/types.js'; import { GPUTest } from '../../../../../gpu_test.js'; import { toVector, TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js'; import { FP, FPKind } from '../../../../../util/floating_point.js'; @@ -41,8 +42,8 @@ export const g = makeTestGroup(GPUTest); * */ function makeCase( kind: FPKind, - i: number[], - s: number[], + i: readonly number[], + s: readonly number[], r: number, check: IntervalFilter ): Case | undefined { @@ -72,9 +73,9 @@ function makeCase( */ function generateCases( kind: FPKind, - param_is: number[][], - param_ss: number[][], - param_rs: number[], + param_is: ROArrayArray, + param_ss: ROArrayArray, + param_rs: readonly number[], check: IntervalFilter ): Case[] { // Cannot use `cartesianProduct` here due to heterogeneous param types diff --git a/src/webgpu/shader/execution/expression/expression.ts b/src/webgpu/shader/execution/expression/expression.ts index e78081e70eab..8765476831d4 100644 --- a/src/webgpu/shader/execution/expression/expression.ts +++ b/src/webgpu/shader/execution/expression/expression.ts @@ -1,4 +1,5 @@ import { globalTestConfig } from '../../../../common/framework/test_config.js'; +import { ROArrayArray } from '../../../../common/util/types.js'; import { assert, objectEquals, unreachable } from '../../../../common/util/util.js'; import { GPUTest } from '../../../gpu_test.js'; import { compare, Comparator, ComparatorImpl } from '../../../util/compare.js'; @@ -27,7 +28,12 @@ import { quantizeToU32, } from '../../../util/math.js'; -export type Expectation = Value | FPInterval | FPInterval[] | FPInterval[][] | Comparator; +export type Expectation = + | Value + | FPInterval + | readonly FPInterval[] + | ROArrayArray + | Comparator; /** @returns if this Expectation actually a Comparator */ export function isComparator(e: Expectation): e is Comparator { @@ -52,7 +58,7 @@ export function toComparator(input: Expectation): Comparator { /** Case is a single expression test case. */ export type Case = { // The input value(s) - input: Value | Array; + input: Value | ReadonlyArray; // The expected result, or function to check the result expected: Expectation; }; @@ -466,7 +472,7 @@ function submitBatch( * transformed with @p fn. * If @p v is not an array, then @p fn is called with (v, 0). */ -function map(v: T | T[], fn: (value: T, index?: number) => U): U[] { +function map(v: T | readonly T[], fn: (value: T, index?: number) => U): U[] { if (v instanceof Array) { return v.map(fn); } @@ -588,7 +594,7 @@ function wgslHeader(parameterTypes: Array, resultType: Type) { * ExpressionBuilder returns the WGSL used to evaluate an expression with the * given input values. */ -export type ExpressionBuilder = (values: Array) => string; +export type ExpressionBuilder = (values: ReadonlyArray) => string; /** * Returns a ShaderBuilder that builds a basic expression test shader. @@ -1212,8 +1218,8 @@ export interface BinaryOp { * @param scalarize function to convert numbers to Scalars */ function generateScalarBinaryToScalarCases( - param0s: number[], - param1s: number[], + param0s: readonly number[], + param1s: readonly number[], op: BinaryOp, quantize: QuantizeFunc, scalarize: ScalarBuilder @@ -1235,7 +1241,11 @@ function generateScalarBinaryToScalarCases( * @param param1s array of inputs to try for the second param * @param op callback called on each pair of inputs to produce each case */ -export function generateBinaryToI32Cases(param0s: number[], param1s: number[], op: BinaryOp) { +export function generateBinaryToI32Cases( + param0s: readonly number[], + param1s: readonly number[], + op: BinaryOp +) { return generateScalarBinaryToScalarCases(param0s, param1s, op, quantizeToI32, i32); } @@ -1245,7 +1255,11 @@ export function generateBinaryToI32Cases(param0s: number[], param1s: number[], o * @param param1s array of inputs to try for the second param * @param op callback called on each pair of inputs to produce each case */ -export function generateBinaryToU32Cases(param0s: number[], param1s: number[], op: BinaryOp) { +export function generateBinaryToU32Cases( + param0s: readonly number[], + param1s: readonly number[], + op: BinaryOp +) { return generateScalarBinaryToScalarCases(param0s, param1s, op, quantizeToU32, u32); } @@ -1259,7 +1273,7 @@ export function generateBinaryToU32Cases(param0s: number[], param1s: number[], o */ function makeScalarVectorBinaryToVectorCase( scalar: number, - vector: number[], + vector: readonly number[], op: BinaryOp, quantize: QuantizeFunc, scalarize: ScalarBuilder @@ -1272,7 +1286,7 @@ function makeScalarVectorBinaryToVectorCase( } return { input: [scalarize(scalar), new Vector(vector.map(scalarize))], - expected: new Vector((result as number[]).map(scalarize)), + expected: new Vector((result as readonly number[]).map(scalarize)), }; } @@ -1285,8 +1299,8 @@ function makeScalarVectorBinaryToVectorCase( * @param scalarize function to convert numbers to Scalars */ function generateScalarVectorBinaryToVectorCases( - scalars: number[], - vectors: number[][], + scalars: readonly number[], + vectors: ROArrayArray, op: BinaryOp, quantize: QuantizeFunc, scalarize: ScalarBuilder @@ -1312,7 +1326,7 @@ function generateScalarVectorBinaryToVectorCases( * @param scalarize function to convert numbers to Scalars */ function makeVectorScalarBinaryToVectorCase( - vector: number[], + vector: readonly number[], scalar: number, op: BinaryOp, quantize: QuantizeFunc, @@ -1326,7 +1340,7 @@ function makeVectorScalarBinaryToVectorCase( } return { input: [new Vector(vector.map(scalarize)), scalarize(scalar)], - expected: new Vector((result as number[]).map(scalarize)), + expected: new Vector((result as readonly number[]).map(scalarize)), }; } @@ -1339,8 +1353,8 @@ function makeVectorScalarBinaryToVectorCase( * @param scalarize function to convert numbers to Scalars */ function generateVectorScalarBinaryToVectorCases( - vectors: number[][], - scalars: number[], + vectors: ROArrayArray, + scalars: readonly number[], op: BinaryOp, quantize: QuantizeFunc, scalarize: ScalarBuilder @@ -1364,8 +1378,8 @@ function generateVectorScalarBinaryToVectorCases( * @param op he op to apply to each pair of scalar and vector */ export function generateU32VectorBinaryToVectorCases( - scalars: number[], - vectors: number[][], + scalars: readonly number[], + vectors: ROArrayArray, op: BinaryOp ): Case[] { return generateScalarVectorBinaryToVectorCases(scalars, vectors, op, quantizeToU32, u32); @@ -1378,8 +1392,8 @@ export function generateU32VectorBinaryToVectorCases( * @param op he op to apply to each pair of vector and scalar */ export function generateVectorU32BinaryToVectorCases( - vectors: number[][], - scalars: number[], + vectors: ROArrayArray, + scalars: readonly number[], op: BinaryOp ): Case[] { return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToU32, u32); @@ -1392,8 +1406,8 @@ export function generateVectorU32BinaryToVectorCases( * @param op he op to apply to each pair of scalar and vector */ export function generateI32VectorBinaryToVectorCases( - scalars: number[], - vectors: number[][], + scalars: readonly number[], + vectors: ROArrayArray, op: BinaryOp ): Case[] { return generateScalarVectorBinaryToVectorCases(scalars, vectors, op, quantizeToI32, i32); @@ -1406,8 +1420,8 @@ export function generateI32VectorBinaryToVectorCases( * @param op he op to apply to each pair of vector and scalar */ export function generateVectorI32BinaryToVectorCases( - vectors: number[][], - scalars: number[], + vectors: ROArrayArray, + scalars: readonly number[], op: BinaryOp ): Case[] { return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToI32, i32); diff --git a/src/webgpu/shader/execution/zero_init.spec.ts b/src/webgpu/shader/execution/zero_init.spec.ts index ad7b095b8995..d209c1325420 100644 --- a/src/webgpu/shader/execution/zero_init.spec.ts +++ b/src/webgpu/shader/execution/zero_init.spec.ts @@ -15,7 +15,7 @@ import { type ShaderTypeInfo = | { type: 'container'; containerType: 'array'; elementType: ShaderTypeInfo; length: number } - | { type: 'container'; containerType: 'struct'; members: ShaderTypeInfo[] } + | { type: 'container'; containerType: 'struct'; members: readonly ShaderTypeInfo[] } | { type: 'container'; containerType: keyof typeof kVectorContainerTypeInfo | keyof typeof kMatrixContainerTypeInfo; diff --git a/src/webgpu/util/conversion.ts b/src/webgpu/util/conversion.ts index f94c2b918806..e78af9783288 100644 --- a/src/webgpu/util/conversion.ts +++ b/src/webgpu/util/conversion.ts @@ -1,4 +1,5 @@ import { Colors } from '../../common/util/colors.js'; +import { ROArrayArray } from '../../common/util/types.js'; import { assert, objectEquals, TypedArrayBufferView, unreachable } from '../../common/util/util.js'; import { Float16Array } from '../../external/petamoriken/float16/float16.js'; @@ -353,7 +354,7 @@ export function unpackRGB9E5UFloat(encoded: number): { R: number; G: number; B: export function pack2x16float(x: number, y: number): (number | undefined)[] { // Generates all possible valid u16 bit fields for a given f32 to f16 conversion. // Assumes FTZ for both the f32 and f16 value is allowed. - const generateU16s = (n: number): number[] => { + const generateU16s = (n: number): readonly number[] => { let contains_subnormals = isSubnormalNumberF32(n); const n_f16s = correctlyRoundedF16(n); contains_subnormals ||= n_f16s.some(isSubnormalNumberF16); @@ -668,7 +669,7 @@ export class VectorType { } /** Constructs a Vector of this type with the given values */ - public create(value: number | number[]): Vector { + public create(value: number | readonly number[]): Vector { if (value instanceof Array) { assert(value.length === this.width); } else { @@ -1184,7 +1185,7 @@ export function vec4(x: Scalar, y: Scalar, z: Scalar, w: Scalar) { * @param v array of numbers to be converted, must contain 2, 3 or 4 elements * @param op function to convert from number to Scalar, e.g. 'f32` */ -export function toVector(v: number[], op: (n: number) => Scalar): Vector { +export function toVector(v: readonly number[], op: (n: number) => Scalar): Vector { switch (v.length) { case 2: return vec2(op(v[0]), op(v[1])); @@ -1266,7 +1267,7 @@ export class Matrix { * be of the same length. All Arrays must have 2, 3, or 4 elements. * @param op function to convert from number to Scalar, e.g. 'f32` */ -export function toMatrix(m: number[][], op: (n: number) => Scalar): Matrix { +export function toMatrix(m: ROArrayArray, op: (n: number) => Scalar): Matrix { const cols = m.length; const rows = m[0].length; const elements: Scalar[][] = [...Array(cols)].map(_ => [...Array(rows)]); @@ -1291,13 +1292,13 @@ export type SerializedValueScalar = { export type SerializedValueVector = { kind: 'vector'; type: ScalarKind; - value: boolean[] | number[]; + value: boolean[] | readonly number[]; }; export type SerializedValueMatrix = { kind: 'matrix'; type: ScalarKind; - value: number[][]; + value: ROArrayArray; }; export type SerializedValue = SerializedValueScalar | SerializedValueVector | SerializedValueMatrix; @@ -1326,7 +1327,7 @@ export function serializeValue(v: Value): SerializedValue { return { kind: 'vector', type: kind, - value: v.elements.map(e => value(kind, e)) as boolean[] | number[], + value: v.elements.map(e => value(kind, e)) as boolean[] | readonly number[], }; } if (v instanceof Matrix) { @@ -1334,7 +1335,7 @@ export function serializeValue(v: Value): SerializedValue { return { kind: 'matrix', type: kind, - value: v.elements.map(c => c.map(r => value(kind, r))) as number[][], + value: v.elements.map(c => c.map(r => value(kind, r))) as ROArrayArray, }; } diff --git a/src/webgpu/util/floating_point.ts b/src/webgpu/util/floating_point.ts index 70109f5cab9e..18a640f43403 100644 --- a/src/webgpu/util/floating_point.ts +++ b/src/webgpu/util/floating_point.ts @@ -1,3 +1,4 @@ +import { ROArrayArray, ROArrayArrayArray } from '../../common/util/types.js'; import { assert, unreachable } from '../../common/util/util.js'; import { Float16Array } from '../../external/petamoriken/float16/float16.js'; import { Case, IntervalFilter } from '../shader/execution/expression/expression.js'; @@ -59,7 +60,7 @@ export type FPKind = 'f32' | 'f16' | 'abstract'; * two elements, the first is the lower bound of the interval and the second is * the upper bound. */ -export type IntervalBounds = [number] | [number, number]; +export type IntervalBounds = readonly [number] | readonly [number, number]; /** Represents a closed interval of floating point numbers */ export class FPInterval { @@ -224,47 +225,54 @@ export type FPVector = | [FPInterval, FPInterval, FPInterval, FPInterval]; /** Shorthand for an Array of Arrays that contains a column-major matrix */ -type Array2D = T[][]; +type Array2D = ROArrayArray; /** * Representation of a matCxR of floating point intervals as an array of arrays * of FPIntervals. This maps onto the WGSL concept of matrix. Internally */ export type FPMatrix = - | [[FPInterval, FPInterval], [FPInterval, FPInterval]] - | [[FPInterval, FPInterval], [FPInterval, FPInterval], [FPInterval, FPInterval]] - | [ - [FPInterval, FPInterval], - [FPInterval, FPInterval], - [FPInterval, FPInterval], - [FPInterval, FPInterval] + | readonly [readonly [FPInterval, FPInterval], readonly [FPInterval, FPInterval]] + | readonly [ + readonly [FPInterval, FPInterval], + readonly [FPInterval, FPInterval], + readonly [FPInterval, FPInterval] ] - | [[FPInterval, FPInterval, FPInterval], [FPInterval, FPInterval, FPInterval]] - | [ - [FPInterval, FPInterval, FPInterval], - [FPInterval, FPInterval, FPInterval], - [FPInterval, FPInterval, FPInterval] + | readonly [ + readonly [FPInterval, FPInterval], + readonly [FPInterval, FPInterval], + readonly [FPInterval, FPInterval], + readonly [FPInterval, FPInterval] ] - | [ - [FPInterval, FPInterval, FPInterval], - [FPInterval, FPInterval, FPInterval], - [FPInterval, FPInterval, FPInterval], - [FPInterval, FPInterval, FPInterval] + | readonly [ + readonly [FPInterval, FPInterval, FPInterval], + readonly [FPInterval, FPInterval, FPInterval] ] - | [ - [FPInterval, FPInterval, FPInterval, FPInterval], - [FPInterval, FPInterval, FPInterval, FPInterval] + | readonly [ + readonly [FPInterval, FPInterval, FPInterval], + readonly [FPInterval, FPInterval, FPInterval], + readonly [FPInterval, FPInterval, FPInterval] ] - | [ - [FPInterval, FPInterval, FPInterval, FPInterval], - [FPInterval, FPInterval, FPInterval, FPInterval], - [FPInterval, FPInterval, FPInterval, FPInterval] + | readonly [ + readonly [FPInterval, FPInterval, FPInterval], + readonly [FPInterval, FPInterval, FPInterval], + readonly [FPInterval, FPInterval, FPInterval], + readonly [FPInterval, FPInterval, FPInterval] ] - | [ - [FPInterval, FPInterval, FPInterval, FPInterval], - [FPInterval, FPInterval, FPInterval, FPInterval], - [FPInterval, FPInterval, FPInterval, FPInterval], - [FPInterval, FPInterval, FPInterval, FPInterval] + | readonly [ + readonly [FPInterval, FPInterval, FPInterval, FPInterval], + readonly [FPInterval, FPInterval, FPInterval, FPInterval] + ] + | readonly [ + readonly [FPInterval, FPInterval, FPInterval, FPInterval], + readonly [FPInterval, FPInterval, FPInterval, FPInterval], + readonly [FPInterval, FPInterval, FPInterval, FPInterval] + ] + | readonly [ + readonly [FPInterval, FPInterval, FPInterval, FPInterval], + readonly [FPInterval, FPInterval, FPInterval, FPInterval], + readonly [FPInterval, FPInterval, FPInterval, FPInterval], + readonly [FPInterval, FPInterval, FPInterval, FPInterval] ]; // Utilities @@ -272,7 +280,7 @@ export type FPMatrix = /** @returns input with an appended 0, if inputs contains non-zero subnormals */ // When f16 traits is defined, this can be replaced with something like // `FP.f16..addFlushIfNeeded` -function addFlushedIfNeededF16(values: number[]): number[] { +function addFlushedIfNeededF16(values: readonly number[]): readonly number[] { return values.some(v => v !== 0 && isSubnormalNumberF16(v)) ? values.concat(0) : values; } @@ -343,8 +351,8 @@ interface ScalarPairToIntervalOp { /** Domain for a ScalarPairToInterval implementation */ interface ScalarPairToIntervalDomain { // Arrays to support discrete valid domain intervals - x: FPInterval[]; - y: FPInterval[]; + x: readonly FPInterval[]; + y: readonly FPInterval[]; } /** @@ -384,7 +392,7 @@ export interface ScalarToVector { * from tests. */ export interface VectorToInterval { - (x: number[]): FPInterval; + (x: readonly number[]): FPInterval; } /** Operation used to implement a VectorToInterval */ @@ -400,7 +408,7 @@ interface VectorToIntervalOp { * from tests. */ export interface VectorPairToInterval { - (x: number[], y: number[]): FPInterval; + (x: readonly number[], y: readonly number[]): FPInterval; } /** Operation used to implement a VectorPairToInterval */ @@ -416,7 +424,7 @@ interface VectorPairToIntervalOp { * from tests. */ export interface VectorToVector { - (x: number[]): FPVector; + (x: readonly number[]): FPVector; } /** Operation used to implement a VectorToVector */ @@ -433,7 +441,7 @@ interface VectorToVectorOp { * from tests. */ export interface VectorPairToVector { - (x: number[], y: number[]): FPVector; + (x: readonly number[], y: readonly number[]): FPVector; } /** Operation used to implement a VectorPairToVector */ @@ -450,7 +458,7 @@ interface VectorPairToVectorOp { * from tests. */ export interface VectorScalarToVector { - (x: number[], y: number): FPVector; + (x: readonly number[], y: number): FPVector; } /** @@ -460,7 +468,7 @@ export interface VectorScalarToVector { * from tests. */ export interface ScalarVectorToVector { - (x: number, y: number[]): FPVector; + (x: number, y: readonly number[]): FPVector; } /** @@ -525,7 +533,7 @@ export interface ScalarMatrixToMatrix { * from tests. */ export interface MatrixVectorToVector { - (x: Array2D, y: number[]): FPVector; + (x: Array2D, y: readonly number[]): FPVector; } /** @@ -535,7 +543,7 @@ export interface MatrixVectorToVector { * from tests. */ export interface VectorMatrixToVector { - (x: number[], y: Array2D): FPVector; + (x: readonly number[], y: Array2D): FPVector; } // Traits @@ -684,7 +692,7 @@ export abstract class FPTraits { * @returns an interval with the tightest bounds that includes all provided * intervals */ - public spanIntervals(...intervals: FPInterval[]): FPInterval { + public spanIntervals(...intervals: readonly FPInterval[]): FPInterval { assert(intervals.length > 0, `span of an empty list of FPIntervals is not allowed`); assert( intervals.every(i => i.kind === this.kind), @@ -700,7 +708,7 @@ export abstract class FPTraits { } /** Narrow an array of values to FPVector if possible */ - public isVector(v: (number | IntervalBounds | FPInterval)[]): v is FPVector { + public isVector(v: ReadonlyArray): v is FPVector { if (v.every(e => e instanceof FPInterval && e.kind === this.kind)) { return v.length === 2 || v.length === 3 || v.length === 4; } @@ -708,13 +716,13 @@ export abstract class FPTraits { } /** @returns an FPVector representation of an array of values if possible */ - public toVector(v: (number | IntervalBounds | FPInterval)[]): FPVector { + public toVector(v: ReadonlyArray): FPVector { if (this.isVector(v) && v.every(e => e.kind === this.kind)) { return v; } const f = v.map(e => this.toInterval(e)); - // The return of the map above is a FPInterval[], which needs to be narrowed + // The return of the map above is a readonly FPInterval[], which needs to be narrowed // to FPVector, since FPVector is defined as fixed length tuples. if (this.isVector(f)) { return f; @@ -751,13 +759,13 @@ export abstract class FPTraits { if (!m.every(c => c.every(e => e instanceof FPInterval && e.kind === this.kind))) { return false; } - // At this point m guaranteed to be a FPInterval[][], but maybe typed as a + // At this point m guaranteed to be a ROArrayArray, but maybe typed as a // FPVector[]. // Coercing the type since FPVector[] is functionally equivalent to - // FPInterval[][] for .length and .every, but they are type compatible, + // ROArrayArray for .length and .every, but they are type compatible, // since tuples are not equivalent to arrays, so TS considers c in .every to // be unresolvable below, even though our usage is safe. - m = m as FPInterval[][]; + m = m as ROArrayArray; if (m.length > 4 || m.length < 2) { return false; @@ -784,7 +792,7 @@ export abstract class FPTraits { const result = map2DArray(m, this.toInterval.bind(this)); - // The return of the map above is a FPInterval[][], which needs to be + // The return of the map above is a ROArrayArray, which needs to be // narrowed to FPMatrix, since FPMatrix is defined as fixed length tuples. if (this.isMatrix(result)) { return result; @@ -808,7 +816,7 @@ export abstract class FPTraits { `Matrix span is not defined for Matrices of differing dimensions` ); - const result: Array2D = [...Array(num_cols)].map(_ => [...Array(num_rows)]); + const result: FPInterval[][] = [...Array(num_cols)].map(_ => [...Array(num_rows)]); for (let i = 0; i < num_cols; i++) { for (let j = 0; j < num_rows; j++) { result[i][j] = this.spanIntervals(...ms.map(m => m[i][j])); @@ -819,7 +827,7 @@ export abstract class FPTraits { } /** @returns input with an appended 0, if inputs contains non-zero subnormals */ - public addFlushedIfNeeded(values: number[]): number[] { + public addFlushedIfNeeded(values: readonly number[]): readonly number[] { const subnormals = values.filter(this.isSubnormal); const needs_zero = subnormals.length > 0 && subnormals.every(s => s !== 0); return needs_zero ? values.concat(0) : values; @@ -904,22 +912,25 @@ export abstract class FPTraits { /** Stub for vector pair to interval generator */ protected unimplementedVectorPairToInterval( name: string, - _x: (number | FPInterval)[], - _y: (number | FPInterval)[] + _x: readonly (number | FPInterval)[], + _y: readonly (number | FPInterval)[] ): FPInterval { unreachable(`'${name}' is not yet implemented for '${this.kind}'`); } /** Stub for vector to vector generator */ - protected unimplementedVectorToVector(name: string, _x: (number | FPInterval)[]): FPVector { + protected unimplementedVectorToVector( + name: string, + _x: readonly (number | FPInterval)[] + ): FPVector { unreachable(`'${name}' is not yet implemented for '${this.kind}'`); } /** Stub for vector pair to vector generator */ protected unimplementedVectorPairToVector( name: string, - _x: (number | FPInterval)[], - _y: (number | FPInterval)[] + _x: readonly (number | FPInterval)[], + _y: readonly (number | FPInterval)[] ): FPVector { unreachable(`'${name}' is not yet implemented for '${this.kind}'`); } @@ -927,7 +938,7 @@ export abstract class FPTraits { /** Stub for vector-scalar to vector generator */ protected unimplementedVectorScalarToVector( name: string, - _x: (number | FPInterval)[], + _x: readonly (number | FPInterval)[], _y: number | FPInterval ): FPVector { unreachable(`'${name}' is not yet implemented for '${this.kind}'`); @@ -983,7 +994,7 @@ export abstract class FPTraits { protected unimplementedMatrixVectorToVector( name: string, _x: Array2D, - _y: (number | FPInterval)[] + _y: readonly (number | FPInterval)[] ): FPVector { unreachable(`'${name}' is not yet implemented for '${this.kind}'`); } @@ -991,28 +1002,33 @@ export abstract class FPTraits { /** Stub for vector-matrix to vector generator */ protected unimplementedVectorMatrixToVector( name: string, - _x: (number | FPInterval)[], + _x: readonly (number | FPInterval)[], _y: Array2D ): FPVector { unreachable(`'${name}' is not yet implemented for '${this.kind}'`); } /** Stub for distance generator */ - protected unimplementedDistance(_x: number | number[], _y: number | number[]): FPInterval { + protected unimplementedDistance( + _x: number | readonly number[], + _y: number | readonly number[] + ): FPInterval { unreachable(`'distance' is not yet implemented for '${this.kind}'`); } /** Stub for faceForward */ protected unimplementedFaceForward( - _x: number[], - _y: number[], - _z: number[] + _x: readonly number[], + _y: readonly number[], + _z: readonly number[] ): (FPVector | undefined)[] { unreachable(`'faceForward' is not yet implemented for '${this.kind}'`); } /** Stub for length generator */ - protected unimplementedLength(_x: number | FPInterval | number[] | FPVector): FPInterval { + protected unimplementedLength( + _x: number | FPInterval | readonly number[] | FPVector + ): FPInterval { unreachable(`'length' is not yet implemented for '${this.kind}'`); } @@ -1022,7 +1038,11 @@ export abstract class FPTraits { } /** Stub for refract generator */ - protected unimplementedRefract(_i: number[], _s: number[], _r: number): FPVector { + protected unimplementedRefract( + _i: readonly number[], + _s: readonly number[], + _r: number + ): FPVector { unreachable(`'refract' is not yet implemented for '${this.kind}'`); } @@ -1043,7 +1063,7 @@ export abstract class FPTraits { */ public abstract readonly quantize: (n: number) => number; /** @returns all valid roundings of input */ - public abstract readonly correctlyRounded: (n: number) => number[]; + public abstract readonly correctlyRounded: (n: number) => readonly number[]; /** @returns true if input is considered finite, otherwise false */ public abstract readonly isFinite: (n: number) => boolean; /** @returns true if input is considered subnormal, otherwise false */ @@ -1085,7 +1105,7 @@ export abstract class FPTraits { * @param ops callbacks that implement generating an acceptance interval */ public generateScalarToIntervalCases( - params: number[], + params: readonly number[], filter: IntervalFilter, ...ops: ScalarToInterval[] ): Case[] { @@ -1133,8 +1153,8 @@ export abstract class FPTraits { * @param ops callbacks that implement generating an acceptance interval */ public generateScalarPairToIntervalCases( - param0s: number[], - param1s: number[], + param0s: readonly number[], + param1s: readonly number[], filter: IntervalFilter, ...ops: ScalarPairToInterval[] ): Case[] { @@ -1186,9 +1206,9 @@ export abstract class FPTraits { * @param ops callbacks that implement generating an acceptance interval */ public generateScalarTripleToIntervalCases( - param0s: number[], - param1s: number[], - param2s: number[], + param0s: readonly number[], + param1s: readonly number[], + param2s: readonly number[], filter: IntervalFilter, ...ops: ScalarTripleToInterval[] ): Case[] { @@ -1209,7 +1229,7 @@ export abstract class FPTraits { * @param ops callbacks that implement generating an acceptance interval */ private makeVectorToIntervalCase( - param: number[], + param: readonly number[], filter: IntervalFilter, ...ops: VectorToInterval[] ): Case | undefined { @@ -1232,7 +1252,7 @@ export abstract class FPTraits { * @param ops callbacks that implement generating an acceptance interval */ public generateVectorToIntervalCases( - params: number[][], + params: ROArrayArray, filter: IntervalFilter, ...ops: VectorToInterval[] ): Case[] { @@ -1254,8 +1274,8 @@ export abstract class FPTraits { * @param ops callbacks that implement generating an acceptance interval */ private makeVectorPairToIntervalCase( - param0: number[], - param1: number[], + param0: readonly number[], + param1: readonly number[], filter: IntervalFilter, ...ops: VectorPairToInterval[] ): Case | undefined { @@ -1280,8 +1300,8 @@ export abstract class FPTraits { * @param ops callbacks that implement generating an acceptance interval */ public generateVectorPairToIntervalCases( - param0s: number[][], - param1s: number[][], + param0s: ROArrayArray, + param1s: ROArrayArray, filter: IntervalFilter, ...ops: VectorPairToInterval[] ): Case[] { @@ -1302,7 +1322,7 @@ export abstract class FPTraits { * intervals. */ private makeVectorToVectorCase( - param: number[], + param: readonly number[], filter: IntervalFilter, ...ops: VectorToVector[] ): Case | undefined { @@ -1326,7 +1346,7 @@ export abstract class FPTraits { * intervals. */ public generateVectorToVectorCases( - params: number[][], + params: ROArrayArray, filter: IntervalFilter, ...ops: VectorToVector[] ): Case[] { @@ -1349,7 +1369,7 @@ export abstract class FPTraits { */ private makeScalarVectorToVectorCase( scalar: number, - vector: number[], + vector: readonly number[], filter: IntervalFilter, ...ops: ScalarVectorToVector[] ): Case | undefined { @@ -1374,8 +1394,8 @@ export abstract class FPTraits { * @param ops callbacks that implement generating a vector of acceptance intervals */ public generateScalarVectorToVectorCases( - scalars: number[], - vectors: number[][], + scalars: readonly number[], + vectors: ROArrayArray, filter: IntervalFilter, ...ops: ScalarVectorToVector[] ): Case[] { @@ -1401,7 +1421,7 @@ export abstract class FPTraits { * @param ops callbacks that implement generating a vector of acceptance intervals */ private makeVectorScalarToVectorCase( - vector: number[], + vector: readonly number[], scalar: number, filter: IntervalFilter, ...ops: VectorScalarToVector[] @@ -1427,8 +1447,8 @@ export abstract class FPTraits { * @param ops callbacks that implement generating a vector of acceptance intervals */ public generateVectorScalarToVectorCases( - vectors: number[][], - scalars: number[], + vectors: ROArrayArray, + scalars: readonly number[], filter: IntervalFilter, ...ops: VectorScalarToVector[] ): Case[] { @@ -1454,8 +1474,8 @@ export abstract class FPTraits { * intervals. */ private makeVectorPairToVectorCase( - param0: number[], - param1: number[], + param0: readonly number[], + param1: readonly number[], filter: IntervalFilter, ...ops: VectorPairToVector[] ): Case | undefined { @@ -1480,8 +1500,8 @@ export abstract class FPTraits { * intervals. */ public generateVectorPairToVectorCases( - param0s: number[][], - param1s: number[][], + param0s: ROArrayArray, + param1s: ROArrayArray, filter: IntervalFilter, ...ops: VectorPairToVector[] ): Case[] { @@ -1505,8 +1525,8 @@ export abstract class FPTraits { * one component result at a time. */ private makeVectorPairScalarToVectorComponentWiseCase( - param0: number[], - param1: number[], + param0: readonly number[], + param1: readonly number[], param2: number, filter: IntervalFilter, ...componentWiseOps: ScalarTripleToInterval[] @@ -1545,9 +1565,9 @@ export abstract class FPTraits { * @param componentWiseOpscallbacks that implement generating a component-wise acceptance interval */ public generateVectorPairScalarToVectorComponentWiseCase( - param0s: number[][], - param1s: number[][], - param2s: number[], + param0s: ROArrayArray, + param1s: ROArrayArray, + param2s: readonly number[], filter: IntervalFilter, ...componentWiseOps: ScalarTripleToInterval[] ): Case[] { @@ -1579,7 +1599,7 @@ export abstract class FPTraits { * @param ops callbacks that implement generating an acceptance interval */ private makeMatrixToScalarCase( - param: number[][], + param: ROArrayArray, filter: IntervalFilter, ...ops: MatrixToScalar[] ): Case | undefined { @@ -1603,7 +1623,7 @@ export abstract class FPTraits { * @param ops callbacks that implement generating an acceptance interval */ public generateMatrixToScalarCases( - params: number[][][], + params: ROArrayArrayArray, filter: IntervalFilter, ...ops: MatrixToScalar[] ): Case[] { @@ -1624,7 +1644,7 @@ export abstract class FPTraits { * intervals */ private makeMatrixToMatrixCase( - param: number[][], + param: ROArrayArray, filter: IntervalFilter, ...ops: MatrixToMatrix[] ): Case | undefined { @@ -1649,7 +1669,7 @@ export abstract class FPTraits { * intervals */ public generateMatrixToMatrixCases( - params: number[][][], + params: ROArrayArrayArray, filter: IntervalFilter, ...ops: MatrixToMatrix[] ): Case[] { @@ -1671,8 +1691,8 @@ export abstract class FPTraits { * intervals */ private makeMatrixPairToMatrixCase( - param0: number[][], - param1: number[][], + param0: ROArrayArray, + param1: ROArrayArray, filter: IntervalFilter, ...ops: MatrixPairToMatrix[] ): Case | undefined { @@ -1698,8 +1718,8 @@ export abstract class FPTraits { * intervals */ public generateMatrixPairToMatrixCases( - param0s: number[][][], - param1s: number[][][], + param0s: ROArrayArrayArray, + param1s: ROArrayArrayArray, filter: IntervalFilter, ...ops: MatrixPairToMatrix[] ): Case[] { @@ -1721,7 +1741,7 @@ export abstract class FPTraits { * intervals */ private makeMatrixScalarToMatrixCase( - mat: number[][], + mat: ROArrayArray, scalar: number, filter: IntervalFilter, ...ops: MatrixScalarToMatrix[] @@ -1748,8 +1768,8 @@ export abstract class FPTraits { * intervals */ public generateMatrixScalarToMatrixCases( - mats: number[][][], - scalars: number[], + mats: ROArrayArrayArray, + scalars: readonly number[], filter: IntervalFilter, ...ops: MatrixScalarToMatrix[] ): Case[] { @@ -1776,7 +1796,7 @@ export abstract class FPTraits { */ private makeScalarMatrixToMatrixCase( scalar: number, - mat: number[][], + mat: ROArrayArray, filter: IntervalFilter, ...ops: ScalarMatrixToMatrix[] ): Case | undefined { @@ -1802,8 +1822,8 @@ export abstract class FPTraits { * intervals */ public generateScalarMatrixToMatrixCases( - scalars: number[], - mats: number[][][], + scalars: readonly number[], + mats: ROArrayArrayArray, filter: IntervalFilter, ...ops: ScalarMatrixToMatrix[] ): Case[] { @@ -1829,8 +1849,8 @@ export abstract class FPTraits { * intervals */ private makeMatrixVectorToVectorCase( - mat: number[][], - vec: number[], + mat: ROArrayArray, + vec: readonly number[], filter: IntervalFilter, ...ops: MatrixVectorToVector[] ): Case | undefined { @@ -1856,8 +1876,8 @@ export abstract class FPTraits { * intervals */ public generateMatrixVectorToVectorCases( - mats: number[][][], - vecs: number[][], + mats: ROArrayArrayArray, + vecs: ROArrayArray, filter: IntervalFilter, ...ops: MatrixVectorToVector[] ): Case[] { @@ -1883,8 +1903,8 @@ export abstract class FPTraits { * intervals */ private makeVectorMatrixToVectorCase( - vec: number[], - mat: number[][], + vec: readonly number[], + mat: ROArrayArray, filter: IntervalFilter, ...ops: VectorMatrixToVector[] ): Case | undefined { @@ -1910,8 +1930,8 @@ export abstract class FPTraits { * intervals */ public generateVectorMatrixToVectorCases( - vecs: number[][], - mats: number[][][], + vecs: ROArrayArray, + mats: ROArrayArrayArray, filter: IntervalFilter, ...ops: VectorMatrixToVector[] ): Case[] { @@ -2035,14 +2055,14 @@ export abstract class FPTraits { * @param op operation defining the function being run * @returns a span over all the outputs of op.impl */ - private roundAndFlushVectorToInterval(x: number[], op: VectorToIntervalOp): FPInterval { + private roundAndFlushVectorToInterval(x: readonly number[], op: VectorToIntervalOp): FPInterval { assert( x.every(e => !Number.isNaN(e)), `flush not defined for NaN` ); - const x_rounded: number[][] = x.map(this.correctlyRounded); - const x_flushed: number[][] = x_rounded.map(this.addFlushedIfNeeded.bind(this)); + const x_rounded: ROArrayArray = x.map(this.correctlyRounded); + const x_flushed: ROArrayArray = x_rounded.map(this.addFlushedIfNeeded.bind(this)); const x_inputs = cartesianProduct(...x_flushed); const intervals = new Set(); @@ -2066,8 +2086,8 @@ export abstract class FPTraits { * @returns a span over all the outputs of op.impl */ private roundAndFlushVectorPairToInterval( - x: number[], - y: number[], + x: readonly number[], + y: readonly number[], op: VectorPairToIntervalOp ): FPInterval { assert( @@ -2079,10 +2099,10 @@ export abstract class FPTraits { `flush not defined for NaN` ); - const x_rounded: number[][] = x.map(this.correctlyRounded); - const y_rounded: number[][] = y.map(this.correctlyRounded); - const x_flushed: number[][] = x_rounded.map(this.addFlushedIfNeeded.bind(this)); - const y_flushed: number[][] = y_rounded.map(this.addFlushedIfNeeded.bind(this)); + const x_rounded: ROArrayArray = x.map(this.correctlyRounded); + const y_rounded: ROArrayArray = y.map(this.correctlyRounded); + const x_flushed: ROArrayArray = x_rounded.map(this.addFlushedIfNeeded.bind(this)); + const y_flushed: ROArrayArray = y_rounded.map(this.addFlushedIfNeeded.bind(this)); const x_inputs = cartesianProduct(...x_flushed); const y_inputs = cartesianProduct(...y_flushed); @@ -2106,14 +2126,14 @@ export abstract class FPTraits { * @param op operation defining the function being run * @returns a vector of spans for each outputs of op.impl */ - private roundAndFlushVectorToVector(x: number[], op: VectorToVectorOp): FPVector { + private roundAndFlushVectorToVector(x: readonly number[], op: VectorToVectorOp): FPVector { assert( x.every(e => !Number.isNaN(e)), `flush not defined for NaN` ); - const x_rounded: number[][] = x.map(this.correctlyRounded); - const x_flushed: number[][] = x_rounded.map(this.addFlushedIfNeeded.bind(this)); + const x_rounded: ROArrayArray = x.map(this.correctlyRounded); + const x_flushed: ROArrayArray = x_rounded.map(this.addFlushedIfNeeded.bind(this)); const x_inputs = cartesianProduct(...x_flushed); const interval_vectors = new Set(); @@ -2137,8 +2157,8 @@ export abstract class FPTraits { * @returns a vector of spans for each output of op.impl */ private roundAndFlushVectorPairToVector( - x: number[], - y: number[], + x: readonly number[], + y: readonly number[], op: VectorPairToVectorOp ): FPVector { assert( @@ -2150,10 +2170,10 @@ export abstract class FPTraits { `flush not defined for NaN` ); - const x_rounded: number[][] = x.map(this.correctlyRounded); - const y_rounded: number[][] = y.map(this.correctlyRounded); - const x_flushed: number[][] = x_rounded.map(this.addFlushedIfNeeded.bind(this)); - const y_flushed: number[][] = y_rounded.map(this.addFlushedIfNeeded.bind(this)); + const x_rounded: ROArrayArray = x.map(this.correctlyRounded); + const y_rounded: ROArrayArray = y.map(this.correctlyRounded); + const x_flushed: ROArrayArray = x_rounded.map(this.addFlushedIfNeeded.bind(this)); + const y_flushed: ROArrayArray = y_rounded.map(this.addFlushedIfNeeded.bind(this)); const x_inputs = cartesianProduct(...x_flushed); const y_inputs = cartesianProduct(...y_flushed); @@ -2187,10 +2207,12 @@ export abstract class FPTraits { ); const m_flat = flatten2DArray(m); - const m_rounded: number[][] = m_flat.map(this.correctlyRounded); - const m_flushed: number[][] = m_rounded.map(this.addFlushedIfNeeded.bind(this)); - const m_options: number[][] = cartesianProduct(...m_flushed); - const m_inputs: Array2D[] = m_options.map(e => unflatten2DArray(e, num_cols, num_rows)); + const m_rounded: ROArrayArray = m_flat.map(this.correctlyRounded); + const m_flushed: ROArrayArray = m_rounded.map(this.addFlushedIfNeeded.bind(this)); + const m_options: ROArrayArray = cartesianProduct(...m_flushed); + const m_inputs: ROArrayArrayArray = m_options.map(e => + unflatten2DArray(e, num_cols, num_rows) + ); const interval_matrices = new Set(); m_inputs.forEach(inner_m => { @@ -2470,8 +2492,8 @@ export abstract class FPTraits { return this.constants().unboundedMatrix[num_cols][num_rows]; } - const m_flat: FPInterval[] = flatten2DArray(m); - const m_values: number[][] = cartesianProduct(...m_flat.map(e => e.bounds())); + const m_flat: readonly FPInterval[] = flatten2DArray(m); + const m_values: ROArrayArray = cartesianProduct(...m_flat.map(e => e.bounds())); const outputs = new Set(); m_values.forEach(inner_m => { @@ -2483,10 +2505,10 @@ export abstract class FPTraits { const result_cols = result.length; const result_rows = result[0].length; - // FPMatrix has to be coerced to FPInterval[][] to use .every. This should + // FPMatrix has to be coerced to ROArrayArray to use .every. This should // always be safe, since FPMatrix are defined as fixed length array of // arrays. - return (result as FPInterval[][]).every(c => c.every(r => r.isFinite())) + return (result as ROArrayArray).every(c => c.every(r => r.isFinite())) ? result : this.constants().unboundedMatrix[result_cols][result_rows]; } @@ -2991,7 +3013,7 @@ export abstract class FPTraits { public abstract readonly coshInterval: (n: number) => FPInterval; private readonly CrossIntervalOp: VectorPairToVectorOp = { - impl: (x: number[], y: number[]): FPVector => { + impl: (x: readonly number[], y: readonly number[]): FPVector => { assert(x.length === 3, `CrossIntervalOp received x with ${x.length} instead of 3`); assert(y.length === 3, `CrossIntervalOp received y with ${y.length} instead of 3`); @@ -3016,14 +3038,14 @@ export abstract class FPTraits { }, }; - protected crossIntervalImpl(x: number[], y: number[]): FPVector { + protected crossIntervalImpl(x: readonly number[], y: readonly number[]): FPVector { assert(x.length === 3, `Cross is only defined for vec3`); assert(y.length === 3, `Cross is only defined for vec3`); return this.runVectorPairToVectorOp(this.toVector(x), this.toVector(y), this.CrossIntervalOp); } /** Calculate a vector of acceptance intervals for cross(x, y) */ - public abstract readonly crossInterval: (x: number[], y: number[]) => FPVector; + public abstract readonly crossInterval: (x: readonly number[], y: readonly number[]) => FPVector; private readonly DegreesIntervalOp: ScalarToIntervalOp = { impl: (n: number): FPInterval => { @@ -3050,10 +3072,10 @@ export abstract class FPTraits { assert(col >= 0 && col < dim, `col ${col} needs be in [0, # of columns '${dim}')`); assert(row >= 0 && row < dim, `row ${row} needs be in [0, # of rows '${dim}')`); - const result: Array2D = [...Array(dim - 1)].map(_ => [...Array(dim - 1)]); + const result: number[][] = [...Array(dim - 1)].map(_ => [...Array(dim - 1)]); - const col_indices: number[] = [...Array(dim).keys()].filter(e => e !== col); - const row_indices: number[] = [...Array(dim).keys()].filter(e => e !== row); + const col_indices: readonly number[] = [...Array(dim).keys()].filter(e => e !== col); + const row_indices: readonly number[] = [...Array(dim).keys()].filter(e => e !== row); col_indices.forEach((c, i) => { row_indices.forEach((r, j) => { @@ -3104,7 +3126,7 @@ export abstract class FPTraits { // Need to calculate permutations, since for fp addition is not associative, // so A + B + C is not guaranteed to equal B + C + A, etc. - const permutations: FPInterval[][] = calculatePermutations([A, B, C]); + const permutations: ROArrayArray = calculatePermutations([A, B, C]); return this.spanIntervals( ...permutations.map(p => p.reduce((prev: FPInterval, cur: FPInterval) => this.additionInterval(prev, cur)) @@ -3145,7 +3167,7 @@ export abstract class FPTraits { // Need to calculate permutations, since for fp addition is not associative // so A + B + C + D is not guaranteed to equal B + C + A + D, etc. - const permutations: FPInterval[][] = calculatePermutations([A, B, C, D]); + const permutations: ROArrayArray = calculatePermutations([A, B, C, D]); return this.spanIntervals( ...permutations.map(p => p.reduce((prev: FPInterval, cur: FPInterval) => this.additionInterval(prev, cur)) @@ -3200,7 +3222,7 @@ export abstract class FPTraits { }; private readonly DistanceIntervalVectorOp: VectorPairToIntervalOp = { - impl: (x: number[], y: number[]): FPInterval => { + impl: (x: readonly number[], y: readonly number[]): FPInterval => { return this.lengthInterval( this.runScalarPairToIntervalOpVectorComponentWise( this.toVector(x), @@ -3211,7 +3233,10 @@ export abstract class FPTraits { }, }; - protected distanceIntervalImpl(x: number | number[], y: number | number[]): FPInterval { + protected distanceIntervalImpl( + x: number | readonly number[], + y: number | readonly number[] + ): FPInterval { if (x instanceof Array && y instanceof Array) { assert( x.length === y.length, @@ -3236,8 +3261,8 @@ export abstract class FPTraits { /** Calculate an acceptance interval of distance(x, y) */ public abstract readonly distanceInterval: ( - x: number | number[], - y: number | number[] + x: number | readonly number[], + y: number | readonly number[] ) => FPInterval; // This op is implemented differently for f32 and f16. @@ -3286,7 +3311,7 @@ export abstract class FPTraits { ) => FPInterval; private readonly DotIntervalOp: VectorPairToIntervalOp = { - impl: (x: number[], y: number[]): FPInterval => { + impl: (x: readonly number[], y: readonly number[]): FPInterval => { // dot(x, y) = sum of x[i] * y[i] const multiplications = this.runScalarPairToIntervalOpVectorComponentWise( this.toVector(x), @@ -3303,22 +3328,25 @@ export abstract class FPTraits { // permutations are calculated and their results spanned, since addition // of more than two floats is not transitive, i.e. a + b + c is not // guaranteed to equal b + a + c - const permutations: FPInterval[][] = calculatePermutations(multiplications); + const permutations: ROArrayArray = calculatePermutations(multiplications); return this.spanIntervals( ...permutations.map(p => p.reduce((prev, cur) => this.additionInterval(prev, cur))) ); }, }; - protected dotIntervalImpl(x: number[] | FPInterval[], y: number[] | FPInterval[]): FPInterval { + protected dotIntervalImpl( + x: readonly number[] | readonly FPInterval[], + y: readonly number[] | readonly FPInterval[] + ): FPInterval { assert(x.length === y.length, `dot not defined for vectors with different lengths`); return this.runVectorPairToIntervalOp(this.toVector(x), this.toVector(y), this.DotIntervalOp); } /** Calculated the acceptance interval for dot(x, y) */ public abstract readonly dotInterval: ( - x: number[] | FPInterval[], - y: number[] | FPInterval[] + x: readonly number[] | readonly FPInterval[], + y: readonly number[] | readonly FPInterval[] ) => FPInterval; private readonly ExpIntervalOp: ScalarToIntervalOp = { @@ -3362,9 +3390,9 @@ export abstract class FPTraits { * defining an Op and running that through the framework. */ protected faceForwardIntervalsImpl( - x: number[], - y: number[], - z: number[] + x: readonly number[], + y: readonly number[], + z: readonly number[] ): (FPVector | undefined)[] { const x_vec = this.toVector(x); // Running vector through this.runScalarToIntervalOpComponentWise to make @@ -3412,9 +3440,9 @@ export abstract class FPTraits { /** Calculate the acceptance intervals for faceForward(x, y, z) */ public abstract readonly faceForwardIntervals: ( - x: number[], - y: number[], - z: number[] + x: readonly number[], + y: readonly number[], + z: readonly number[] ) => (FPVector | undefined)[]; private readonly FloorIntervalOp: ScalarToIntervalOp = { @@ -3552,12 +3580,12 @@ export abstract class FPTraits { }; private readonly LengthIntervalVectorOp: VectorToIntervalOp = { - impl: (n: number[]): FPInterval => { + impl: (n: readonly number[]): FPInterval => { return this.sqrtInterval(this.dotInterval(n, n)); }, }; - protected lengthIntervalImpl(n: number | FPInterval | number[] | FPVector): FPInterval { + protected lengthIntervalImpl(n: number | FPInterval | readonly number[] | FPVector): FPInterval { if (n instanceof Array) { return this.runVectorToIntervalOp(this.toVector(n), this.LengthIntervalVectorOp); } else { @@ -3567,7 +3595,7 @@ export abstract class FPTraits { /** Calculate an acceptance interval of length(x) */ public abstract readonly lengthInterval: ( - n: number | FPInterval | number[] | FPVector + n: number | FPInterval | readonly number[] | FPVector ) => FPInterval; private readonly LogIntervalOp: ScalarToIntervalOp = { @@ -3751,7 +3779,7 @@ export abstract class FPTraits { * @returns the vector result of multiplying the given vector by the given * scalar */ - private multiplyVectorByScalar(v: number[], c: number | FPInterval): FPVector { + private multiplyVectorByScalar(v: readonly number[], c: number | FPInterval): FPVector { return this.toVector(v.map(x => this.multiplicationInterval(x, c))); } @@ -3795,14 +3823,14 @@ export abstract class FPTraits { const x_transposed = this.transposeInterval(mat_x); - const result: Array2D = [...Array(y_cols)].map(_ => [...Array(x_rows)]); + const result: FPInterval[][] = [...Array(y_cols)].map(_ => [...Array(x_rows)]); mat_y.forEach((y, i) => { x_transposed.forEach((x, j) => { result[i][j] = this.dotInterval(x, y); }); }); - return result as FPMatrix; + return (result as ROArrayArray) as FPMatrix; } /** Calculate an acceptance interval of x * y, when x is a matrix and y is a matrix */ @@ -3811,7 +3839,10 @@ export abstract class FPTraits { mat_y: Array2D ) => FPMatrix; - protected multiplicationMatrixVectorIntervalImpl(x: Array2D, y: number[]): FPVector { + protected multiplicationMatrixVectorIntervalImpl( + x: Array2D, + y: readonly number[] + ): FPVector { const cols = x.length; const rows = x[0].length; assert(y.length === cols, `'mat${cols}x${rows} * vec${y.length}' is not defined`); @@ -3822,10 +3853,13 @@ export abstract class FPTraits { /** Calculate an acceptance interval of x * y, when x is a matrix and y is a vector */ public abstract readonly multiplicationMatrixVectorInterval: ( x: Array2D, - y: number[] + y: readonly number[] ) => FPVector; - protected multiplicationVectorMatrixIntervalImpl(x: number[], y: Array2D): FPVector { + protected multiplicationVectorMatrixIntervalImpl( + x: readonly number[], + y: Array2D + ): FPVector { const cols = y.length; const rows = y[0].length; assert(x.length === rows, `'vec${x.length} * mat${cols}x${rows}' is not defined`); @@ -3835,7 +3869,7 @@ export abstract class FPTraits { /** Calculate an acceptance interval of x * y, when x is a vector and y is a matrix */ public abstract readonly multiplicationVectorMatrixInterval: ( - x: number[], + x: readonly number[], y: Array2D ) => FPVector; @@ -3853,17 +3887,17 @@ export abstract class FPTraits { public abstract readonly negationInterval: (n: number) => FPInterval; private readonly NormalizeIntervalOp: VectorToVectorOp = { - impl: (n: number[]): FPVector => { + impl: (n: readonly number[]): FPVector => { const length = this.lengthInterval(n); return this.toVector(n.map(e => this.divisionInterval(e, length))); }, }; - protected normalizeIntervalImpl(n: number[]): FPVector { + protected normalizeIntervalImpl(n: readonly number[]): FPVector { return this.runVectorToVectorOp(this.toVector(n), this.NormalizeIntervalOp); } - public abstract readonly normalizeInterval: (n: number[]) => FPVector; + public abstract readonly normalizeInterval: (n: readonly number[]) => FPVector; private readonly PowIntervalOp: ScalarPairToIntervalOp = { // pow(x, y) has no explicit domain restrictions, but inherits the x <= 0 @@ -3902,7 +3936,7 @@ export abstract class FPTraits { public abstract readonly radiansInterval: (n: number) => FPInterval; private readonly ReflectIntervalOp: VectorPairToVectorOp = { - impl: (x: number[], y: number[]): FPVector => { + impl: (x: readonly number[], y: readonly number[]): FPVector => { assert( x.length === y.length, `ReflectIntervalOp received x (${x}) and y (${y}) with different numbers of elements` @@ -3922,7 +3956,7 @@ export abstract class FPTraits { }, }; - protected reflectIntervalImpl(x: number[], y: number[]): FPVector { + protected reflectIntervalImpl(x: readonly number[], y: readonly number[]): FPVector { assert( x.length === y.length, `reflect is only defined for vectors with the same number of elements` @@ -3931,7 +3965,10 @@ export abstract class FPTraits { } /** Calculate an acceptance interval of reflect(x, y) */ - public abstract readonly reflectInterval: (x: number[], y: number[]) => FPVector; + public abstract readonly reflectInterval: ( + x: readonly number[], + y: readonly number[] + ) => FPVector; /** * refract is a singular function in the sense that it is the only builtin that @@ -3942,7 +3979,7 @@ export abstract class FPTraits { * own operation type, etc, it instead has a bespoke implementation that is a * composition of other builtin functions that use the framework. */ - protected refractIntervalImpl(i: number[], s: number[], r: number): FPVector { + protected refractIntervalImpl(i: readonly number[], s: readonly number[], r: number): FPVector { assert( i.length === s.length, `refract is only defined for vectors with the same number of elements` @@ -3979,7 +4016,11 @@ export abstract class FPTraits { } /** Calculate acceptance interval vectors of reflect(i, s, r) */ - public abstract readonly refractInterval: (i: number[], s: number[], r: number) => FPVector; + public abstract readonly refractInterval: ( + i: readonly number[], + s: readonly number[], + r: number + ) => FPVector; private readonly RemainderIntervalOp: ScalarPairToIntervalOp = { impl: (x: number, y: number): FPInterval => { @@ -4245,7 +4286,7 @@ export abstract class FPTraits { impl: (m: Array2D): FPMatrix => { const num_cols = m.length; const num_rows = m[0].length; - const result: Array2D = [...Array(num_rows)].map(_ => [...Array(num_cols)]); + const result: FPInterval[][] = [...Array(num_rows)].map(_ => [...Array(num_cols)]); for (let i = 0; i < num_cols; i++) { for (let j = 0; j < num_rows; j++) { @@ -4595,7 +4636,7 @@ class F32Traits extends FPTraits { * @param ops callbacks that implement generating an acceptance interval */ public generateU32ToIntervalCases( - params: number[], + params: readonly number[], filter: IntervalFilter, ...ops: ScalarToVector[] ): Case[] { diff --git a/src/webgpu/util/math.ts b/src/webgpu/util/math.ts index c22b4f395024..6e8c9951ce34 100644 --- a/src/webgpu/util/math.ts +++ b/src/webgpu/util/math.ts @@ -1,3 +1,4 @@ +import { ROArrayArray, ROArrayArrayArray } from '../../common/util/types.js'; import { assert } from '../../common/util/util.js'; import { Float16Array, @@ -470,7 +471,7 @@ export function oneULPF16(target: number, mode: FlushMode = 'flush'): number { * @returns all of the acceptable roundings for quantizing to 64-bits in * ascending order. */ -export function correctlyRoundedF64(n: number): number[] { +export function correctlyRoundedF64(n: number): readonly number[] { assert(!Number.isNaN(n), `correctlyRoundedF32 not defined for NaN`); // Above f64 range if (n === Number.POSITIVE_INFINITY) { @@ -510,7 +511,7 @@ export function correctlyRoundedF64(n: number): number[] { * @returns all of the acceptable roundings for quantizing to 32-bits in * ascending order. */ -export function correctlyRoundedF32(n: number): number[] { +export function correctlyRoundedF32(n: number): readonly number[] { if (Number.isNaN(n)) { return [n]; } @@ -578,7 +579,7 @@ export function correctlyRoundedF32(n: number): number[] { * @returns all of the acceptable roundings for quantizing to 16-bits in * ascending order. */ -export function correctlyRoundedF16(n: number): number[] { +export function correctlyRoundedF16(n: number): readonly number[] { if (Number.isNaN(n)) { return [n]; } @@ -824,7 +825,7 @@ export function lerpBigInt(a: bigint, b: bigint, idx: number, steps: number): bi } /** @returns a linear increasing range of numbers. */ -export function linearRange(a: number, b: number, num_steps: number): number[] { +export function linearRange(a: number, b: number, num_steps: number): readonly number[] { if (num_steps <= 0) { return []; } @@ -865,7 +866,7 @@ export function linearRangeBigInt(a: bigint, b: bigint, num_steps: number): Arra * This biased range is then scaled to the desired range using lerp. * Different curves could be generated by changing c, where greater values of c will bias more towards 0. */ -export function biasedRange(a: number, b: number, num_steps: number): number[] { +export function biasedRange(a: number, b: number, num_steps: number): readonly number[] { const c = 2; if (num_steps <= 0) { return []; @@ -1108,7 +1109,7 @@ export function filteredF64Range( } /** Short list of i32 values of interest to test against */ -const kInterestingI32Values: number[] = [ +const kInterestingI32Values: readonly number[] = [ kValue.i32.negative.max, Math.trunc(kValue.i32.negative.max / 2), -256, @@ -1128,7 +1129,7 @@ const kInterestingI32Values: number[] = [ * generated is a super linear function of the length of i32 values which is * leading to time outs. */ -export function sparseI32Range(): number[] { +export function sparseI32Range(): readonly number[] { return kInterestingI32Values; } @@ -1172,7 +1173,7 @@ const kVectorI32Values = { * vector to get a spread of testing over the entire range. This reduces the * number of cases being run substantially, but maintains coverage. */ -export function vectorI32Range(dim: number): number[][] { +export function vectorI32Range(dim: number): ROArrayArray { assert(dim === 2 || dim === 3 || dim === 4, 'vectorI32Range only accepts dimensions 2, 3, and 4'); return kVectorI32Values[dim]; } @@ -1200,7 +1201,7 @@ export function fullI32Range( } /** Short list of u32 values of interest to test against */ -const kInterestingU32Values: number[] = [ +const kInterestingU32Values: readonly number[] = [ 0, 1, 10, @@ -1215,7 +1216,7 @@ const kInterestingU32Values: number[] = [ * generated is a super linear function of the length of u32 values which is * leading to time outs. */ -export function sparseU32Range(): number[] { +export function sparseU32Range(): readonly number[] { return kInterestingU32Values; } @@ -1250,7 +1251,7 @@ const kVectorU32Values = { * vector to get a spread of testing over the entire range. This reduces the * number of cases being run substantially, but maintains coverage. */ -export function vectorU32Range(dim: number): number[][] { +export function vectorU32Range(dim: number): ROArrayArray { assert(dim === 2 || dim === 3 || dim === 4, 'vectorU32Range only accepts dimensions 2, 3, and 4'); return kVectorU32Values[dim]; } @@ -1267,7 +1268,7 @@ export function fullU32Range(count: number = 50): Array { } /** Short list of f32 values of interest to test against */ -const kInterestingF32Values: number[] = [ +const kInterestingF32Values: readonly number[] = [ kValue.f32.negative.min, -10.0, -1.0, @@ -1298,7 +1299,7 @@ const kInterestingF32Values: number[] = [ * specific values of interest. If there are known values of interest they * should be appended to this list in the test generation code. */ -export function sparseF32Range(): number[] { +export function sparseF32Range(): readonly number[] { return kInterestingF32Values; } @@ -1342,7 +1343,7 @@ const kVectorF32Values = { * vector to get a spread of testing over the entire range. This reduces the * number of cases being run substantially, but maintains coverage. */ -export function vectorF32Range(dim: number): number[][] { +export function vectorF32Range(dim: number): ROArrayArray { assert(dim === 2 || dim === 3 || dim === 4, 'vectorF32Range only accepts dimensions 2, 3, and 4'); return kVectorF32Values[dim]; } @@ -1371,7 +1372,7 @@ const kSparseVectorF32Values = { * All of the interesting floats from sparseF32 are guaranteed to be tested, but * not in every position. */ -export function sparseVectorF32Range(dim: number): number[][] { +export function sparseVectorF32Range(dim: number): ROArrayArray { assert( dim === 2 || dim === 3 || dim === 4, 'sparseVectorF32Range only accepts dimensions 2, 3, and 4' @@ -1490,7 +1491,7 @@ const kSparseMatrixF32Values = { * All of the interesting floats from sparseF32 are guaranteed to be tested, but * not in every position. */ -export function sparseMatrixF32Range(c: number, r: number): number[][][] { +export function sparseMatrixF32Range(c: number, r: number): ROArrayArrayArray { assert( c === 2 || c === 3 || c === 4, 'sparseMatrixF32Range only accepts column counts of 2, 3, and 4' @@ -1503,7 +1504,7 @@ export function sparseMatrixF32Range(c: number, r: number): number[][][] { } /** Short list of f16 values of interest to test against */ -const kInterestingF16Values: number[] = [ +const kInterestingF16Values: readonly number[] = [ kValue.f16.negative.min, -10.0, -1.0, @@ -1534,7 +1535,7 @@ const kInterestingF16Values: number[] = [ * specific values of interest. If there are known values of interest they * should be appended to this list in the test generation code. */ -export function sparseF16Range(): number[] { +export function sparseF16Range(): readonly number[] { return kInterestingF16Values; } @@ -1578,7 +1579,7 @@ const kVectorF16Values = { * vector to get a spread of testing over the entire range. This reduces the * number of cases being run substantially, but maintains coverage. */ -export function vectorF16Range(dim: number): number[][] { +export function vectorF16Range(dim: number): ROArrayArray { assert(dim === 2 || dim === 3 || dim === 4, 'vectorF16Range only accepts dimensions 2, 3, and 4'); return kVectorF16Values[dim]; } @@ -1607,7 +1608,7 @@ const kSparseVectorF16Values = { * All of the interesting floats from sparseF16 are guaranteed to be tested, but * not in every position. */ -export function sparseVectorF16Range(dim: number): number[][] { +export function sparseVectorF16Range(dim: number): ROArrayArray { assert( dim === 2 || dim === 3 || dim === 4, 'sparseVectorF16Range only accepts dimensions 2, 3, and 4' @@ -1726,7 +1727,7 @@ const kSparseMatrixF16Values = { * All of the interesting floats from sparseF16 are guaranteed to be tested, but * not in every position. */ -export function sparseMatrixF16Range(c: number, r: number): number[][][] { +export function sparseMatrixF16Range(c: number, r: number): ROArrayArray[] { assert( c === 2 || c === 3 || c === 4, 'sparseMatrixF16Range only accepts column counts of 2, 3, and 4' @@ -1739,7 +1740,7 @@ export function sparseMatrixF16Range(c: number, r: number): number[][][] { } /** Short list of f64 values of interest to test against */ -const kInterestingF64Values: number[] = [ +const kInterestingF64Values: readonly number[] = [ kValue.f64.negative.min, -10.0, -1.0, @@ -1770,7 +1771,7 @@ const kInterestingF64Values: number[] = [ * specific values of interest. If there are known values of interest they * should be appended to this list in the test generation code. */ -export function sparseF64Range(): number[] { +export function sparseF64Range(): readonly number[] { return kInterestingF64Values; } @@ -1814,7 +1815,7 @@ const kVectorF64Values = { * vector to get a spread of testing over the entire range. This reduces the * number of cases being run substantially, but maintains coverage. */ -export function vectorF64Range(dim: number): number[][] { +export function vectorF64Range(dim: number): ROArrayArray { assert(dim === 2 || dim === 3 || dim === 4, 'vectorF64Range only accepts dimensions 2, 3, and 4'); return kVectorF64Values[dim]; } @@ -1843,7 +1844,7 @@ const kSparseVectorF64Values = { * All the interesting floats from sparseF64 are guaranteed to be tested, but * not in every position. */ -export function sparseVectorF64Range(dim: number): number[][] { +export function sparseVectorF64Range(dim: number): ROArrayArray { assert( dim === 2 || dim === 3 || dim === 4, 'sparseVectorF64Range only accepts dimensions 2, 3, and 4' @@ -1962,7 +1963,7 @@ const kSparseMatrixF64Values = { * All the interesting floats from sparseF64 are guaranteed to be tested, but * not in every position. */ -export function sparseMatrixF64Range(c: number, r: number): number[][][] { +export function sparseMatrixF64Range(c: number, r: number): ROArrayArray[] { assert( c === 2 || c === 3 || c === 4, 'sparseMatrixF64Range only accepts column counts of 2, 3, and 4' @@ -2074,11 +2075,14 @@ export function lcm(a: number, b: number): number { * @param intermediate arrays of values representing the partial result of * cartesianProduct */ -function cartesianProductImpl(elements: T[], intermediate: T[][]): T[][] { +function cartesianProductImpl( + elements: readonly T[], + intermediate: ROArrayArray +): ROArrayArray { const result: T[][] = []; elements.forEach((e: T) => { if (intermediate.length > 0) { - intermediate.forEach((i: T[]) => { + intermediate.forEach((i: readonly T[]) => { result.push([...i, e]); }); } else { @@ -2098,9 +2102,9 @@ function cartesianProductImpl(elements: T[], intermediate: T[][]): T[][] { * * @param inputs arrays of numbers to calculate cartesian product over */ -export function cartesianProduct(...inputs: T[][]): T[][] { - let result: T[][] = []; - inputs.forEach((i: T[]) => { +export function cartesianProduct(...inputs: ROArrayArray): ROArrayArray { + let result: ROArrayArray = []; + inputs.forEach((i: readonly T[]) => { result = cartesianProductImpl(i, result); }); @@ -2122,7 +2126,7 @@ export function cartesianProduct(...inputs: T[][]): T[][] { * * @param input the array to get permutations of */ -export function calculatePermutations(input: T[]): T[][] { +export function calculatePermutations(input: readonly T[]): ROArrayArray { if (input.length === 0) { return []; } @@ -2155,7 +2159,7 @@ export function calculatePermutations(input: T[]): T[][] { * * @param m Matrix to convert */ -export function flatten2DArray(m: T[][]): T[] { +export function flatten2DArray(m: ROArrayArray): T[] { const c = m.length; const r = m[0].length; assert( @@ -2177,7 +2181,7 @@ export function flatten2DArray(m: T[][]): T[] { * @param c number of elements in the array containing arrays * @param r number of elements in the arrays that are contained */ -export function unflatten2DArray(n: T[], c: number, r: number): T[][] { +export function unflatten2DArray(n: readonly T[], c: number, r: number): ROArrayArray { assert( c > 0 && Number.isInteger(c) && r > 0 && Number.isInteger(r), `columns (${c}) and rows (${r}) need to be positive integers` @@ -2200,7 +2204,7 @@ export function unflatten2DArray(n: T[], c: number, r: number): T[][] { * @param op operation that converts an element of type T to one of type S * @returns a matrix with elements of type S that are calculated by applying op element by element */ -export function map2DArray(m: T[][], op: (input: T) => S): S[][] { +export function map2DArray(m: ROArrayArray, op: (input: T) => S): ROArrayArray { const c = m.length; const r = m[0].length; assert( @@ -2223,7 +2227,7 @@ export function map2DArray(m: T[][], op: (input: T) => S): S[][] { * @param op operation that performs a test on an element * @returns a boolean indicating if the test passed for every element */ -export function every2DArray(m: T[][], op: (input: T) => boolean): boolean { +export function every2DArray(m: ROArrayArray, op: (input: T) => boolean): boolean { const r = m[0].length; assert( m.every(c => c.length === r),