summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts837
1 files changed, 837 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts
new file mode 100644
index 0000000000..75dfea0086
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts
@@ -0,0 +1,837 @@
+import { assert } from '../../../../../../common/util/util.js';
+import { Comparator, alwaysPass, anyOf } from '../../../../../util/compare.js';
+import { kBit, kValue } from '../../../../../util/constants.js';
+import {
+ ScalarValue,
+ VectorValue,
+ f16,
+ f32,
+ i32,
+ toVector,
+ u32,
+ abstractFloat,
+ abstractInt,
+} from '../../../../../util/conversion.js';
+import { FP, FPInterval } from '../../../../../util/floating_point.js';
+import {
+ cartesianProduct,
+ fullI32Range,
+ fullU32Range,
+ isFiniteF16,
+ isFiniteF32,
+ isSubnormalNumberF16,
+ isSubnormalNumberF32,
+ linearRange,
+ scalarF16Range,
+ scalarF32Range,
+} from '../../../../../util/math.js';
+import {
+ reinterpretF16AsU16,
+ reinterpretF32AsI32,
+ reinterpretF32AsU32,
+ reinterpretI32AsF32,
+ reinterpretI32AsU32,
+ reinterpretU16AsF16,
+ reinterpretU32AsF32,
+ reinterpretU32AsI32,
+} from '../../../../../util/reinterpret.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const numNaNs = 11;
+const f32InfAndNaNInU32: number[] = [
+ // Cover NaNs evenly in integer space.
+ // The positive NaN with the lowest integer representation is the integer
+ // for infinity, plus one.
+ // The positive NaN with the highest integer representation is i32.max (!)
+ ...linearRange(kBit.f32.positive.infinity + 1, kBit.i32.positive.max, numNaNs),
+ // The negative NaN with the lowest integer representation is the integer
+ // for negative infinity, plus one.
+ // The negative NaN with the highest integer representation is u32.max (!)
+ ...linearRange(kBit.f32.negative.infinity + 1, kBit.u32.max, numNaNs),
+ kBit.f32.positive.infinity,
+ kBit.f32.negative.infinity,
+];
+const f32InfAndNaNInF32 = f32InfAndNaNInU32.map(u => reinterpretU32AsF32(u));
+const f32InfAndNaNInI32 = f32InfAndNaNInU32.map(u => reinterpretU32AsI32(u));
+
+const f32ZerosInU32 = [0, kBit.f32.negative.zero];
+const f32ZerosInF32 = f32ZerosInU32.map(u => reinterpretU32AsF32(u));
+const f32ZerosInI32 = f32ZerosInU32.map(u => reinterpretU32AsI32(u));
+const f32ZerosInterval: FPInterval = new FPInterval('f32', -0.0, 0.0);
+
+// f32FiniteRange is a list of finite f32s. fullF32Range() already
+// has +0, we only need to add -0.
+const f32FiniteRange: number[] = [...scalarF32Range(), kValue.f32.negative.zero];
+const f32RangeWithInfAndNaN: number[] = [...f32FiniteRange, ...f32InfAndNaNInF32];
+
+// Type.f16 values, finite, Inf/NaN, and zeros. Represented in float and u16.
+const f16FiniteInF16: number[] = [...scalarF16Range(), kValue.f16.negative.zero];
+const f16FiniteInU16: number[] = f16FiniteInF16.map(u => reinterpretF16AsU16(u));
+
+const f16InfAndNaNInU16: number[] = [
+ // Cover NaNs evenly in integer space.
+ // The positive NaN with the lowest integer representation is the integer
+ // for infinity, plus one.
+ // The positive NaN with the highest integer representation is u16 0x7fff i.e. 32767.
+ ...linearRange(kBit.f16.positive.infinity + 1, 32767, numNaNs).map(v => Math.ceil(v)),
+ // The negative NaN with the lowest integer representation is the integer
+ // for negative infinity, plus one.
+ // The negative NaN with the highest integer representation is u16 0xffff i.e. 65535
+ ...linearRange(kBit.f16.negative.infinity + 1, 65535, numNaNs).map(v => Math.floor(v)),
+ kBit.f16.positive.infinity,
+ kBit.f16.negative.infinity,
+];
+const f16InfAndNaNInF16 = f16InfAndNaNInU16.map(u => reinterpretU16AsF16(u));
+
+const f16ZerosInU16 = [kBit.f16.negative.zero, 0];
+
+// f16 interval that match +/-0.0.
+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: readonly number[]): number {
+ assert(u16x2.length === 2);
+ // Create a DataView with 4 bytes buffer.
+ const buffer = new ArrayBuffer(4);
+ const view = new DataView(buffer);
+ // Enforce little-endian.
+ view.setUint16(0, u16x2[0], true);
+ view.setUint16(2, u16x2[1], true);
+ return view.getUint32(0, true);
+}
+
+/**
+ * @returns an array of two u16, respectively the lower and higher 16bits of
+ * given u32 in little-endian.
+ */
+function u32ToU16x2(u32: number): number[] {
+ // Create a DataView with 4 bytes buffer.
+ const buffer = new ArrayBuffer(4);
+ const view = new DataView(buffer);
+ // Enforce little-endian.
+ view.setUint32(0, u32, true);
+ return [view.getUint16(0, true), view.getUint16(2, true)];
+}
+
+/**
+ * @returns a vec2<f16> from an array of two u16, each reinterpreted as f16.
+ */
+function u16x2ToVec2F16(u16x2: number[]): VectorValue {
+ assert(u16x2.length === 2);
+ return toVector(u16x2.map(reinterpretU16AsF16), f16);
+}
+
+/**
+ * @returns a vec4<f16> from an array of four u16, each reinterpreted as f16.
+ */
+function u16x4ToVec4F16(u16x4: number[]): VectorValue {
+ assert(u16x4.length === 4);
+ return toVector(u16x4.map(reinterpretU16AsF16), f16);
+}
+
+/**
+ * @returns true if and only if a given u32 can bitcast to a vec2<f16> with all elements
+ * being finite f16 values.
+ */
+function canU32BitcastToFiniteVec2F16(u32: number): boolean {
+ return u32ToU16x2(u32)
+ .map(u16 => isFiniteF16(reinterpretU16AsF16(u16)))
+ .reduce((a, b) => a && b, true);
+}
+
+/**
+ * @returns an array of N elements with the i-th element being an array of len elements
+ * [a_i, a_((i+1)%N), ..., a_((i+len-1)%N)], for the input array of N element [a_1, ... a_N]
+ * and the given len. For example, slidingSlice([1, 2, 3], 2) result in
+ * [[1, 2], [2, 3], [3, 1]].
+ * This helper function is used for generating vector cases from scalar values array.
+ */
+function slidingSlice(input: number[], len: number) {
+ const result: number[][] = [];
+ for (let i = 0; i < input.length; i++) {
+ const sub: number[] = [];
+ for (let j = 0; j < len; j++) {
+ sub.push(input[(i + j) % input.length]);
+ }
+ result.push(sub);
+ }
+ return result;
+}
+
+// vec2<f16> interesting (zeros, Inf, and NaN) values for testing cases.
+// vec2<f16> values that has at least one Inf/NaN f16 element, reinterpreted as u32/i32.
+const f16Vec2InfAndNaNInU32 = [
+ ...cartesianProduct(f16InfAndNaNInU16, [...f16InfAndNaNInU16, ...f16FiniteInU16]),
+ ...cartesianProduct(f16FiniteInU16, f16InfAndNaNInU16),
+].map(u16x2ToU32);
+const f16Vec2InfAndNaNInI32 = f16Vec2InfAndNaNInU32.map(u => reinterpretU32AsI32(u));
+// vec2<f16> values with two f16 0.0 element, reinterpreted as u32/i32.
+const f16Vec2ZerosInU32 = cartesianProduct(f16ZerosInU16, f16ZerosInU16).map(u16x2ToU32);
+const f16Vec2ZerosInI32 = f16Vec2ZerosInU32.map(u => reinterpretU32AsI32(u));
+
+// i32/u32/f32 range for bitcasting to vec2<f16>
+// u32 values for bitcasting to vec2<f16> finite, Inf, and NaN.
+const u32RangeForF16Vec2FiniteInfNaN: number[] = [
+ ...fullU32Range(),
+ ...f16Vec2ZerosInU32,
+ ...f16Vec2InfAndNaNInU32,
+];
+// u32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
+const u32RangeForF16Vec2Finite: number[] = u32RangeForF16Vec2FiniteInfNaN.filter(
+ canU32BitcastToFiniteVec2F16
+);
+// i32 values for bitcasting to vec2<f16> finite, zeros, Inf, and NaN.
+const i32RangeForF16Vec2FiniteInfNaN: number[] = [
+ ...fullI32Range(),
+ ...f16Vec2ZerosInI32,
+ ...f16Vec2InfAndNaNInI32,
+];
+// i32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
+const i32RangeForF16Vec2Finite: number[] = i32RangeForF16Vec2FiniteInfNaN.filter(u =>
+ canU32BitcastToFiniteVec2F16(reinterpretI32AsU32(u))
+);
+// f32 values with finite/Inf/NaN f32, for bitcasting to vec2<f16> finite, zeros, Inf, and NaN.
+const f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN: number[] = [
+ ...f32RangeWithInfAndNaN,
+ ...u32RangeForF16Vec2FiniteInfNaN.map(reinterpretU32AsF32),
+];
+// Finite f32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
+const f32FiniteRangeForF16Vec2Finite: number[] = f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN
+ .filter(isFiniteF32)
+ .filter(u => canU32BitcastToFiniteVec2F16(reinterpretF32AsU32(u)));
+
+// vec2<f16> cases for bitcasting to i32/u32/f32, by combining f16 values into pairs
+const f16Vec2FiniteInU16x2 = slidingSlice(f16FiniteInU16, 2);
+const f16Vec2FiniteInfNanInU16x2 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 2);
+// vec4<f16> cases for bitcasting to vec2<i32/u32/f32>, by combining f16 values 4-by-4
+const f16Vec2FiniteInU16x4 = slidingSlice(f16FiniteInU16, 4);
+const f16Vec2FiniteInfNanInU16x4 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 4);
+
+// alwaysPass comparator for i32/u32/f32 cases. For f32/f16 we also use unbound interval, which
+// allow per-element unbounded expectation for vector.
+const anyF32 = alwaysPass('any f32');
+const anyI32 = alwaysPass('any i32');
+const anyU32 = alwaysPass('any u32');
+
+// Unbounded FPInterval
+const f32UnboundedInterval = FP.f32.constants().unboundedInterval;
+const f16UnboundedInterval = FP.f16.constants().unboundedInterval;
+
+// i32 and u32 cases for bitcasting to f32.
+// i32 cases for bitcasting to f32 finite, zeros, Inf, and NaN.
+const i32RangeForF32FiniteInfNaN: number[] = [
+ ...fullI32Range(),
+ ...f32ZerosInI32,
+ ...f32InfAndNaNInI32,
+];
+// i32 cases for bitcasting to f32 finite only.
+const i32RangeForF32Finite: number[] = i32RangeForF32FiniteInfNaN.filter(i =>
+ isFiniteF32(reinterpretI32AsF32(i))
+);
+// u32 cases for bitcasting to f32 finite, zeros, Inf, and NaN.
+const u32RangeForF32FiniteInfNaN: number[] = [
+ ...fullU32Range(),
+ ...f32ZerosInU32,
+ ...f32InfAndNaNInU32,
+];
+// u32 cases for bitcasting to f32 finite only.
+const u32RangeForF32Finite: number[] = u32RangeForF32FiniteInfNaN.filter(u =>
+ isFiniteF32(reinterpretU32AsF32(u))
+);
+
+/**
+ * @returns a Comparator for checking if a f32 value is a valid
+ * bitcast conversion from f32.
+ */
+function bitcastF32ToF32Comparator(f: number): Comparator {
+ if (!isFiniteF32(f)) return anyF32;
+ const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
+ return anyOf(...acceptable.map(f32));
+}
+
+/**
+ * @returns a Comparator for checking if a u32 value is a valid
+ * bitcast conversion from f32.
+ */
+function bitcastF32ToU32Comparator(f: number): Comparator {
+ if (!isFiniteF32(f)) return anyU32;
+ const acceptable: number[] = [
+ reinterpretF32AsU32(f),
+ ...(isSubnormalNumberF32(f) ? f32ZerosInU32 : []),
+ ];
+ return anyOf(...acceptable.map(u32));
+}
+
+/**
+ * @returns a Comparator for checking if a i32 value is a valid
+ * bitcast conversion from f32.
+ */
+function bitcastF32ToI32Comparator(f: number): Comparator {
+ if (!isFiniteF32(f)) return anyI32;
+ const acceptable: number[] = [
+ reinterpretF32AsI32(f),
+ ...(isSubnormalNumberF32(f) ? f32ZerosInI32 : []),
+ ];
+ return anyOf(...acceptable.map(i32));
+}
+
+/**
+ * @returns a Comparator for checking if a f32 value is a valid
+ * bitcast conversion from i32.
+ */
+function bitcastI32ToF32Comparator(i: number): Comparator {
+ const f: number = reinterpretI32AsF32(i);
+ if (!isFiniteF32(f)) return anyI32;
+ // Positive or negative zero bit pattern map to any zero.
+ if (f32ZerosInI32.includes(i)) return anyOf(...f32ZerosInF32.map(f32));
+ const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
+ return anyOf(...acceptable.map(f32));
+}
+
+/**
+ * @returns a Comparator for checking if a f32 value is a valid
+ * bitcast conversion from u32.
+ */
+function bitcastU32ToF32Comparator(u: number): Comparator {
+ const f: number = reinterpretU32AsF32(u);
+ if (!isFiniteF32(f)) return anyU32;
+ // Positive or negative zero bit pattern map to any zero.
+ if (f32ZerosInU32.includes(u)) return anyOf(...f32ZerosInF32.map(f32));
+ const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
+ return anyOf(...acceptable.map(f32));
+}
+
+/**
+ * @returns an array of expected f16 FPInterval for the given bitcasted f16 value, which may be
+ * subnormal, Inf, or NaN. Test cases that bitcasted to vector of f16 use this function to get
+ * per-element expectation and build vector expectation using cartesianProduct.
+ */
+function generateF16ExpectationIntervals(bitcastedF16Value: number): FPInterval[] {
+ // If the bitcasted f16 value is inf or nan, the result is unbounded
+ if (!isFiniteF16(bitcastedF16Value)) {
+ return [f16UnboundedInterval];
+ }
+ // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0.
+ if (bitcastedF16Value === 0.0) {
+ return [f16ZerosInterval];
+ }
+ const exactInterval = FP.f16.toInterval(bitcastedF16Value);
+ // If the casted f16 value is subnormal, it also may be flushed to +/-0.0.
+ return [exactInterval, ...(isSubnormalNumberF16(bitcastedF16Value) ? [f16ZerosInterval] : [])];
+}
+
+/**
+ * @returns a Comparator for checking if a f16 value is a valid
+ * bitcast conversion from f16.
+ */
+function bitcastF16ToF16Comparator(f: number): Comparator {
+ if (!isFiniteF16(f)) return anyOf(f16UnboundedInterval);
+ return anyOf(...generateF16ExpectationIntervals(f));
+}
+
+/**
+ * @returns a Comparator for checking if a vec2<f16> is a valid bitcast
+ * conversion from u32.
+ */
+function bitcastU32ToVec2F16Comparator(u: number): Comparator {
+ const bitcastedVec2F16InU16x2 = u32ToU16x2(u).map(reinterpretU16AsF16);
+ // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec2<f16> value is a valid
+ * bitcast conversion from i32.
+ */
+function bitcastI32ToVec2F16Comparator(i: number): Comparator {
+ const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretI32AsU32(i)).map(reinterpretU16AsF16);
+ // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec2<f16> value is a valid
+ * bitcast conversion from f32.
+ */
+function bitcastF32ToVec2F16Comparator(f: number): Comparator {
+ // If input f32 is not finite, it can be evaluated to any value and thus any result f16 vec2 is
+ // possible.
+ if (!isFiniteF32(f)) {
+ return anyOf([f16UnboundedInterval, f16UnboundedInterval]);
+ }
+ const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretF32AsU32(f)).map(reinterpretU16AsF16);
+ // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec4<f16> is a valid
+ * bitcast conversion from vec2<u32>.
+ */
+function bitcastVec2U32ToVec4F16Comparator(u32x2: number[]): Comparator {
+ assert(u32x2.length === 2);
+ const bitcastedVec4F16InU16x4 = u32x2.flatMap(u32ToU16x2).map(reinterpretU16AsF16);
+ // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec4<f16> is a valid
+ * bitcast conversion from vec2<i32>.
+ */
+function bitcastVec2I32ToVec4F16Comparator(i32x2: number[]): Comparator {
+ assert(i32x2.length === 2);
+ const bitcastedVec4F16InU16x4 = i32x2
+ .map(reinterpretI32AsU32)
+ .flatMap(u32ToU16x2)
+ .map(reinterpretU16AsF16);
+ // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec4<f16> is a valid
+ * bitcast conversion from vec2<f32>.
+ */
+function bitcastVec2F32ToVec4F16Comparator(f32x2: number[]): Comparator {
+ assert(f32x2.length === 2);
+ const bitcastedVec4F16InU16x4 = f32x2
+ .map(reinterpretF32AsU32)
+ .flatMap(u32ToU16x2)
+ .map(reinterpretU16AsF16);
+ // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+// Structure that store the expectations of a single 32bit scalar/element bitcasted from two f16.
+interface ExpectionFor32BitsScalarFromF16x2 {
+ // possibleExpectations is Scalar array if the expectation is for i32/u32 and FPInterval array for
+ // f32. Note that if the expectation for i32/u32 is unbound, possibleExpectations is meaningless.
+ possibleExpectations: (ScalarValue | FPInterval)[];
+ isUnbounded: boolean;
+}
+
+/**
+ * @returns the array of possible 16bits, represented in u16, that bitcasted
+ * from a given finite f16 represented in u16, handling the possible subnormal
+ * flushing. Used to build up 32bits or larger results.
+ */
+function possibleBitsInU16FromFiniteF16InU16(f16InU16: number): number[] {
+ const h = reinterpretU16AsF16(f16InU16);
+ assert(isFiniteF16(h));
+ return [f16InU16, ...(isSubnormalNumberF16(h) ? f16ZerosInU16 : [])];
+}
+
+/**
+ * @returns the expectation for a single 32bit scalar bitcasted from given pair of
+ * f16, result in ExpectionFor32BitsScalarFromF16x2.
+ */
+function possible32BitScalarIntervalsFromF16x2(
+ f16x2InU16x2: number[],
+ type: 'i32' | 'u32' | 'f32'
+): ExpectionFor32BitsScalarFromF16x2 {
+ assert(f16x2InU16x2.length === 2);
+ let reinterpretFromU32: (x: number) => number;
+ let expectationsForValue: (x: number) => ScalarValue[] | FPInterval[];
+ let unboundedExpectations: FPInterval[] | ScalarValue[];
+ if (type === 'u32') {
+ reinterpretFromU32 = (x: number) => x;
+ expectationsForValue = x => [u32(x)];
+ // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a
+ // placeholder, and the possibleExpectations should be ignored if the result is unbounded.
+ unboundedExpectations = [u32(0)];
+ } else if (type === 'i32') {
+ reinterpretFromU32 = (x: number) => reinterpretU32AsI32(x);
+ expectationsForValue = x => [i32(x)];
+ // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a
+ // placeholder, and the possibleExpectations should be ignored if the result is unbounded.
+ unboundedExpectations = [i32(0)];
+ } else {
+ assert(type === 'f32');
+ reinterpretFromU32 = (x: number) => reinterpretU32AsF32(x);
+ expectationsForValue = x => {
+ // Handle the possible Inf/NaN/zeros and subnormal cases for f32 result.
+ if (!isFiniteF32(x)) {
+ return [f32UnboundedInterval];
+ }
+ // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0.
+ if (x === 0.0) {
+ return [f32ZerosInterval];
+ }
+ const exactInterval = FP.f32.toInterval(x);
+ // If the casted f16 value is subnormal, it also may be flushed to +/-0.0.
+ return [exactInterval, ...(isSubnormalNumberF32(x) ? [f32ZerosInterval] : [])];
+ };
+ unboundedExpectations = [f32UnboundedInterval];
+ }
+ // Return unbounded expection if f16 Inf/NaN occurs
+ if (
+ !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[0])) ||
+ !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[1]))
+ ) {
+ return { possibleExpectations: unboundedExpectations, isUnbounded: true };
+ }
+ const possibleU16Bits = f16x2InU16x2.map(possibleBitsInU16FromFiniteF16InU16);
+ const possibleExpectations = cartesianProduct(...possibleU16Bits).flatMap<
+ ScalarValue | FPInterval
+ >((possibleBitsU16x2: readonly number[]) => {
+ assert(possibleBitsU16x2.length === 2);
+ return expectationsForValue(reinterpretFromU32(u16x2ToU32(possibleBitsU16x2)));
+ });
+ return { possibleExpectations, isUnbounded: false };
+}
+
+/**
+ * @returns a Comparator for checking if a u32 value is a valid
+ * bitcast conversion from vec2 f16.
+ */
+function bitcastVec2F16ToU32Comparator(vec2F16InU16x2: number[]): Comparator {
+ assert(vec2F16InU16x2.length === 2);
+ const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'u32');
+ // Return alwaysPass if result is expected unbounded.
+ if (expectations.isUnbounded) {
+ return anyU32;
+ }
+ return anyOf(...expectations.possibleExpectations);
+}
+
+/**
+ * @returns a Comparator for checking if a i32 value is a valid
+ * bitcast conversion from vec2 f16.
+ */
+function bitcastVec2F16ToI32Comparator(vec2F16InU16x2: number[]): Comparator {
+ assert(vec2F16InU16x2.length === 2);
+ const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'i32');
+ // Return alwaysPass if result is expected unbounded.
+ if (expectations.isUnbounded) {
+ return anyI32;
+ }
+ return anyOf(...expectations.possibleExpectations);
+}
+
+/**
+ * @returns a Comparator for checking if a i32 value is a valid
+ * bitcast conversion from vec2 f16.
+ */
+function bitcastVec2F16ToF32Comparator(vec2F16InU16x2: number[]): Comparator {
+ assert(vec2F16InU16x2.length === 2);
+ const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'f32');
+ // Return alwaysPass if result is expected unbounded.
+ if (expectations.isUnbounded) {
+ return anyF32;
+ }
+ return anyOf(...expectations.possibleExpectations);
+}
+
+/**
+ * @returns a Comparator for checking if a vec2 u32 value is a valid
+ * bitcast conversion from vec4 f16.
+ */
+function bitcastVec4F16ToVec2U32Comparator(vec4F16InU16x4: number[]): Comparator {
+ assert(vec4F16InU16x4.length === 4);
+ const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
+ possible32BitScalarIntervalsFromF16x2(e, 'u32')
+ );
+ // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded
+ // element in the result vector, currently we don't have a way to build a comparator that expect
+ // only one element of i32/u32 vector unbounded.
+ if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) {
+ return alwaysPass('any vec2<u32>');
+ }
+ return anyOf(
+ ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(
+ e => new VectorValue(e as ScalarValue[])
+ )
+ );
+}
+
+/**
+ * @returns a Comparator for checking if a vec2 i32 value is a valid
+ * bitcast conversion from vec4 f16.
+ */
+function bitcastVec4F16ToVec2I32Comparator(vec4F16InU16x4: number[]): Comparator {
+ assert(vec4F16InU16x4.length === 4);
+ const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
+ possible32BitScalarIntervalsFromF16x2(e, 'i32')
+ );
+ // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded
+ // element in the result vector, currently we don't have a way to build a comparator that expect
+ // only one element of i32/u32 vector unbounded.
+ if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) {
+ return alwaysPass('any vec2<i32>');
+ }
+ return anyOf(
+ ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(
+ e => new VectorValue(e as ScalarValue[])
+ )
+ );
+}
+
+/**
+ * @returns a Comparator for checking if a vec2 f32 value is a valid
+ * bitcast conversion from vec4 f16.
+ */
+function bitcastVec4F16ToVec2F32Comparator(vec4F16InU16x4: number[]): Comparator {
+ assert(vec4F16InU16x4.length === 4);
+ const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
+ possible32BitScalarIntervalsFromF16x2(e, 'f32')
+ );
+ return anyOf(
+ ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(e => [
+ e[0] as FPInterval,
+ e[1] as FPInterval,
+ ])
+ );
+}
+
+export const d = makeCaseCache('bitcast', {
+ // Identity Cases
+ i32_to_i32: () => fullI32Range().map(e => ({ input: i32(e), expected: i32(e) })),
+ u32_to_u32: () => fullU32Range().map(e => ({ input: u32(e), expected: u32(e) })),
+ f32_inf_nan_to_f32: () =>
+ f32RangeWithInfAndNaN.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToF32Comparator(e),
+ })),
+ f32_to_f32: () =>
+ f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToF32Comparator(e) })),
+ f16_inf_nan_to_f16: () =>
+ [...f16FiniteInF16, ...f16InfAndNaNInF16].map(e => ({
+ input: f16(e),
+ expected: bitcastF16ToF16Comparator(e),
+ })),
+ f16_to_f16: () =>
+ f16FiniteInF16.map(e => ({ input: f16(e), expected: bitcastF16ToF16Comparator(e) })),
+
+ // i32,u32,f32,Abstract to different i32,u32,f32
+ i32_to_u32: () => fullI32Range().map(e => ({ input: i32(e), expected: u32(e) })),
+ i32_to_f32: () =>
+ i32RangeForF32Finite.map(e => ({
+ input: i32(e),
+ expected: bitcastI32ToF32Comparator(e),
+ })),
+ ai_to_i32: () => fullI32Range().map(e => ({ input: abstractInt(BigInt(e)), expected: i32(e) })),
+ ai_to_u32: () => fullU32Range().map(e => ({ input: abstractInt(BigInt(e)), expected: u32(e) })),
+ ai_to_f32: () =>
+ // AbstractInt is converted to i32, because there is no explicit overload
+ i32RangeForF32Finite.map(e => ({
+ input: abstractInt(BigInt(e)),
+ expected: bitcastI32ToF32Comparator(e),
+ })),
+ i32_to_f32_inf_nan: () =>
+ i32RangeForF32FiniteInfNaN.map(e => ({
+ input: i32(e),
+ expected: bitcastI32ToF32Comparator(e),
+ })),
+ u32_to_i32: () => fullU32Range().map(e => ({ input: u32(e), expected: i32(e) })),
+ u32_to_f32: () =>
+ u32RangeForF32Finite.map(e => ({
+ input: u32(e),
+ expected: bitcastU32ToF32Comparator(e),
+ })),
+ u32_to_f32_inf_nan: () =>
+ u32RangeForF32FiniteInfNaN.map(e => ({
+ input: u32(e),
+ expected: bitcastU32ToF32Comparator(e),
+ })),
+ f32_inf_nan_to_i32: () =>
+ f32RangeWithInfAndNaN.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToI32Comparator(e),
+ })),
+ f32_to_i32: () =>
+ f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToI32Comparator(e) })),
+
+ f32_inf_nan_to_u32: () =>
+ f32RangeWithInfAndNaN.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToU32Comparator(e),
+ })),
+ f32_to_u32: () =>
+ f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToU32Comparator(e) })),
+
+ // i32,u32,f32,AbstractInt to vec2<f16>
+ u32_to_vec2_f16_inf_nan: () =>
+ u32RangeForF16Vec2FiniteInfNaN.map(e => ({
+ input: u32(e),
+ expected: bitcastU32ToVec2F16Comparator(e),
+ })),
+ u32_to_vec2_f16: () =>
+ u32RangeForF16Vec2Finite.map(e => ({
+ input: u32(e),
+ expected: bitcastU32ToVec2F16Comparator(e),
+ })),
+ i32_to_vec2_f16_inf_nan: () =>
+ i32RangeForF16Vec2FiniteInfNaN.map(e => ({
+ input: i32(e),
+ expected: bitcastI32ToVec2F16Comparator(e),
+ })),
+ i32_to_vec2_f16: () =>
+ i32RangeForF16Vec2Finite.map(e => ({
+ input: i32(e),
+ expected: bitcastI32ToVec2F16Comparator(e),
+ })),
+ ai_to_vec2_f16: () =>
+ // AbstractInt is converted to i32, because there is no explicit overload
+ i32RangeForF16Vec2Finite.map(e => ({
+ input: abstractInt(BigInt(e)),
+ expected: bitcastI32ToVec2F16Comparator(e),
+ })),
+ f32_inf_nan_to_vec2_f16_inf_nan: () =>
+ f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToVec2F16Comparator(e),
+ })),
+ f32_to_vec2_f16: () =>
+ f32FiniteRangeForF16Vec2Finite.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToVec2F16Comparator(e),
+ })),
+ af_to_vec2_f16: () =>
+ f32FiniteRangeForF16Vec2Finite.map(e => ({
+ input: abstractFloat(e),
+ expected: bitcastF32ToVec2F16Comparator(e),
+ })),
+
+ // vec2<i32>, vec2<u32>, vec2<f32>, vec2<AbstractInt> to vec4<f16>
+ vec2_i32_to_vec4_f16_inf_nan: () =>
+ slidingSlice(i32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({
+ input: toVector(e, i32),
+ expected: bitcastVec2I32ToVec4F16Comparator(e),
+ })),
+ vec2_i32_to_vec4_f16: () =>
+ slidingSlice(i32RangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, i32),
+ expected: bitcastVec2I32ToVec4F16Comparator(e),
+ })),
+ vec2_ai_to_vec4_f16: () =>
+ // AbstractInt is converted to i32, because there is no explicit overload
+ slidingSlice(i32RangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, (n: number) => abstractInt(BigInt(n))),
+ expected: bitcastVec2I32ToVec4F16Comparator(e),
+ })),
+ vec2_u32_to_vec4_f16_inf_nan: () =>
+ slidingSlice(u32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({
+ input: toVector(e, u32),
+ expected: bitcastVec2U32ToVec4F16Comparator(e),
+ })),
+ vec2_u32_to_vec4_f16: () =>
+ slidingSlice(u32RangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, u32),
+ expected: bitcastVec2U32ToVec4F16Comparator(e),
+ })),
+ vec2_f32_inf_nan_to_vec4_f16_inf_nan: () =>
+ slidingSlice(f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN, 2).map(e => ({
+ input: toVector(e, f32),
+ expected: bitcastVec2F32ToVec4F16Comparator(e),
+ })),
+ vec2_f32_to_vec4_f16: () =>
+ slidingSlice(f32FiniteRangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, f32),
+ expected: bitcastVec2F32ToVec4F16Comparator(e),
+ })),
+ vec2_af_to_vec4_f16: () =>
+ slidingSlice(f32FiniteRangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, abstractFloat),
+ expected: bitcastVec2F32ToVec4F16Comparator(e),
+ })),
+
+ // vec2<f16> to i32, u32, f32
+ vec2_f16_to_u32: () =>
+ f16Vec2FiniteInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToU32Comparator(e),
+ })),
+ vec2_f16_inf_nan_to_u32: () =>
+ f16Vec2FiniteInfNanInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToU32Comparator(e),
+ })),
+ vec2_f16_to_i32: () =>
+ f16Vec2FiniteInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToI32Comparator(e),
+ })),
+ vec2_f16_inf_nan_to_i32: () =>
+ f16Vec2FiniteInfNanInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToI32Comparator(e),
+ })),
+ vec2_f16_to_f32_finite: () =>
+ f16Vec2FiniteInU16x2
+ .filter(u16x2 => isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x2))))
+ .map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToF32Comparator(e),
+ })),
+ vec2_f16_inf_nan_to_f32: () =>
+ f16Vec2FiniteInfNanInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToF32Comparator(e),
+ })),
+
+ // vec4<f16> to vec2 of i32, u32, f32
+ vec4_f16_to_vec2_u32: () =>
+ f16Vec2FiniteInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2U32Comparator(e),
+ })),
+ vec4_f16_inf_nan_to_vec2_u32: () =>
+ f16Vec2FiniteInfNanInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2U32Comparator(e),
+ })),
+ vec4_f16_to_vec2_i32: () =>
+ f16Vec2FiniteInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2I32Comparator(e),
+ })),
+ vec4_f16_inf_nan_to_vec2_i32: () =>
+ f16Vec2FiniteInfNanInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2I32Comparator(e),
+ })),
+ vec4_f16_to_vec2_f32_finite: () =>
+ f16Vec2FiniteInU16x4
+ .filter(
+ u16x4 =>
+ isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(0, 2)))) &&
+ isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(2, 4))))
+ )
+ .map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2F32Comparator(e),
+ })),
+ vec4_f16_inf_nan_to_vec2_f32: () =>
+ f16Vec2FiniteInfNanInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2F32Comparator(e),
+ })),
+});