summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts196
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts78
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts92
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts92
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/arrayLength.spec.ts306
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts78
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts65
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts80
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts83
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts87
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts101
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts135
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts742
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts470
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts192
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts101
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts100
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts131
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts301
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts101
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts135
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts208
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts1275
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts101
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts195
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts84
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts68
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts250
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts249
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts250
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts113
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts95
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts137
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts241
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts182
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts22
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts21
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts22
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts22
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts21
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts90
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts90
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts337
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts256
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts350
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts250
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts96
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts113
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts103
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts475
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidth.spec.ts21
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthCoarse.spec.ts21
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthFine.spec.ts21
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts386
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts121
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts178
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts89
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts89
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts165
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts164
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts275
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts661
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts137
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts88
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts55
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts55
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts88
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts70
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts90
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts180
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts253
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts250
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts79
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts100
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts253
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts109
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts84
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts68
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts68
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts87
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/storageBarrier.spec.ts38
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts78
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts160
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGather.spec.ts270
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.ts134
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.ts185
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLayers.spec.ts100
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLevels.spec.ts65
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumSamples.spec.ts37
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts273
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts163
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts145
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel.spec.ts149
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleGrad.spec.ts136
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleLevel.spec.ts274
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureStore.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts158
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts75
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts48
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts48
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts48
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts48
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts48
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/utils.ts45
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupBarrier.spec.ts38
113 files changed, 17057 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts
new file mode 100644
index 0000000000..05d5242f73
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts
@@ -0,0 +1,196 @@
+export const description = `
+Execution tests for the 'abs' builtin function
+
+S is AbstractInt, i32, or u32
+T is S or vecN<S>
+@const fn abs(e: T ) -> T
+The absolute value of e. Component-wise when T is a vector. If e is a signed
+integral scalar type and evaluates to the largest negative value, then the
+result is e. If e is an unsigned integral type, then the result is e.
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn abs(e: T ) -> T
+Returns the absolute value of e (e.g. e with a positive sign bit).
+Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kBit } from '../../../../../util/constants.js';
+import {
+ i32Bits,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeU32,
+ u32Bits,
+ TypeAbstractFloat,
+} from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, fullF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('abs', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.absInterval);
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.absInterval);
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ fullF64Range(),
+ 'unfiltered',
+ FP.abstract.absInterval
+ );
+ },
+});
+
+g.test('abstract_int')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`abstract int tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`unsigned int tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ await run(t, builtin('abs'), [TypeU32], TypeU32, t.params, [
+ // Min and Max u32
+ { input: u32Bits(kBit.u32.min), expected: u32Bits(kBit.u32.min) },
+ { input: u32Bits(kBit.u32.max), expected: u32Bits(kBit.u32.max) },
+ // Powers of 2: -2^i: 0 =< i =< 31
+ { input: u32Bits(kBit.powTwo.to0), expected: u32Bits(kBit.powTwo.to0) },
+ { input: u32Bits(kBit.powTwo.to1), expected: u32Bits(kBit.powTwo.to1) },
+ { input: u32Bits(kBit.powTwo.to2), expected: u32Bits(kBit.powTwo.to2) },
+ { input: u32Bits(kBit.powTwo.to3), expected: u32Bits(kBit.powTwo.to3) },
+ { input: u32Bits(kBit.powTwo.to4), expected: u32Bits(kBit.powTwo.to4) },
+ { input: u32Bits(kBit.powTwo.to5), expected: u32Bits(kBit.powTwo.to5) },
+ { input: u32Bits(kBit.powTwo.to6), expected: u32Bits(kBit.powTwo.to6) },
+ { input: u32Bits(kBit.powTwo.to7), expected: u32Bits(kBit.powTwo.to7) },
+ { input: u32Bits(kBit.powTwo.to8), expected: u32Bits(kBit.powTwo.to8) },
+ { input: u32Bits(kBit.powTwo.to9), expected: u32Bits(kBit.powTwo.to9) },
+ { input: u32Bits(kBit.powTwo.to10), expected: u32Bits(kBit.powTwo.to10) },
+ { input: u32Bits(kBit.powTwo.to11), expected: u32Bits(kBit.powTwo.to11) },
+ { input: u32Bits(kBit.powTwo.to12), expected: u32Bits(kBit.powTwo.to12) },
+ { input: u32Bits(kBit.powTwo.to13), expected: u32Bits(kBit.powTwo.to13) },
+ { input: u32Bits(kBit.powTwo.to14), expected: u32Bits(kBit.powTwo.to14) },
+ { input: u32Bits(kBit.powTwo.to15), expected: u32Bits(kBit.powTwo.to15) },
+ { input: u32Bits(kBit.powTwo.to16), expected: u32Bits(kBit.powTwo.to16) },
+ { input: u32Bits(kBit.powTwo.to17), expected: u32Bits(kBit.powTwo.to17) },
+ { input: u32Bits(kBit.powTwo.to18), expected: u32Bits(kBit.powTwo.to18) },
+ { input: u32Bits(kBit.powTwo.to19), expected: u32Bits(kBit.powTwo.to19) },
+ { input: u32Bits(kBit.powTwo.to20), expected: u32Bits(kBit.powTwo.to20) },
+ { input: u32Bits(kBit.powTwo.to21), expected: u32Bits(kBit.powTwo.to21) },
+ { input: u32Bits(kBit.powTwo.to22), expected: u32Bits(kBit.powTwo.to22) },
+ { input: u32Bits(kBit.powTwo.to23), expected: u32Bits(kBit.powTwo.to23) },
+ { input: u32Bits(kBit.powTwo.to24), expected: u32Bits(kBit.powTwo.to24) },
+ { input: u32Bits(kBit.powTwo.to25), expected: u32Bits(kBit.powTwo.to25) },
+ { input: u32Bits(kBit.powTwo.to26), expected: u32Bits(kBit.powTwo.to26) },
+ { input: u32Bits(kBit.powTwo.to27), expected: u32Bits(kBit.powTwo.to27) },
+ { input: u32Bits(kBit.powTwo.to28), expected: u32Bits(kBit.powTwo.to28) },
+ { input: u32Bits(kBit.powTwo.to29), expected: u32Bits(kBit.powTwo.to29) },
+ { input: u32Bits(kBit.powTwo.to30), expected: u32Bits(kBit.powTwo.to30) },
+ { input: u32Bits(kBit.powTwo.to31), expected: u32Bits(kBit.powTwo.to31) },
+ ]);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`signed int tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ await run(t, builtin('abs'), [TypeI32], TypeI32, t.params, [
+ // Min and max i32
+ // If e evaluates to the largest negative value, then the result is e.
+ { input: i32Bits(kBit.i32.negative.min), expected: i32Bits(kBit.i32.negative.min) },
+ { input: i32Bits(kBit.i32.negative.max), expected: i32Bits(kBit.i32.positive.min) },
+ { input: i32Bits(kBit.i32.positive.max), expected: i32Bits(kBit.i32.positive.max) },
+ { input: i32Bits(kBit.i32.positive.min), expected: i32Bits(kBit.i32.positive.min) },
+ // input: -1 * pow(2, n), n = {-31, ..., 0 }, expected: pow(2, n), n = {-31, ..., 0}]
+ { input: i32Bits(kBit.negPowTwo.to0), expected: i32Bits(kBit.powTwo.to0) },
+ { input: i32Bits(kBit.negPowTwo.to1), expected: i32Bits(kBit.powTwo.to1) },
+ { input: i32Bits(kBit.negPowTwo.to2), expected: i32Bits(kBit.powTwo.to2) },
+ { input: i32Bits(kBit.negPowTwo.to3), expected: i32Bits(kBit.powTwo.to3) },
+ { input: i32Bits(kBit.negPowTwo.to4), expected: i32Bits(kBit.powTwo.to4) },
+ { input: i32Bits(kBit.negPowTwo.to5), expected: i32Bits(kBit.powTwo.to5) },
+ { input: i32Bits(kBit.negPowTwo.to6), expected: i32Bits(kBit.powTwo.to6) },
+ { input: i32Bits(kBit.negPowTwo.to7), expected: i32Bits(kBit.powTwo.to7) },
+ { input: i32Bits(kBit.negPowTwo.to8), expected: i32Bits(kBit.powTwo.to8) },
+ { input: i32Bits(kBit.negPowTwo.to9), expected: i32Bits(kBit.powTwo.to9) },
+ { input: i32Bits(kBit.negPowTwo.to10), expected: i32Bits(kBit.powTwo.to10) },
+ { input: i32Bits(kBit.negPowTwo.to11), expected: i32Bits(kBit.powTwo.to11) },
+ { input: i32Bits(kBit.negPowTwo.to12), expected: i32Bits(kBit.powTwo.to12) },
+ { input: i32Bits(kBit.negPowTwo.to13), expected: i32Bits(kBit.powTwo.to13) },
+ { input: i32Bits(kBit.negPowTwo.to14), expected: i32Bits(kBit.powTwo.to14) },
+ { input: i32Bits(kBit.negPowTwo.to15), expected: i32Bits(kBit.powTwo.to15) },
+ { input: i32Bits(kBit.negPowTwo.to16), expected: i32Bits(kBit.powTwo.to16) },
+ { input: i32Bits(kBit.negPowTwo.to17), expected: i32Bits(kBit.powTwo.to17) },
+ { input: i32Bits(kBit.negPowTwo.to18), expected: i32Bits(kBit.powTwo.to18) },
+ { input: i32Bits(kBit.negPowTwo.to19), expected: i32Bits(kBit.powTwo.to19) },
+ { input: i32Bits(kBit.negPowTwo.to20), expected: i32Bits(kBit.powTwo.to20) },
+ { input: i32Bits(kBit.negPowTwo.to21), expected: i32Bits(kBit.powTwo.to21) },
+ { input: i32Bits(kBit.negPowTwo.to22), expected: i32Bits(kBit.powTwo.to22) },
+ { input: i32Bits(kBit.negPowTwo.to23), expected: i32Bits(kBit.powTwo.to23) },
+ { input: i32Bits(kBit.negPowTwo.to24), expected: i32Bits(kBit.powTwo.to24) },
+ { input: i32Bits(kBit.negPowTwo.to25), expected: i32Bits(kBit.powTwo.to25) },
+ { input: i32Bits(kBit.negPowTwo.to26), expected: i32Bits(kBit.powTwo.to26) },
+ { input: i32Bits(kBit.negPowTwo.to27), expected: i32Bits(kBit.powTwo.to27) },
+ { input: i32Bits(kBit.negPowTwo.to28), expected: i32Bits(kBit.powTwo.to28) },
+ { input: i32Bits(kBit.negPowTwo.to29), expected: i32Bits(kBit.powTwo.to29) },
+ { input: i32Bits(kBit.negPowTwo.to30), expected: i32Bits(kBit.powTwo.to30) },
+ { input: i32Bits(kBit.negPowTwo.to31), expected: i32Bits(kBit.powTwo.to31) },
+ ]);
+ });
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(t, abstractBuiltin('abs'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ });
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`float 32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('abs'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('abs'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts
new file mode 100644
index 0000000000..5755c07905
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts
@@ -0,0 +1,78 @@
+export const description = `
+Execution tests for the 'acos' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn acos(e: T ) -> T
+Returns the arc cosine of e. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const f32_inputs = [
+ ...linearRange(-1, 1, 100), // acos is defined on [-1, 1]
+ ...fullF32Range(),
+];
+
+const f16_inputs = [
+ ...linearRange(-1, 1, 100), // acos is defined on [-1, 1]
+ ...fullF16Range(),
+];
+
+export const d = makeCaseCache('acos', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.acosInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.acosInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.acosInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.acosInterval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('acos'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('acos'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts
new file mode 100644
index 0000000000..cc78ce3eee
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts
@@ -0,0 +1,81 @@
+export const description = `
+Execution tests for the 'acosh' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn acosh(e: T ) -> T
+Returns the hyperbolic arc cosine of e. The result is 0 when e < 1.
+Computes the non-negative functional inverse of cosh.
+Component-wise when T is a vector.
+Note: The result is not mathematically meaningful when e < 1.
+
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const f32_inputs = [
+ ...biasedRange(1, 2, 100), // x near 1 can be problematic to implement
+ ...fullF32Range(),
+];
+const f16_inputs = [
+ ...biasedRange(1, 2, 100), // x near 1 can be problematic to implement
+ ...fullF16Range(),
+];
+
+export const d = makeCaseCache('acosh', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', ...FP.f32.acoshIntervals);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', ...FP.f32.acoshIntervals);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', ...FP.f16.acoshIntervals);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', ...FP.f16.acoshIntervals);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('acosh'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('acosh'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts
new file mode 100644
index 0000000000..9a2938c1d5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts
@@ -0,0 +1,92 @@
+export const description = `
+Execution tests for the 'all' builtin function
+
+S is a bool
+T is S or vecN<S>
+@const fn all(e: T) -> bool
+Returns e if e is scalar.
+Returns true if each component of e is true if e is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ False,
+ True,
+ TypeBool,
+ TypeVec,
+ vec2,
+ vec3,
+ vec4,
+} from '../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('bool')
+ .specURL('https://www.w3.org/TR/WGSL/#logical-builtin-functions')
+ .desc(`bool tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('overload', ['scalar', 'vec2', 'vec3', 'vec4'] as const)
+ )
+ .fn(async t => {
+ const overloads = {
+ scalar: {
+ type: TypeBool,
+ cases: [
+ { input: False, expected: False },
+ { input: True, expected: True },
+ ],
+ },
+ vec2: {
+ type: TypeVec(2, TypeBool),
+ cases: [
+ { input: vec2(False, False), expected: False },
+ { input: vec2(True, False), expected: False },
+ { input: vec2(False, True), expected: False },
+ { input: vec2(True, True), expected: True },
+ ],
+ },
+ vec3: {
+ type: TypeVec(3, TypeBool),
+ cases: [
+ { input: vec3(False, False, False), expected: False },
+ { input: vec3(True, False, False), expected: False },
+ { input: vec3(False, True, False), expected: False },
+ { input: vec3(True, True, False), expected: False },
+ { input: vec3(False, False, True), expected: False },
+ { input: vec3(True, False, True), expected: False },
+ { input: vec3(False, True, True), expected: False },
+ { input: vec3(True, True, True), expected: True },
+ ],
+ },
+ vec4: {
+ type: TypeVec(4, TypeBool),
+ cases: [
+ { input: vec4(False, False, False, False), expected: False },
+ { input: vec4(False, True, False, False), expected: False },
+ { input: vec4(False, False, True, False), expected: False },
+ { input: vec4(False, True, True, False), expected: False },
+ { input: vec4(False, False, False, True), expected: False },
+ { input: vec4(False, True, False, True), expected: False },
+ { input: vec4(False, False, True, True), expected: False },
+ { input: vec4(False, True, True, True), expected: False },
+ { input: vec4(True, False, False, False), expected: False },
+ { input: vec4(True, False, False, True), expected: False },
+ { input: vec4(True, False, True, False), expected: False },
+ { input: vec4(True, False, True, True), expected: False },
+ { input: vec4(True, True, False, False), expected: False },
+ { input: vec4(True, True, False, True), expected: False },
+ { input: vec4(True, True, True, False), expected: False },
+ { input: vec4(True, True, True, True), expected: True },
+ ],
+ },
+ };
+ const overload = overloads[t.params.overload];
+
+ await run(t, builtin('all'), [overload.type], TypeBool, t.params, overload.cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts
new file mode 100644
index 0000000000..19ed7d186f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts
@@ -0,0 +1,92 @@
+export const description = `
+Execution tests for the 'any' builtin function
+
+S is a bool
+T is S or vecN<S>
+@const fn all(e) -> bool
+Returns e if e is scalar.
+Returns true if any component of e is true if e is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ False,
+ True,
+ TypeBool,
+ TypeVec,
+ vec2,
+ vec3,
+ vec4,
+} from '../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('bool')
+ .specURL('https://www.w3.org/TR/WGSL/#logical-builtin-functions')
+ .desc(`bool tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('overload', ['scalar', 'vec2', 'vec3', 'vec4'] as const)
+ )
+ .fn(async t => {
+ const overloads = {
+ scalar: {
+ type: TypeBool,
+ cases: [
+ { input: False, expected: False },
+ { input: True, expected: True },
+ ],
+ },
+ vec2: {
+ type: TypeVec(2, TypeBool),
+ cases: [
+ { input: vec2(False, False), expected: False },
+ { input: vec2(True, False), expected: True },
+ { input: vec2(False, True), expected: True },
+ { input: vec2(True, True), expected: True },
+ ],
+ },
+ vec3: {
+ type: TypeVec(3, TypeBool),
+ cases: [
+ { input: vec3(False, False, False), expected: False },
+ { input: vec3(True, False, False), expected: True },
+ { input: vec3(False, True, False), expected: True },
+ { input: vec3(True, True, False), expected: True },
+ { input: vec3(False, False, True), expected: True },
+ { input: vec3(True, False, True), expected: True },
+ { input: vec3(False, True, True), expected: True },
+ { input: vec3(True, True, True), expected: True },
+ ],
+ },
+ vec4: {
+ type: TypeVec(4, TypeBool),
+ cases: [
+ { input: vec4(False, False, False, False), expected: False },
+ { input: vec4(False, True, False, False), expected: True },
+ { input: vec4(False, False, True, False), expected: True },
+ { input: vec4(False, True, True, False), expected: True },
+ { input: vec4(False, False, False, True), expected: True },
+ { input: vec4(False, True, False, True), expected: True },
+ { input: vec4(False, False, True, True), expected: True },
+ { input: vec4(False, True, True, True), expected: True },
+ { input: vec4(True, False, False, False), expected: True },
+ { input: vec4(True, False, False, True), expected: True },
+ { input: vec4(True, False, True, False), expected: True },
+ { input: vec4(True, False, True, True), expected: True },
+ { input: vec4(True, True, False, False), expected: True },
+ { input: vec4(True, True, False, True), expected: True },
+ { input: vec4(True, True, True, False), expected: True },
+ { input: vec4(True, True, True, True), expected: True },
+ ],
+ },
+ };
+ const overload = overloads[t.params.overload];
+
+ await run(t, builtin('any'), [overload.type], TypeBool, t.params, overload.cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/arrayLength.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/arrayLength.spec.ts
new file mode 100644
index 0000000000..e5c20391d8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/arrayLength.spec.ts
@@ -0,0 +1,306 @@
+export const description = `
+Execution tests for the 'arrayLength' builtin function.
+
+fn arrayLength(e: ptr<storage,array<T>> ) -> u32
+Returns the number of elements in the runtime-sized array.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { align } from '../../../../../util/math.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// List of array element types to test.
+const kTestTypes = [
+ { type: 'u32', stride: 4 },
+ { type: 'i32', stride: 4 },
+ { type: 'f32', stride: 4 },
+ { type: 'f16', stride: 2 },
+ { type: 'vec2<u32>', stride: 8 },
+ { type: 'vec2<i32>', stride: 8 },
+ { type: 'vec2<f32>', stride: 8 },
+ { type: 'vec2<f16>', stride: 4 },
+ { type: 'vec3<u32>', stride: 16 },
+ { type: 'vec3<i32>', stride: 16 },
+ { type: 'vec3<f32>', stride: 16 },
+ { type: 'vec3<f16>', stride: 8 },
+ { type: 'vec4<u32>', stride: 16 },
+ { type: 'vec4<i32>', stride: 16 },
+ { type: 'vec4<f32>', stride: 16 },
+ { type: 'vec4<f16>', stride: 8 },
+ { type: 'mat2x2<f32>', stride: 16 },
+ { type: 'mat2x3<f32>', stride: 32 },
+ { type: 'mat2x4<f32>', stride: 32 },
+ { type: 'mat3x2<f32>', stride: 24 },
+ { type: 'mat3x3<f32>', stride: 48 },
+ { type: 'mat3x4<f32>', stride: 48 },
+ { type: 'mat4x2<f32>', stride: 32 },
+ { type: 'mat4x3<f32>', stride: 64 },
+ { type: 'mat4x4<f32>', stride: 64 },
+ { type: 'mat2x2<f16>', stride: 8 },
+ { type: 'mat2x3<f16>', stride: 16 },
+ { type: 'mat2x4<f16>', stride: 16 },
+ { type: 'mat3x2<f16>', stride: 12 },
+ { type: 'mat3x3<f16>', stride: 24 },
+ { type: 'mat3x4<f16>', stride: 24 },
+ { type: 'mat4x2<f16>', stride: 16 },
+ { type: 'mat4x3<f16>', stride: 32 },
+ { type: 'mat4x4<f16>', stride: 32 },
+ { type: 'atomic<u32>', stride: 4 },
+ { type: 'atomic<i32>', stride: 4 },
+ { type: 'array<u32,4>', stride: 16 },
+ { type: 'array<i32,4>', stride: 16 },
+ { type: 'array<f32,4>', stride: 16 },
+ { type: 'array<f16,4>', stride: 8 },
+ // Structures - see declarations below.
+ { type: 'ElemStruct', stride: 4 },
+ { type: 'ElemStruct_ImplicitPadding', stride: 16 },
+ { type: 'ElemStruct_ExplicitPadding', stride: 32 },
+] as const;
+
+// Declarations for structures used as array element types.
+const kWgslStructures = `
+struct ElemStruct { a : u32 }
+struct ElemStruct_ImplicitPadding { a : vec3<u32> }
+struct ElemStruct_ExplicitPadding { @align(32) a : u32 }
+`;
+
+/**
+ * Run a shader and check that the array length is correct.
+ *
+ * @param t The test object
+ * @param wgsl The shader source
+ * @param stride The stride in bytes of the array element type
+ * @param offset The offset in bytes of the array from the start of the binding
+ * @param buffer_size The size in bytes of the buffer to allocate
+ * @param binding_size The size in bytes of the binding
+ * @param binding_offset The offset in bytes of the binding
+ * @param expected The array of expected values after running the shader
+ */
+function runShaderTest(
+ t: GPUTest,
+ wgsl: string,
+ stride: number,
+ offset: number,
+ buffer_size: number,
+ binding_size: number,
+ binding_offset: number
+): void {
+ // Create the compute pipeline.
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ // Create the buffer that will contain the runtime-sized array.
+ const buffer = t.device.createBuffer({
+ size: buffer_size,
+ usage: GPUBufferUsage.STORAGE,
+ });
+
+ // Create the buffer that will receive the array length.
+ const lengthBuffer = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+
+ // Set up bindings.
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer, size: binding_size, offset: binding_offset } },
+ { binding: 1, resource: { buffer: lengthBuffer } },
+ ],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Check the length.
+ const length = (binding_size - offset) / stride;
+ t.expectGPUBufferValuesEqual(lengthBuffer, new Uint32Array([length]));
+}
+
+/**
+ * Test if a WGSL type string require using f16 extension.
+ *
+ * @param test_type The wgsl type for testing
+ */
+function typeRequiresF16(test_type: string): boolean {
+ return test_type.includes('f16');
+}
+
+/**
+ * Generate the necessary wgsl header for tested type, especially for f16
+ *
+ * @param test_type The wgsl type for testing
+ */
+function shaderHeader(test_type: string): string {
+ return typeRequiresF16(test_type) ? 'enable f16;\n\n' : '';
+}
+
+g.test('single_element')
+ .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin')
+ .desc(
+ `Test the arrayLength() builtin with a binding that is just large enough for a single element.
+
+ Test parameters:
+ - type: The WGSL type to use as the array element type.
+ - stride: The stride in bytes of the array element type.
+ `
+ )
+ .params(u => u.combineWithParams(kTestTypes))
+ .beforeAllSubcases(t => {
+ if (typeRequiresF16(t.params.type)) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const wgsl =
+ shaderHeader(t.params.type) +
+ kWgslStructures +
+ `
+ @group(0) @binding(0) var<storage, read_write> buffer : array<${t.params.type}>;
+ @group(0) @binding(1) var<storage, read_write> length : u32;
+ @compute @workgroup_size(1)
+ fn main() {
+ length = arrayLength(&buffer);
+ }
+ `;
+ let buffer_size: number = t.params.stride;
+ // Ensure that binding size is multiple of 4.
+ buffer_size = buffer_size + ((~buffer_size + 1) & 3);
+ runShaderTest(t, wgsl, t.params.stride, 0, buffer_size, buffer_size, 0);
+ });
+
+g.test('multiple_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin')
+ .desc(
+ `Test the arrayLength() builtin with a binding that is large enough for multiple elements.
+
+ We test sizes that are not exact multiples of the array element strides, to test that the
+ length is correctly floored to the next whole element.
+
+ Test parameters:
+ - buffer_size: The size in bytes of the buffer.
+ - type: The WGSL type to use as the array element type.
+ - stride: The stride in bytes of the array element type.
+ `
+ )
+ .params(u =>
+ u.combine('buffer_size', [640, 1004, 1048576] as const).combineWithParams(kTestTypes)
+ )
+ .beforeAllSubcases(t => {
+ if (typeRequiresF16(t.params.type)) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const wgsl =
+ shaderHeader(t.params.type) +
+ kWgslStructures +
+ `
+ @group(0) @binding(0) var<storage, read_write> buffer : array<${t.params.type}>;
+ @group(0) @binding(1) var<storage, read_write> length : u32;
+ @compute @workgroup_size(1)
+ fn main() {
+ length = arrayLength(&buffer);
+ }
+ `;
+ runShaderTest(t, wgsl, t.params.stride, 0, t.params.buffer_size, t.params.buffer_size, 0);
+ });
+
+g.test('struct_member')
+ .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin')
+ .desc(
+ `Test the arrayLength() builtin with an array that is inside a structure.
+
+ We include offsets that are not exact multiples of the array element strides, to test that
+ the length is correctly floored to the next whole element.
+
+ Test parameters:
+ - member_offset: The offset (in bytes) of the array member from the start of the struct.
+ - type: The WGSL type to use as the array element type.
+ - stride: The stride in bytes of the array element type.
+ `
+ )
+ .params(u => u.combine('member_offset', [0, 4, 20] as const).combineWithParams(kTestTypes))
+ .beforeAllSubcases(t => {
+ if (typeRequiresF16(t.params.type)) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const member_offset = align(t.params.member_offset, t.params.stride);
+ const wgsl =
+ shaderHeader(t.params.type) +
+ kWgslStructures +
+ `
+ alias ArrayType = array<${t.params.type}>;
+ struct Struct {
+ ${t.params.member_offset > 0 ? `@size(${member_offset}) padding : u32,` : ``}
+ arr : ArrayType,
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : Struct;
+ @group(0) @binding(1) var<storage, read_write> length : u32;
+ @compute @workgroup_size(1)
+ fn main() {
+ length = arrayLength(&buffer.arr);
+ }
+ `;
+ const buffer_size = 1048576;
+ runShaderTest(t, wgsl, t.params.stride, member_offset, buffer_size, buffer_size, 0);
+ });
+
+g.test('binding_subregion')
+ .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin')
+ .desc(
+ `Test the arrayLength() builtin when used with a binding that starts at a non-zero offset and
+ does not fill the entire buffer.
+ `
+ )
+ .fn(t => {
+ const wgsl = `
+ @group(0) @binding(0) var<storage, read_write> buffer : array<vec3<f32>>;
+ @group(0) @binding(1) var<storage, read_write> length : u32;
+ @compute @workgroup_size(1)
+ fn main() {
+ length = arrayLength(&buffer);
+ }
+ `;
+ const stride = 16;
+ const buffer_size = 1024;
+ const binding_size = 640;
+ const binding_offset = 256;
+ runShaderTest(t, wgsl, stride, 0, buffer_size, binding_size, binding_offset);
+ });
+
+g.test('read_only')
+ .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin')
+ .desc(
+ `Test the arrayLength() builtin when used with a read-only storage buffer.
+ `
+ )
+ .fn(t => {
+ const wgsl = `
+ @group(0) @binding(0) var<storage, read> buffer : array<vec3<f32>>;
+ @group(0) @binding(1) var<storage, read_write> length : u32;
+ @compute @workgroup_size(1)
+ fn main() {
+ length = arrayLength(&buffer);
+ }
+ `;
+ const stride = 16;
+ const buffer_size = 1024;
+ runShaderTest(t, wgsl, stride, 0, buffer_size, buffer_size, 0);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts
new file mode 100644
index 0000000000..8d18ebb303
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts
@@ -0,0 +1,78 @@
+export const description = `
+Execution tests for the 'asin' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn asin(e: T ) -> T
+Returns the arc sine of e. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const f32_inputs = [
+ ...linearRange(-1, 1, 100), // asin is defined on [-1, 1]
+ ...fullF32Range(),
+];
+
+const f16_inputs = [
+ ...linearRange(-1, 1, 100), // asin is defined on [-1, 1]
+ ...fullF16Range(),
+];
+
+export const d = makeCaseCache('asin', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.asinInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.asinInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.asinInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.asinInterval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('asin'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('asin'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts
new file mode 100644
index 0000000000..9a8384e090
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts
@@ -0,0 +1,65 @@
+export const description = `
+Execution tests for the 'sinh' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn asinh(e: T ) -> T
+Returns the hyperbolic arc sine of e.
+Computes the functional inverse of sinh.
+Component-wise when T is a vector.
+
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('asinh', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.asinhInterval);
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.asinhInterval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float test`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('asinh'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('asinh'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts
new file mode 100644
index 0000000000..3d0d3e6725
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts
@@ -0,0 +1,80 @@
+export const description = `
+Execution tests for the 'atan' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn atan(e: T ) -> T
+Returns the arc tangent of e. Component-wise when T is a vector.
+
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const known_values = [-Math.sqrt(3), -1, -1 / Math.sqrt(3), 0, 1, 1 / Math.sqrt(3), Math.sqrt(3)];
+
+const f32_inputs = [...known_values, ...fullF32Range()];
+const f16_inputs = [...known_values, ...fullF16Range()];
+
+export const d = makeCaseCache('atan', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.atanInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.atanInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.atanInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.atanInterval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+f32 tests
+
+TODO(#792): Decide what the ground-truth is for these tests. [1]
+`
+ )
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('atan'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('atan'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts
new file mode 100644
index 0000000000..fbace73dd2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts
@@ -0,0 +1,83 @@
+export const description = `
+Execution tests for the 'atan2' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn atan2(e1: T ,e2: T ) -> T
+Returns the arc tangent of e1 over e2. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange, sparseF32Range, sparseF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const cases = (['f32', 'f16'] as const)
+ .flatMap(kind =>
+ ([true, false] as const).map(nonConst => ({
+ [`${kind}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ const fp = FP[kind];
+ // Using sparse range since there are N^2 cases being generated, and also including extra values
+ // around 0, where there is a discontinuity that implementations may behave badly at.
+ const numeric_range = [
+ ...(kind === 'f32' ? sparseF32Range() : sparseF16Range()),
+ ...linearRange(fp.constants().negative.max, fp.constants().positive.min, 10),
+ ];
+ return fp.generateScalarPairToIntervalCases(
+ numeric_range,
+ numeric_range,
+ nonConst ? 'unfiltered' : 'finite',
+ fp.atan2Interval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('atan2', cases);
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+f32 tests
+
+TODO(#792): Decide what the ground-truth is for these tests. [1]
+`
+ )
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(`f32_${t.params.inputSource === 'const' ? 'const' : 'non_const'}`);
+ await run(t, builtin('atan2'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(`f16_${t.params.inputSource === 'const' ? 'const' : 'non_const'}`);
+ await run(t, builtin('atan2'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts
new file mode 100644
index 0000000000..90f322a7ea
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts
@@ -0,0 +1,87 @@
+export const description = `
+Execution tests for the 'atanh' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn atanh(e: T ) -> T
+Returns the hyperbolic arc tangent of e. The result is 0 when abs(e) ≥ 1.
+Computes the functional inverse of tanh.
+Component-wise when T is a vector.
+Note: The result is not mathematically meaningful when abs(e) >= 1.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const f32_inputs = [
+ ...biasedRange(kValue.f32.negative.less_than_one, -0.9, 20), // discontinuity at x = -1
+ -1,
+ ...biasedRange(kValue.f32.positive.less_than_one, 0.9, 20), // discontinuity at x = 1
+ 1,
+ ...fullF32Range(),
+];
+const f16_inputs = [
+ ...biasedRange(kValue.f16.negative.less_than_one, -0.9, 20), // discontinuity at x = -1
+ -1,
+ ...biasedRange(kValue.f16.positive.less_than_one, 0.9, 20), // discontinuity at x = 1
+ 1,
+ ...fullF16Range(),
+];
+
+export const d = makeCaseCache('atanh', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.atanhInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.atanhInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.atanhInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.atanhInterval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('atanh'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('atanh'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts
new file mode 100644
index 0000000000..37d3ce5292
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts
@@ -0,0 +1,101 @@
+export const description = `
+Atomically read, add and store value.
+
+ * Load the original value pointed to by atomic_ptr.
+ * Obtains a new value by adding with the value v.
+ * Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;
+
+import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ typedArrayCtor,
+} from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('add_storage')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ // Allocate one extra element to ensure it doesn't get modified
+ const bufferNumElements = 2;
+
+ const initValue = 0;
+ const op = `atomicAdd(&output[0], 1)`;
+ const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ expected[0] = numInvocations;
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected,
+ });
+ });
+
+g.test('add_workgroup')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ // Allocate one extra element to ensure it doesn't get modified
+ const wgNumElements = 2;
+
+ const initValue = 0;
+ const op = `atomicAdd(&wg[0], 1)`;
+
+ const expected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * t.params.dispatchSize
+ );
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ const wg = expected.subarray(d * wgNumElements);
+ wg[0] = t.params.workgroupSize;
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected,
+ });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts
new file mode 100644
index 0000000000..ed5cfa84a3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts
@@ -0,0 +1,135 @@
+export const description = `
+Atomically read, and and store value.
+
+* Load the original value pointed to by atomic_ptr.
+* Obtains a new value by anding with the value v.
+* Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;
+
+import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ kMapId,
+ typedArrayCtor,
+} from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('and_storage')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+
+ // Allocate an output buffer with bitsize of max invocations plus 1 for validation
+ const bufferNumElements = Math.max(1, numInvocations / 32) + 1;
+
+ // Start with all bits high, then using atomicAnd to set mapped global id bit off.
+ // Note: Both WGSL and JS will shift left 1 by id modulo 32.
+ const initValue = 0xffffffff;
+
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations); // Defines map_id()
+ const op = `
+ let i = map_id(u32(id));
+ atomicAnd(&output[i / 32], ~(${scalarType}(1) << i))
+ `;
+
+ const expected = new (typedArrayCtor(scalarType))(bufferNumElements).fill(initValue);
+ for (let id = 0; id < numInvocations; ++id) {
+ const i = mapId.f(id, numInvocations);
+ expected[Math.floor(i / 32)] &= ~(1 << i);
+ }
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected,
+ extra,
+ });
+ });
+
+g.test('and_workgroup')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize;
+
+ // Allocate workgroup array with bitsize of max invocations plus 1 for validation
+ const wgNumElements = Math.max(1, numInvocations / 32) + 1;
+
+ // Start with all bits high, then using atomicAnd to set mapped global id bit off.
+ // Note: Both WGSL and JS will shift left 1 by id modulo 32.
+ const initValue = 0xffffffff;
+
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations); // Defines map_id()
+ const op = `
+ let i = map_id(u32(id));
+ atomicAnd(&wg[i / 32], ~(${scalarType}(1) << i))
+ `;
+
+ const expected = new (typedArrayCtor(scalarType))(wgNumElements * t.params.dispatchSize).fill(
+ initValue
+ );
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ for (let id = 0; id < numInvocations; ++id) {
+ const wg = expected.subarray(d * wgNumElements);
+ const i = mapId.f(id, numInvocations);
+ wg[Math.floor(i / 32)] &= ~(1 << i);
+ }
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected,
+ extra,
+ });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts
new file mode 100644
index 0000000000..2556bb744b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts
@@ -0,0 +1,742 @@
+export const description = `
+Performs the following steps atomically:
+ * Load the original value pointed to by atomic_ptr.
+ * Compare the original value to the value v using an equality operation.
+ * Store the value v only if the result of the equality comparison was true.
+
+Returns a two member structure, where the first member, old_value, is the original
+value of the atomic object and the second member, exchanged, is whether or not
+the comparison succeeded.
+
+Note: the equality comparison may spuriously fail on some implementations.
+That is, the second component of the result vector may be false even if the first
+component of the result vector equals cmp.
+`;
+
+import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { assert } from '../../../../../../../common/util/util.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ typedArrayCtor,
+ kMapId,
+ onlyWorkgroupSizes,
+} from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('compare_exchange_weak_storage_basic')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T>
+
+struct __atomic_compare_exchange_result<T> {
+ old_value : T, // old value stored in the atomic
+ exchanged : bool, // true if the exchange was done
+}
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(async t => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ const bufferNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ @group(0) @binding(0)
+ var<storage, read_write> input : array<atomic<${scalarType}>>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> output : array<${scalarType}>;
+
+ @group(0) @binding(2)
+ var<storage, read_write> exchanged : array<${scalarType}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+
+ // Exchange every third value
+ var comp = id + 1;
+ if (id % 3 == 0) {
+ comp = id;
+ }
+ let r = atomicCompareExchangeWeak(&input[id], comp, map_id(id * 2));
+
+ // Store results
+ output[id] = r.old_value;
+ if (r.exchanged) {
+ exchanged[id] = 1;
+ } else {
+ exchanged[id] = 0;
+ }
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ // Create input buffer with values [0..n]
+ const inputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true,
+ });
+ t.trackForCleanup(inputBuffer);
+ const data = new arrayType(inputBuffer.getMappedRange());
+ data.forEach((_, i) => (data[i] = i));
+ inputBuffer.unmap();
+
+ const outputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const exchangedBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(exchangedBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } },
+ { binding: 2, resource: { buffer: exchangedBuffer } },
+ ],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(t.params.dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Output buffer should be the same as the initial input buffer as it contains
+ // values returned from atomicCompareExchangeWeak
+ const outputExpected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ outputExpected.forEach((_, i) => (outputExpected[i] = i));
+ t.expectGPUBufferValuesEqual(outputBuffer, outputExpected);
+
+ // Read back exchanged buffer
+ const exchangedBufferResult = await t.readGPUBufferRangeTyped(exchangedBuffer, {
+ type: arrayType,
+ typedLength: exchangedBuffer.size / arrayType.BYTES_PER_ELEMENT,
+ });
+
+ // The input buffer should have been modified to a computed value for every third value,
+ // unless the comparison spuriously failed.
+ const inputExpected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ inputExpected.forEach((_, i) => {
+ if (i % 3 === 0 && exchangedBufferResult.data[i]) {
+ inputExpected[i] = mapId.f(i * 2, numInvocations);
+ } else {
+ inputExpected[i] = i; // No change
+ }
+ });
+ t.expectGPUBufferValuesEqual(inputBuffer, inputExpected);
+ });
+
+g.test('compare_exchange_weak_workgroup_basic')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T>
+
+struct __atomic_compare_exchange_result<T> {
+ old_value : T, // old value stored in the atomic
+ exchanged : bool, // true if the exchange was done
+}
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(async t => {
+ const numInvocations = t.params.workgroupSize;
+ const wgNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const dispatchSize = t.params.dispatchSize;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ var<workgroup> wg: array<atomic<${scalarType}>, ${wgNumElements}>;
+
+ @group(0) @binding(0)
+ var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> exchanged: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ // Result of each workgroup is written to output[workgroup_id.x]
+ @group(0) @binding(2)
+ var<storage, read_write> wg_copy: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+ let global_id = ${scalarType}(workgroup_id.x * ${wgNumElements} + local_invocation_index);
+
+ // Initialize wg[id] with this invocations global id
+ atomicStore(&wg[id], global_id);
+
+ // Exchange every third value
+ var comp = global_id + 1;
+ if (global_id % 3 == 0) {
+ comp = global_id;
+ }
+ let r = atomicCompareExchangeWeak(&wg[id], comp, map_id(global_id * 2));
+
+ // Store results
+ output[global_id] = r.old_value;
+ if (r.exchanged) {
+ exchanged[global_id] = 1;
+ } else {
+ exchanged[global_id] = 0;
+ }
+
+ // Copy new value into wg_copy
+ wg_copy[global_id] = atomicLoad(&wg[id]);
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ const outputBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const wgCopyBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const exchangedBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(exchangedBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: outputBuffer } },
+ { binding: 1, resource: { buffer: exchangedBuffer } },
+ { binding: 2, resource: { buffer: wgCopyBuffer } },
+ ],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Output buffer should be the same as the initial wg buffer as it contains
+ // values returned from atomicCompareExchangeWeak
+ const outputExpected = new (typedArrayCtor(t.params.scalarType))(wgNumElements * dispatchSize);
+ outputExpected.forEach((_, i) => (outputExpected[i] = i));
+ t.expectGPUBufferValuesEqual(outputBuffer, outputExpected);
+
+ // Read back exchanged buffer
+ const exchangedBufferResult = await t.readGPUBufferRangeTyped(exchangedBuffer, {
+ type: arrayType,
+ typedLength: exchangedBuffer.size / arrayType.BYTES_PER_ELEMENT,
+ });
+
+ // And the wg copy buffer should have been modified to a computed value for every third value,
+ // unless the comparison spuriously failed.
+ const wgCopyBufferExpected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * dispatchSize
+ );
+ wgCopyBufferExpected.forEach((_, i) => {
+ if (i % 3 === 0 && exchangedBufferResult.data[i]) {
+ wgCopyBufferExpected[i] = mapId.f(i * 2, numInvocations);
+ } else {
+ wgCopyBufferExpected[i] = i; // No change
+ }
+ });
+ t.expectGPUBufferValuesEqual(wgCopyBuffer, wgCopyBufferExpected);
+ });
+
+g.test('compare_exchange_weak_storage_advanced')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T>
+
+struct __atomic_compare_exchange_result<T> {
+ old_value : T, // old value stored in the atomic
+ exchanged : bool, // true if the exchange was done
+}
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', onlyWorkgroupSizes) //
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(async t => {
+ const numInvocations = t.params.workgroupSize;
+ const scalarType = t.params.scalarType;
+
+ // Number of times each workgroup attempts to exchange the same value to the same memory address
+ const numWrites = 4;
+
+ const bufferNumElements = numInvocations * numWrites;
+ const pingPongValues = [24, 68];
+
+ const wgsl = `
+ @group(0) @binding(0)
+ var<storage, read_write> data : atomic<${scalarType}>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> old_values : array<${scalarType}>;
+
+ @group(0) @binding(2)
+ var<storage, read_write> exchanged : array<${scalarType}>;
+
+ fn ping_pong_value(i: u32) -> ${scalarType} {
+ if (i % 2 == 0) {
+ return ${pingPongValues[0]};
+ } else {
+ return ${pingPongValues[1]};
+ }
+ }
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+
+ // Each invocation attempts to write an alternating (ping-pong) value, once per loop.
+ // The data value is initialized with the first of the two ping-pong values.
+ // Only one invocation per loop iteration should succeed. Note the workgroupBarrier() used
+ // to synchronize each invocation in the loop.
+ // The reason we alternate is in case atomicCompareExchangeWeak spurioulsy fails:
+ // If all invocations of one iteration spuriously fail, the very next iteration will also
+ // fail since the value will not have been exchanged; however, the subsequent one will succeed
+ // (assuming not all iterations spuriously fail yet again).
+
+ for (var i = 0u; i < ${numWrites}u; i++) {
+ let compare = ping_pong_value(i);
+ let next = ping_pong_value(i + 1);
+
+ let r = atomicCompareExchangeWeak(&data, compare, next);
+
+ let slot = i * ${numInvocations}u + u32(id);
+ old_values[slot] = r.old_value;
+ if (r.exchanged) {
+ exchanged[slot] = 1;
+ } else {
+ exchanged[slot] = 0;
+ }
+
+ workgroupBarrier();
+ }
+ }
+ `;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+ const defaultValue = 99999999;
+
+ // Create single-value data buffer initialized to the first ping-pong value
+ const dataBuffer = t.device.createBuffer({
+ size: 1 * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true,
+ });
+ {
+ const data = new arrayType(dataBuffer.getMappedRange());
+ data[0] = pingPongValues[0];
+ dataBuffer.unmap();
+ }
+ t.trackForCleanup(dataBuffer);
+
+ const oldValuesBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true,
+ });
+ t.trackForCleanup(oldValuesBuffer);
+ {
+ const data = new arrayType(oldValuesBuffer.getMappedRange());
+ data.fill(defaultValue);
+ oldValuesBuffer.unmap();
+ }
+
+ const exchangedBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true,
+ });
+ t.trackForCleanup(exchangedBuffer);
+ {
+ const data = new arrayType(exchangedBuffer.getMappedRange());
+ data.fill(defaultValue);
+ exchangedBuffer.unmap();
+ }
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: dataBuffer } },
+ { binding: 1, resource: { buffer: oldValuesBuffer } },
+ { binding: 2, resource: { buffer: exchangedBuffer } },
+ ],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Read back buffers
+ const oldValuesBufferResult = (
+ await t.readGPUBufferRangeTyped(oldValuesBuffer, {
+ type: arrayType,
+ typedLength: oldValuesBuffer.size / arrayType.BYTES_PER_ELEMENT,
+ })
+ ).data;
+ const exchangedBufferResult = (
+ await t.readGPUBufferRangeTyped(exchangedBuffer, {
+ type: arrayType,
+ typedLength: exchangedBuffer.size / arrayType.BYTES_PER_ELEMENT,
+ })
+ ).data;
+
+ for (let w = 0; w < numWrites; ++w) {
+ const offset = w * numInvocations;
+ const exchanged = exchangedBufferResult.subarray(offset, offset + numInvocations);
+ const oldValues = oldValuesBufferResult.subarray(offset, offset + numInvocations);
+
+ const dumpValues = () => {
+ return `
+ For write: ${w}
+ exchanged: ${exchanged}
+ oldValues: ${oldValues}`;
+ };
+
+ // Only one of the invocations should have succeeded to exchange - or none if spurious failures occured
+ const noExchanges = exchanged.every(v => v === 0);
+ if (noExchanges) {
+ // Spurious failure, all values in oldValues should be the default value
+ if (!oldValues.every(v => v === defaultValue)) {
+ t.fail(
+ `Spurious failure detected, expected only default value of ${defaultValue} in oldValues buffer.${dumpValues()}`
+ );
+ return;
+ }
+ } else {
+ // Only one invocation should have exchanged its value
+ if (exchanged.filter(v => v === 1).length !== 1) {
+ t.fail(`More than one invocation exchanged its value.${dumpValues()}`);
+ return;
+ }
+
+ // Get its index
+ const idx = exchanged.findIndex(v => v === 1);
+ assert(idx !== -1);
+
+ // Its output should contain the old value after exchange
+ const oldValue = pingPongValues[w % 2];
+ if (oldValues[idx] !== oldValue) {
+ t.fail(
+ `oldValues[${idx}] expected to contain old value from exchange: ${oldValue}.${dumpValues()}'`
+ );
+ return;
+ }
+
+ // The rest of oldValues should either contain the old value or the newly exchanged value,
+ // depending on whether they executed atomicCompareExchangWeak before or after invocation 'idx'.
+ const oldValuesRest = oldValues.filter((_, i) => i !== idx);
+ if (!oldValuesRest.every(v => pingPongValues.includes(v))) {
+ t.fail(
+ `Values in oldValues buffer should be one of '${pingPongValues}', except at index '${idx} where it is '${oldValue}'.${dumpValues()}`
+ );
+ return;
+ }
+ }
+ }
+ });
+
+g.test('compare_exchange_weak_workgroup_advanced')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T>
+
+struct __atomic_compare_exchange_result<T> {
+ old_value : T, // old value stored in the atomic
+ exchanged : bool, // true if the exchange was done
+}
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', onlyWorkgroupSizes) //
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(async t => {
+ const numInvocations = t.params.workgroupSize;
+ const scalarType = t.params.scalarType;
+
+ // Number of times each workgroup attempts to exchange the same value to the same memory address
+ const numWrites = 4;
+
+ const bufferNumElements = numInvocations * numWrites;
+ const pingPongValues = [24, 68];
+
+ const wgsl = `
+ var<workgroup> wg: atomic<${scalarType}>;
+
+ @group(0) @binding(0)
+ var<storage, read_write> old_values : array<${scalarType}>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> exchanged : array<${scalarType}>;
+
+ fn ping_pong_value(i: u32) -> ${scalarType} {
+ if (i % 2 == 0) {
+ return ${pingPongValues[0]};
+ } else {
+ return ${pingPongValues[1]};
+ }
+ }
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+
+ // Each invocation attempts to write an alternating (ping-pong) value, once per loop.
+ // The input value is initialized with the first of the two ping-pong values.
+ // Only one invocation per loop iteration should succeed. Note the workgroupBarrier() used
+ // to synchronize each invocation in the loop.
+ // The reason we alternate is in case atomicCompareExchangeWeak spurioulsy fails:
+ // If all invocations of one iteration spuriously fail, the very next iteration will also
+ // fail since the value will not have been exchanged; however, the subsequent one will succeed
+ // (assuming not all iterations spuriously fail yet again).
+
+ // Initialize wg
+ if (local_invocation_index == 0) {
+ atomicStore(&wg, ${pingPongValues[0]});
+ }
+ workgroupBarrier();
+
+ for (var i = 0u; i < ${numWrites}u; i++) {
+ let compare = ping_pong_value(i);
+ let next = ping_pong_value(i + 1);
+
+ let r = atomicCompareExchangeWeak(&wg, compare, next);
+
+ let slot = i * ${numInvocations}u + u32(id);
+ old_values[slot] = r.old_value;
+ if (r.exchanged) {
+ exchanged[slot] = 1;
+ } else {
+ exchanged[slot] = 0;
+ }
+
+ workgroupBarrier();
+ }
+ }
+ `;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+ const defaultValue = 99999999;
+
+ const oldValuesBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true,
+ });
+ t.trackForCleanup(oldValuesBuffer);
+ {
+ const data = new arrayType(oldValuesBuffer.getMappedRange());
+ data.fill(defaultValue);
+ oldValuesBuffer.unmap();
+ }
+
+ const exchangedBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true,
+ });
+ t.trackForCleanup(exchangedBuffer);
+ {
+ const data = new arrayType(exchangedBuffer.getMappedRange());
+ data.fill(defaultValue);
+ exchangedBuffer.unmap();
+ }
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: oldValuesBuffer } },
+ { binding: 1, resource: { buffer: exchangedBuffer } },
+ ],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Read back buffers
+ const oldValuesBufferResult = (
+ await t.readGPUBufferRangeTyped(oldValuesBuffer, {
+ type: arrayType,
+ typedLength: oldValuesBuffer.size / arrayType.BYTES_PER_ELEMENT,
+ })
+ ).data;
+ const exchangedBufferResult = (
+ await t.readGPUBufferRangeTyped(exchangedBuffer, {
+ type: arrayType,
+ typedLength: exchangedBuffer.size / arrayType.BYTES_PER_ELEMENT,
+ })
+ ).data;
+
+ for (let w = 0; w < numWrites; ++w) {
+ const offset = w * numInvocations;
+ const exchanged = exchangedBufferResult.subarray(offset, offset + numInvocations);
+ const oldValues = oldValuesBufferResult.subarray(offset, offset + numInvocations);
+
+ const dumpValues = () => {
+ return `
+ For write: ${w}
+ exchanged: ${exchanged}
+ oldValues: ${oldValues}`;
+ };
+
+ // Only one of the invocations should have succeeded to exchange - or none if spurious failures occured
+ const noExchanges = exchanged.every(v => v === 0);
+ if (noExchanges) {
+ // Spurious failure, all values in oldValues should be the default value
+ if (!oldValues.every(v => v === defaultValue)) {
+ t.fail(
+ `Spurious failure detected, expected only default value of ${defaultValue} in oldValues buffer.${dumpValues()}`
+ );
+ return;
+ }
+ } else {
+ // Only one invocation should have exchanged its value
+ if (exchanged.filter(v => v === 1).length !== 1) {
+ t.fail(`More than one invocation exchanged its value.${dumpValues()}`);
+ return;
+ }
+
+ // Get its index
+ const idx = exchanged.findIndex(v => v === 1);
+ assert(idx !== -1);
+
+ // Its output should contain the old value after exchange
+ const oldValue = pingPongValues[w % 2];
+ if (oldValues[idx] !== oldValue) {
+ t.fail(
+ `oldValues[${idx}] expected to contain old value from exchange: ${oldValue}.${dumpValues()}'`
+ );
+ return;
+ }
+
+ // The rest of oldValues should either contain the old value or the newly exchanged value,
+ // depending on whether they executed atomicCompareExchangWeak before or after invocation 'idx'.
+ const oldValuesRest = oldValues.filter((_, i) => i !== idx);
+ if (!oldValuesRest.every(v => pingPongValues.includes(v))) {
+ t.fail(
+ `Values in oldValues buffer should be one of '${pingPongValues}', except at index '${idx} where it is '${oldValue}'.${dumpValues()}`
+ );
+ return;
+ }
+ }
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts
new file mode 100644
index 0000000000..540ac16b07
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts
@@ -0,0 +1,470 @@
+export const description = `
+Atomically stores the value v in the atomic object pointed to atomic_ptr and returns the original value stored in the atomic object.
+`;
+
+import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+import { checkElementsEqual } from '../../../../../../util/check_contents.js';
+
+import { dispatchSizes, workgroupSizes, typedArrayCtor, kMapId } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('exchange_storage_basic')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ const bufferNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ @group(0) @binding(0)
+ var<storage, read_write> input : array<atomic<${scalarType}>>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> output : array<${scalarType}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+
+ output[id] = atomicExchange(&input[id], map_id(id * 2));
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ // Create input buffer with values [0..n]
+ const inputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true,
+ });
+ t.trackForCleanup(inputBuffer);
+ const data = new arrayType(inputBuffer.getMappedRange());
+ data.forEach((_, i) => (data[i] = i));
+ inputBuffer.unmap();
+
+ const outputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } },
+ ],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(t.params.dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Output buffer should be the same as the initial input buffer as it contains
+ // values returned from atomicExchange
+ const outputExpected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ outputExpected.forEach((_, i) => (outputExpected[i] = i));
+ t.expectGPUBufferValuesEqual(outputBuffer, outputExpected);
+
+ // And the input buffer should have been modified to a computed value
+ const inputExpected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ inputExpected.forEach((_, i) => (inputExpected[i] = mapId.f(i * 2, numInvocations)));
+ t.expectGPUBufferValuesEqual(inputBuffer, inputExpected);
+ });
+
+g.test('exchange_workgroup_basic')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-load')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
+
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize;
+ const wgNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const dispatchSize = t.params.dispatchSize;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ var<workgroup> wg: array<atomic<${scalarType}>, ${wgNumElements}>;
+
+ // Result of each workgroup is written to output[workgroup_id.x]
+ @group(0) @binding(0)
+ var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> wg_copy: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+ let global_id = ${scalarType}(workgroup_id.x * ${wgNumElements} + local_invocation_index);
+
+ // Initialize wg[id] with this invocations global id
+ atomicStore(&wg[id], global_id);
+ workgroupBarrier();
+
+ // Test atomicExchange, storing old value into output
+ output[global_id] = atomicExchange(&wg[id], map_id(global_id * 2));
+
+ // Copy new value into wg_copy
+ wg_copy[global_id] = atomicLoad(&wg[id]);
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ const outputBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const wgCopyBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: outputBuffer } },
+ { binding: 1, resource: { buffer: wgCopyBuffer } },
+ ],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Output buffer should be the same as the initial wg buffer as it contains
+ // values returned from atomicExchange
+ const outputExpected = new (typedArrayCtor(t.params.scalarType))(wgNumElements * dispatchSize);
+ outputExpected.forEach((_, i) => (outputExpected[i] = i));
+ t.expectGPUBufferValuesEqual(outputBuffer, outputExpected);
+
+ // And the wg copy buffer should have been modified to a computed value
+ const wgCopyBufferExpected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * dispatchSize
+ );
+ wgCopyBufferExpected.forEach(
+ (_, i) => (wgCopyBufferExpected[i] = mapId.f(i * 2, numInvocations))
+ );
+ t.expectGPUBufferValuesEqual(wgCopyBuffer, wgCopyBufferExpected);
+ });
+
+g.test('exchange_storage_advanced')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(async t => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ const bufferNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ @group(0) @binding(0)
+ var<storage, read_write> input : atomic<${scalarType}>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> output : array<${scalarType}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+
+ // All invocations exchange with same single memory address, and we store
+ // the old value at the current invocation's location in the output buffer.
+ output[id] = atomicExchange(&input, map_id(id));
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ // Create input buffer of size 1 with initial value 0
+ const inputBuffer = t.device.createBuffer({
+ size: 1 * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(inputBuffer);
+
+ const outputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } },
+ ],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(t.params.dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Read back buffers
+ const inputBufferResult = await t.readGPUBufferRangeTyped(inputBuffer, {
+ type: arrayType,
+ typedLength: inputBuffer.size / arrayType.BYTES_PER_ELEMENT,
+ });
+ const outputBufferResult = await t.readGPUBufferRangeTyped(outputBuffer, {
+ type: arrayType,
+ typedLength: outputBuffer.size / arrayType.BYTES_PER_ELEMENT,
+ });
+
+ // The one value in the input buffer plus all values in the output buffer
+ // should contain initial value 0 plus map_id(0..n), unsorted.
+ const values = new arrayType([...inputBufferResult.data, ...outputBufferResult.data]);
+
+ const expected = new arrayType(values.length);
+ expected.forEach((_, i) => {
+ if (i === 0) {
+ expected[0] = 0;
+ } else {
+ expected[i] = mapId.f(i - 1, numInvocations);
+ }
+ });
+
+ // Sort both arrays and compare
+ values.sort();
+ expected.sort(); // Sort because we store hashed results when mapId == 'remap'
+ t.expectOK(checkElementsEqual(values, expected));
+ });
+
+g.test('exchange_workgroup_advanced')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-load')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
+
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(async t => {
+ const numInvocations = t.params.workgroupSize;
+ const scalarType = t.params.scalarType;
+ const dispatchSize = t.params.dispatchSize;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ var<workgroup> wg: atomic<${scalarType}>;
+
+ // Will contain the atomicExchange result for each invocation at global index
+ @group(0) @binding(0)
+ var<storage, read_write> output: array<${scalarType}, ${numInvocations * dispatchSize}>;
+
+ // Will contain the final value in wg in wg_copy for this dispatch
+ @group(0) @binding(1)
+ var<storage, read_write> wg_copy: array<${scalarType}, ${dispatchSize}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+ let global_id = ${scalarType}(workgroup_id.x * ${numInvocations} + local_invocation_index);
+
+ // All invocations exchange with same single memory address, and we store
+ // the old value at the current invocation's location in the output buffer.
+ output[global_id] = atomicExchange(&wg, map_id(id));
+
+ // Once all invocations have completed, the first one copies the final exchanged value
+ // to wg_copy for this dispatch (workgroup_id.x)
+ workgroupBarrier();
+ if (local_invocation_index == 0u) {
+ wg_copy[workgroup_id.x] = atomicLoad(&wg);
+ }
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ const outputBuffer = t.device.createBuffer({
+ size: numInvocations * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const wgCopyBuffer = t.device.createBuffer({
+ size: dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: outputBuffer } },
+ { binding: 1, resource: { buffer: wgCopyBuffer } },
+ ],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Read back buffers
+ const outputBufferResult = await t.readGPUBufferRangeTyped(outputBuffer, {
+ type: arrayType,
+ typedLength: outputBuffer.size / arrayType.BYTES_PER_ELEMENT,
+ });
+ const wgCopyBufferResult = await t.readGPUBufferRangeTyped(wgCopyBuffer, {
+ type: arrayType,
+ typedLength: wgCopyBuffer.size / arrayType.BYTES_PER_ELEMENT,
+ });
+
+ // For each dispatch, the one value in wgCopyBuffer plus all values in the output buffer
+ // should contain initial value 0 plus map_id(0..n), unsorted.
+
+ // Expected values for each dispatch
+ const expected = new arrayType(numInvocations + 1);
+ expected.forEach((_, i) => {
+ if (i === 0) {
+ expected[0] = 0;
+ } else {
+ expected[i] = mapId.f(i - 1, numInvocations);
+ }
+ });
+ expected.sort(); // Sort because we store hashed results when mapId == 'remap'
+
+ // Test values for each dispatch
+ for (let d = 0; d < dispatchSize; ++d) {
+ // Get values for this dispatch
+ const dispatchOffset = d * numInvocations;
+ const values = new arrayType([
+ wgCopyBufferResult.data[d], // Last 'wg' value for this dispatch
+ ...outputBufferResult.data.subarray(dispatchOffset, dispatchOffset + numInvocations), // Rest of the returned values
+ ]);
+
+ values.sort();
+ t.expectOK(checkElementsEqual(values, expected));
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts
new file mode 100644
index 0000000000..2aac7bb9b9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts
@@ -0,0 +1,192 @@
+export const description = `
+Returns the atomically loaded the value pointed to by atomic_ptr. It does not modify the object.
+`;
+
+import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import { dispatchSizes, workgroupSizes, typedArrayCtor, kMapId } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('load_storage')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-load')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
+
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ const bufferNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+
+ const wgsl = `
+ @group(0) @binding(0)
+ var<storage, read_write> input : array<atomic<${scalarType}>>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> output : array<${scalarType}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+ output[id] = atomicLoad(&input[id]);
+ }
+ `;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ // Create input buffer with values [map_id(0)..map_id(n)]
+ const inputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true,
+ });
+ t.trackForCleanup(inputBuffer);
+ const data = new arrayType(inputBuffer.getMappedRange());
+ data.forEach((_, i) => (data[i] = mapId.f(i, numInvocations)));
+ inputBuffer.unmap();
+
+ const outputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } },
+ ],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(t.params.dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Both input and output buffer should be the same now
+ const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ expected.forEach((_, i) => (expected[i] = mapId.f(i, numInvocations)));
+ t.expectGPUBufferValuesEqual(inputBuffer, expected);
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+ });
+
+g.test('load_workgroup')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-load')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
+
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize;
+ const wgNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const dispatchSize = t.params.dispatchSize;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ var<workgroup> wg: array<atomic<${scalarType}>, ${wgNumElements}>;
+
+ // Result of each workgroup is written to output[workgroup_id.x]
+ @group(0) @binding(0)
+ var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+ let global_id = ${scalarType}(workgroup_id.x * ${wgNumElements} + local_invocation_index);
+
+ // Initialize wg[id] with this invocations global id (mapped)
+ atomicStore(&wg[id], map_id(global_id));
+ workgroupBarrier();
+
+ // Test atomic loading of value at wg[id] and store result in output[global_id]
+ output[global_id] = atomicLoad(&wg[id]);
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ const outputBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Expected values should be map_id(0..n)
+ const expected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * t.params.dispatchSize
+ );
+ expected.forEach((_, i) => (expected[i] = mapId.f(i, numInvocations)));
+
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts
new file mode 100644
index 0000000000..066d673018
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts
@@ -0,0 +1,101 @@
+export const description = `
+Atomically read, max and store value.
+
+* Load the original value pointed to by atomic_ptr.
+* Obtains a new value by taking the max with the value v.
+* Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;
+
+import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ typedArrayCtor,
+} from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('max_storage')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ // Allocate one extra element to ensure it doesn't get modified
+ const bufferNumElements = 2;
+
+ const initValue = 0;
+ const op = `atomicMax(&output[0], id)`;
+ const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ expected[0] = numInvocations - 1;
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected,
+ });
+ });
+
+g.test('max_workgroup')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ // Allocate one extra element to ensure it doesn't get modified
+ const wgNumElements = 2;
+
+ const initValue = 0;
+ const op = `atomicMax(&wg[0], id)`;
+
+ const expected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * t.params.dispatchSize
+ ).fill(initValue);
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ const wg = expected.subarray(d * wgNumElements);
+ wg[0] = t.params.workgroupSize - 1;
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected,
+ });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts
new file mode 100644
index 0000000000..ad880c4182
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts
@@ -0,0 +1,100 @@
+export const description = `
+Atomically read, min and store value.
+
+* Load the original value pointed to by atomic_ptr.
+ * Obtains a new value by take the min with the value v.
+ * Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;
+
+import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ typedArrayCtor,
+} from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('min_storage')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ // Allocate one extra element to ensure it doesn't get modified
+ const bufferNumElements = 2;
+
+ const initValue = t.params.scalarType === 'u32' ? 0xffffffff : 0x7fffffff;
+ const op = `atomicMin(&output[0], id)`;
+ const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements).fill(initValue);
+ expected[0] = 0;
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected,
+ });
+ });
+
+g.test('min_workgroup')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ // Allocate one extra element to ensure it doesn't get modified
+ const wgNumElements = 2;
+
+ const initValue = t.params.scalarType === 'u32' ? 0xffffffff : 0x7fffffff;
+ const op = `atomicMin(&wg[0], id)`;
+
+ const expected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * t.params.dispatchSize
+ ).fill(initValue);
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ const wg = expected.subarray(d * wgNumElements);
+ wg[0] = 0;
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected,
+ });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts
new file mode 100644
index 0000000000..3892d41b38
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts
@@ -0,0 +1,131 @@
+export const description = `
+Atomically read, or and store value.
+
+* Load the original value pointed to by atomic_ptr.
+* Obtains a new value by or'ing with the value v.
+* Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;
+
+import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ kMapId,
+ typedArrayCtor,
+} from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('or_storage')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+
+ // Allocate an output buffer with bitsize of max invocations plus 1 for validation
+ const bufferNumElements = Math.max(1, numInvocations / 32) + 1;
+
+ // Start with all bits low, then using atomicOr to set mapped global id bit on.
+ // Note: Both WGSL and JS will shift left 1 by id modulo 32.
+ const initValue = 0;
+
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations); // Defines map_id()
+ const op = `
+ let i = map_id(u32(id));
+ atomicOr(&output[i / 32], ${scalarType}(1) << i)
+ `;
+ const expected = new (typedArrayCtor(scalarType))(bufferNumElements);
+ for (let id = 0; id < numInvocations; ++id) {
+ const i = mapId.f(id, numInvocations);
+ expected[Math.floor(i / 32)] |= 1 << i;
+ }
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected,
+ extra,
+ });
+ });
+
+g.test('or_workgroup')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize;
+
+ // Allocate workgroup array with bitsize of max invocations plus 1 for validation
+ const wgNumElements = Math.max(1, numInvocations / 32) + 1;
+
+ // Start with all bits low, then using atomicOr to set mapped local id bit on.
+ // Note: Both WGSL and JS will shift left 1 by id modulo 32.
+ const initValue = 0;
+
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations); // Defines map_id()
+ const op = `
+ let i = map_id(u32(id));
+ atomicOr(&wg[i / 32], ${scalarType}(1) << i)
+ `;
+ const expected = new (typedArrayCtor(scalarType))(wgNumElements * t.params.dispatchSize);
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ for (let id = 0; id < numInvocations; ++id) {
+ const wg = expected.subarray(d * wgNumElements);
+ const i = mapId.f(id, numInvocations);
+ wg[Math.floor(i / 32)] |= 1 << i;
+ }
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected,
+ extra,
+ });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts
new file mode 100644
index 0000000000..18ff72975d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts
@@ -0,0 +1,301 @@
+export const description = `
+Atomically stores the value v in the atomic object pointed to by atomic_ptr.
+`;
+
+import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ typedArrayCtor,
+ kMapId,
+} from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('store_storage_basic')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-store')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ const bufferNumElements = numInvocations;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const initValue = 0;
+ const op = `atomicStore(&output[id], map_id(id))`;
+ const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ expected.forEach((_, i) => (expected[i] = mapId.f(i, numInvocations)));
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected,
+ extra,
+ });
+ });
+
+g.test('store_workgroup_basic')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-store')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize;
+ const wgNumElements = numInvocations;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const initValue = 0;
+ const op = `atomicStore(&wg[id], map_id(global_id))`;
+ const expected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * t.params.dispatchSize
+ );
+ expected.forEach((_, i) => (expected[i] = mapId.f(i, numInvocations)));
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected,
+ extra,
+ });
+ });
+
+g.test('store_storage_advanced')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-store')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)
+
+Tests that multiple invocations of atomicStore to the same location returns
+one of the values written.
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(async t => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ @group(0) @binding(0)
+ var<storage, read_write> output : array<atomic<${scalarType}>>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+
+ // All invocations store to the same location
+ atomicStore(&output[0], map_id(id));
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ // Output buffer has only 1 element
+ const outputBuffer = t.device.createBuffer({
+ size: 1 * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(t.params.dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Read back the buffer
+ const outputBufferResult = (
+ await t.readGPUBufferRangeTyped(outputBuffer, {
+ type: arrayType,
+ typedLength: outputBuffer.size / arrayType.BYTES_PER_ELEMENT,
+ })
+ ).data;
+
+ // All invocations wrote to the output[0], so validate that it contains one
+ // of the possible computed values.
+ const expected_one_of = new arrayType(numInvocations);
+ expected_one_of.forEach((_, i) => (expected_one_of[i] = mapId.f(i, numInvocations)));
+
+ if (!expected_one_of.includes(outputBufferResult[0])) {
+ t.fail(
+ `Unexpected value in output[0]: '${outputBufferResult[0]}, expected value to be one of: ${expected_one_of}`
+ );
+ }
+ });
+
+g.test('store_workgroup_advanced')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-store')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)
+
+Tests that multiple invocations of atomicStore to the same location returns
+one of the values written.
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(async t => {
+ const numInvocations = t.params.workgroupSize;
+ const scalarType = t.params.scalarType;
+ const dispatchSize = t.params.dispatchSize;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ var<workgroup> wg: atomic<${scalarType}>;
+
+ // Result of each workgroup is written to output[workgroup_id.x]
+ @group(0) @binding(0)
+ var<storage, read_write> output: array<${scalarType}, ${dispatchSize}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+
+ // All invocations of a given dispatch store to the same location.
+ // In the end, the final value should be randomly equal to one of the ids.
+ atomicStore(&wg, map_id(id));
+
+ // Once all invocations have completed, the first one copies the result
+ // to output for this dispatch (workgroup_id.x)
+ workgroupBarrier();
+ if (local_invocation_index == 0u) {
+ output[workgroup_id.x] = atomicLoad(&wg);
+ }
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ const outputBuffer = t.device.createBuffer({
+ size: dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Read back the buffer
+ const outputBufferResult = (
+ await t.readGPUBufferRangeTyped(outputBuffer, {
+ type: arrayType,
+ typedLength: outputBuffer.size / arrayType.BYTES_PER_ELEMENT,
+ })
+ ).data;
+
+ // Each dispatch wrote to a single atomic workgroup var that was copied
+ // to outputBuffer[dispatch]. Validate that each value in the output buffer
+ // is one of the possible computed values.
+ const expected_one_of = new arrayType(numInvocations);
+ expected_one_of.forEach((_, i) => (expected_one_of[i] = mapId.f(i, numInvocations)));
+
+ for (let d = 0; d < dispatchSize; d++) {
+ if (!expected_one_of.includes(outputBufferResult[d])) {
+ t.fail(
+ `Unexpected value in output[d] for dispatch d '${d}': '${outputBufferResult[d]}', expected value to be one of: ${expected_one_of}`
+ );
+ }
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts
new file mode 100644
index 0000000000..6cea190299
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts
@@ -0,0 +1,101 @@
+export const description = `
+Atomically read, subtract and store value.
+
+* Load the original value pointed to by atomic_ptr.
+* Obtains a new value by subtracting with the value v.
+* Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;
+
+import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ typedArrayCtor,
+} from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sub_storage')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ // Allocate one extra element to ensure it doesn't get modified
+ const bufferNumElements = 2;
+
+ const initValue = 0;
+ const op = `atomicSub(&output[0], 1)`;
+ const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ expected[0] = -1 * numInvocations;
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected,
+ });
+ });
+
+g.test('sub_workgroup')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ // Allocate one extra element to ensure it doesn't get modified
+ const wgNumElements = 2;
+
+ const initValue = 0;
+ const op = `atomicSub(&wg[0], 1)`;
+
+ const expected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * t.params.dispatchSize
+ );
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ const wg = expected.subarray(d * wgNumElements);
+ wg[0] = -1 * t.params.workgroupSize;
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected,
+ });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts
new file mode 100644
index 0000000000..99192fd9fe
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts
@@ -0,0 +1,135 @@
+export const description = `
+Atomically read, xor and store value.
+
+* Load the original value pointed to by atomic_ptr.
+* Obtains a new value by xor'ing with the value v.
+* Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;
+
+import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ kMapId,
+ typedArrayCtor,
+} from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('xor_storage')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+
+ // Allocate an output buffer with bitsize of max invocations plus 1 for validation
+ const bufferNumElements = Math.max(1, numInvocations / 32) + 1;
+
+ // Start with all bits set to some random value for each u32 in the buffer, then atomicXor each mapped global id bit.
+ // Note: Both WGSL and JS will shift left 1 by id modulo 32.
+ const initValue = 0b11000011010110100000111100111100;
+
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations); // Defines map_id()
+ const op = `
+ let i = map_id(u32(id));
+ atomicXor(&output[i / 32], ${scalarType}(1) << i)
+ `;
+
+ const expected = new (typedArrayCtor(scalarType))(bufferNumElements).fill(initValue);
+ for (let id = 0; id < numInvocations; ++id) {
+ const i = mapId.f(id, numInvocations);
+ expected[Math.floor(i / 32)] ^= 1 << i;
+ }
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected,
+ extra,
+ });
+ });
+
+g.test('xor_workgroup')
+ .specURL('https://www.w3.org/TR/WGSL/#atomic-rmw')
+ .desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+ )
+ .params(u =>
+ u
+ .combine('workgroupSize', workgroupSizes)
+ .combine('dispatchSize', dispatchSizes)
+ .combine('mapId', keysOf(kMapId))
+ .combine('scalarType', ['u32', 'i32'])
+ )
+ .fn(t => {
+ const numInvocations = t.params.workgroupSize;
+
+ // Allocate workgroup array with bitsize of max invocations plus 1 for validation
+ const wgNumElements = Math.max(1, numInvocations / 32) + 1;
+
+ // Start with all bits set to some random value for each u32 in the buffer, then atomicXor each mapped global id bit.
+ // Note: Both WGSL and JS will shift left 1 by id modulo 32.
+ const initValue = 0b11000011010110100000111100111100;
+
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations); // Defines map_id()
+ const op = `
+ let i = map_id(u32(id));
+ atomicXor(&wg[i / 32], ${scalarType}(1) << i)
+ `;
+
+ const expected = new (typedArrayCtor(scalarType))(wgNumElements * t.params.dispatchSize).fill(
+ initValue
+ );
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ for (let id = 0; id < numInvocations; ++id) {
+ const wg = expected.subarray(d * wgNumElements);
+ const i = mapId.f(id, numInvocations);
+ wg[Math.floor(i / 32)] ^= 1 << i;
+ }
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected,
+ extra,
+ });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts
new file mode 100644
index 0000000000..ed02467f80
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts
@@ -0,0 +1,208 @@
+import {
+ assert,
+ TypedArrayBufferView,
+ TypedArrayBufferViewConstructor,
+} from '../../../../../../../common/util/util.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+// Use these in combination.
+export const workgroupSizes = [1, 2, 32, 64];
+export const dispatchSizes = [1, 4, 8, 16];
+
+// Use this alone - dispatch size should be 1.
+export const onlyWorkgroupSizes = [1, 2, 4, 8, 16, 32, 64, 128, 256];
+
+export const kMapId = {
+ passthrough: {
+ f: (id: number, _max: number) => id,
+ wgsl: (_max: number, scalarType = 'u32') =>
+ `fn map_id(id: ${scalarType}) -> ${scalarType} { return id; }`,
+ },
+ remap: {
+ f: (id: number, max: number) => (((id >>> 0) * 14957) ^ (((id >>> 0) * 26561) >> 2)) % max,
+ wgsl: (max: number, scalarType = 'u32') =>
+ `fn map_id(id: ${scalarType}) -> ${scalarType} { return ((id * 14957) ^ ((id * 26561) >> 2)) % ${max}; }`,
+ },
+};
+
+export function typedArrayCtor(scalarType: string): TypedArrayBufferViewConstructor {
+ switch (scalarType) {
+ case 'u32':
+ return Uint32Array;
+ case 'i32':
+ return Int32Array;
+ default:
+ assert(false, 'Atomic variables can only by u32 or i32');
+ return Uint8Array;
+ }
+}
+
+export function runStorageVariableTest({
+ t,
+ workgroupSize, // Workgroup X-size
+ dispatchSize, // Dispatch X-size
+ bufferNumElements, // Number of 32-bit elements in output buffer
+ initValue, // 32-bit initial value used to fill output buffer
+ // Atomic op source executed by the compute shader, NOTE: 'id' is global_invocation_id.x,
+ // and `output` is a storage array of atomics.
+ op,
+ expected, // Expected values array to compare against output buffer
+ extra, // Optional extra WGSL source
+}: {
+ t: GPUTest;
+ workgroupSize: number;
+ dispatchSize: number;
+ bufferNumElements: number;
+ initValue: number;
+ op: string;
+ expected: TypedArrayBufferView;
+ extra?: string;
+}) {
+ assert(expected.length === bufferNumElements, "'expected' buffer size is incorrect");
+
+ const scalarType = expected instanceof Uint32Array ? 'u32' : 'i32';
+ const arrayType = typedArrayCtor(scalarType);
+
+ const wgsl = `
+ @group(0) @binding(0)
+ var<storage, read_write> output : array<atomic<${scalarType}>>;
+
+ @compute @workgroup_size(${workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+ ${op};
+ }
+ ${extra || ''}
+ `;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const outputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true,
+ });
+ // Fill with initial value
+ t.trackForCleanup(outputBuffer);
+ const data = new arrayType(outputBuffer.getMappedRange());
+ data.fill(initValue);
+ outputBuffer.unmap();
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+}
+
+export function runWorkgroupVariableTest({
+ t,
+ workgroupSize, // Workgroup X-size
+ dispatchSize, // Dispatch X-size
+ wgNumElements, // Number of 32-bit elements in 'wg' array. Output buffer is sized to wgNumElements * dispatchSize.
+ initValue, // 32-bit initial value used to fill 'wg' array
+ // Atomic op source executed by the compute shader, NOTE: 'id' is local_invocation_index,
+ // `wg` is a workgroup array of atomics of size `workgroupSize`, `output` is a storage array of non-atomics of size
+ // `workgroupSize * dispatcSize` to which each dispatch of `wg` gets copied to (dispatch 0 to first workgroupSize elements,
+ // dispatch 1 to second workgroupSize elements, etc.).
+ op,
+ expected, // Expected values array to compare against output buffer
+ extra, // Optional extra WGSL source
+}: {
+ t: GPUTest;
+ workgroupSize: number;
+ dispatchSize: number;
+ wgNumElements: number;
+ initValue: number;
+ op: string;
+ expected: TypedArrayBufferView;
+ extra?: string;
+}) {
+ assert(expected.length === wgNumElements * dispatchSize, "'expected' buffer size is incorrect");
+
+ const scalarType = expected instanceof Uint32Array ? 'u32' : 'i32';
+ const arrayType = typedArrayCtor(scalarType);
+
+ const wgsl = `
+ var<workgroup> wg: array<atomic<${scalarType}>, ${wgNumElements}>;
+
+ // Result of each workgroup is written to output[workgroup_id.x]
+ @group(0) @binding(0)
+ var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ @compute @workgroup_size(${workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+ let global_id = ${scalarType}(workgroup_id.x * ${wgNumElements} + local_invocation_index);
+
+ // Initialize workgroup array
+ if (local_invocation_index == 0) {
+ for (var i = 0u; i < ${wgNumElements}; i++) {
+ atomicStore(&wg[i], bitcast<${scalarType}>(${initValue}u));
+ }
+ }
+ workgroupBarrier();
+
+ ${op};
+
+ // Copy results to output buffer
+ workgroupBarrier();
+ if (local_invocation_index == 0) {
+ for (var i = 0u; i < ${wgNumElements}; i++) {
+ output[(workgroup_id.x * ${wgNumElements}) + i] = atomicLoad(&wg[i]);
+ }
+ }
+ }
+ ${extra || ''}
+ `;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const outputBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts
new file mode 100644
index 0000000000..390129f2c7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts
@@ -0,0 +1,1275 @@
+export const description = `
+Execution tests for the 'bitcast' builtin function
+
+@const @must_use fn bitcast<T>(e: T ) -> T
+T is concrete numeric scalar or concerete numeric vector
+Identity function.
+
+@const @must_use fn bitcast<T>(e: S ) -> T
+@const @must_use fn bitcast<vecN<T>>(e: vecN<S> ) -> vecN<T>
+S is i32, u32, f32
+T is i32, u32, f32, and T is not S
+Reinterpretation of bits. Beware non-normal f32 values.
+
+@const @must_use fn bitcast<T>(e: vec2<f16> ) -> T
+@const @must_use fn bitcast<vec2<T>>(e: vec4<f16> ) -> vec2<T>
+@const @must_use fn bitcast<vec2<f16>>(e: T ) -> vec2<f16>
+@const @must_use fn bitcast<vec4<f16>>(e: vec2<T> ) -> vec4<f16>
+T is i32, u32, f32
+`;
+
+import { TestParams } from '../../../../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { assert } from '../../../../../../common/util/util.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { Comparator, alwaysPass, anyOf } from '../../../../../util/compare.js';
+import { kBit, kValue } from '../../../../../util/constants.js';
+import {
+ f32,
+ i32,
+ u32,
+ f16,
+ TypeF32,
+ TypeI32,
+ TypeU32,
+ TypeF16,
+ TypeVec,
+ Vector,
+ Scalar,
+ toVector,
+} from '../../../../../util/conversion.js';
+import { FPInterval, FP } from '../../../../../util/floating_point.js';
+import {
+ fullF32Range,
+ fullI32Range,
+ fullU32Range,
+ fullF16Range,
+ linearRange,
+ isSubnormalNumberF32,
+ isSubnormalNumberF16,
+ cartesianProduct,
+ isFiniteF32,
+ isFiniteF16,
+} from '../../../../../util/math.js';
+import {
+ reinterpretI32AsF32,
+ reinterpretI32AsU32,
+ reinterpretF32AsI32,
+ reinterpretF32AsU32,
+ reinterpretU32AsF32,
+ reinterpretU32AsI32,
+ reinterpretU16AsF16,
+ reinterpretF16AsU16,
+} from '../../../../../util/reinterpret.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run, ShaderBuilder } from '../../expression.js';
+
+import { builtinWithPredeclaration } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+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[] = [...fullF32Range(), kValue.f32.negative.zero];
+const f32RangeWithInfAndNaN: number[] = [...f32FiniteRange, ...f32InfAndNaNInF32];
+
+// F16 values, finite, Inf/NaN, and zeros. Represented in float and u16.
+const f16FiniteInF16: number[] = [...fullF16Range(), 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[]): Vector {
+ 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[]): Vector {
+ 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: (Scalar | 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) => Scalar[] | FPInterval[];
+ let unboundedExpectations: FPInterval[] | Scalar[];
+ 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<Scalar | 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 Vector(e as Scalar[])
+ )
+ );
+}
+
+/**
+ * @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 Vector(e as Scalar[])
+ )
+ );
+}
+
+/**
+ * @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 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),
+ })),
+ 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 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),
+ })),
+ 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),
+ })),
+
+ // vec2<i32>, vec2<u32>, vec2<f32> 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_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<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),
+ })),
+});
+
+/**
+ * @returns a ShaderBuilder that generates a call to bitcast,
+ * using appropriate destination type, which optionally can be
+ * a WGSL type alias.
+ */
+function bitcastBuilder(canonicalDestType: string, params: TestParams): ShaderBuilder {
+ const destType = params.vectorize
+ ? `vec${params.vectorize}<${canonicalDestType}>`
+ : canonicalDestType;
+
+ return builtinWithPredeclaration(
+ `bitcast<${destType}>`,
+ params.alias ? `alias myalias = ${destType};` : ''
+ );
+}
+
+// Identity cases
+g.test('i32_to_i32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast i32 to i32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get('i32_to_i32');
+ await run(t, bitcastBuilder('i32', t.params), [TypeI32], TypeI32, t.params, cases);
+ });
+
+g.test('u32_to_u32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast u32 to u32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get('u32_to_u32');
+ await run(t, bitcastBuilder('u32', t.params), [TypeU32], TypeU32, t.params, cases);
+ });
+
+g.test('f32_to_f32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast f32 to f32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'f32_to_f32' : 'f32_inf_nan_to_f32'
+ );
+ await run(t, bitcastBuilder('f32', t.params), [TypeF32], TypeF32, t.params, cases);
+ });
+
+// To i32 from u32, f32
+g.test('u32_to_i32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast u32 to i32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get('u32_to_i32');
+ await run(t, bitcastBuilder('i32', t.params), [TypeU32], TypeI32, t.params, cases);
+ });
+
+g.test('f32_to_i32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast f32 to i32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'f32_to_i32' : 'f32_inf_nan_to_i32'
+ );
+ await run(t, bitcastBuilder('i32', t.params), [TypeF32], TypeI32, t.params, cases);
+ });
+
+// To u32 from i32, f32
+g.test('i32_to_u32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast i32 to u32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get('i32_to_u32');
+ await run(t, bitcastBuilder('u32', t.params), [TypeI32], TypeU32, t.params, cases);
+ });
+
+g.test('f32_to_u32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast f32 to i32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'f32_to_u32' : 'f32_inf_nan_to_u32'
+ );
+ await run(t, bitcastBuilder('u32', t.params), [TypeF32], TypeU32, t.params, cases);
+ });
+
+// To f32 from i32, u32
+g.test('i32_to_f32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast i32 to f32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'i32_to_f32' : 'i32_to_f32_inf_nan'
+ );
+ await run(t, bitcastBuilder('f32', t.params), [TypeI32], TypeF32, t.params, cases);
+ });
+
+g.test('u32_to_f32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast u32 to f32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'u32_to_f32' : 'u32_to_f32_inf_nan'
+ );
+ await run(t, bitcastBuilder('f32', t.params), [TypeU32], TypeF32, t.params, cases);
+ });
+
+// 16 bit types
+
+// f16 cases
+
+// f16: Identity
+g.test('f16_to_f16')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast f16 to f16 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'f16_to_f16' : 'f16_inf_nan_to_f16'
+ );
+ await run(t, bitcastBuilder('f16', t.params), [TypeF16], TypeF16, t.params, cases);
+ });
+
+// f16: 32-bit scalar numeric to vec2<f16>
+g.test('i32_to_vec2h')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast i32 to vec2h tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'i32_to_vec2_f16' : 'i32_to_vec2_f16_inf_nan'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec2<f16>', t.params),
+ [TypeI32],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('u32_to_vec2h')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast u32 to vec2h tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'u32_to_vec2_f16' : 'u32_to_vec2_f16_inf_nan'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec2<f16>', t.params),
+ [TypeU32],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_to_vec2h')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast u32 to vec2h tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'f32_to_vec2_f16' : 'f32_inf_nan_to_vec2_f16_inf_nan'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec2<f16>', t.params),
+ [TypeF32],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+// f16: vec2<32-bit scalar numeric> to vec4<f16>
+g.test('vec2i_to_vec4h')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast vec2i to vec4h tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec2_i32_to_vec4_f16' : 'vec2_i32_to_vec4_f16_inf_nan'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec4<f16>', t.params),
+ [TypeVec(2, TypeI32)],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('vec2u_to_vec4h')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast vec2u to vec4h tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec2_u32_to_vec4_f16' : 'vec2_u32_to_vec4_f16_inf_nan'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec4<f16>', t.params),
+ [TypeVec(2, TypeU32)],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('vec2f_to_vec4h')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast vec2f to vec2h tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const'
+ ? 'vec2_f32_to_vec4_f16'
+ : 'vec2_f32_inf_nan_to_vec4_f16_inf_nan'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec4<f16>', t.params),
+ [TypeVec(2, TypeF32)],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+// f16: vec2<f16> to 32-bit scalar numeric
+g.test('vec2h_to_i32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast vec2h to i32 tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec2_f16_to_i32' : 'vec2_f16_inf_nan_to_i32'
+ );
+ await run(t, bitcastBuilder('i32', t.params), [TypeVec(2, TypeF16)], TypeI32, t.params, cases);
+ });
+
+g.test('vec2h_to_u32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast vec2h to u32 tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec2_f16_to_u32' : 'vec2_f16_inf_nan_to_u32'
+ );
+ await run(t, bitcastBuilder('u32', t.params), [TypeVec(2, TypeF16)], TypeU32, t.params, cases);
+ });
+
+g.test('vec2h_to_f32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast vec2h to f32 tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec2_f16_to_f32_finite' : 'vec2_f16_inf_nan_to_f32'
+ );
+ await run(t, bitcastBuilder('f32', t.params), [TypeVec(2, TypeF16)], TypeF32, t.params, cases);
+ });
+
+// f16: vec4<f16> to vec2<32-bit scalar numeric>
+g.test('vec4h_to_vec2i')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast vec4h to vec2i tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec4_f16_to_vec2_i32' : 'vec4_f16_inf_nan_to_vec2_i32'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec2<i32>', t.params),
+ [TypeVec(4, TypeF16)],
+ TypeVec(2, TypeI32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('vec4h_to_vec2u')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast vec4h to vec2u tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec4_f16_to_vec2_u32' : 'vec4_f16_inf_nan_to_vec2_u32'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec2<u32>', t.params),
+ [TypeVec(4, TypeF16)],
+ TypeVec(2, TypeU32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('vec4h_to_vec2f')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast vec4h to vec2f tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const'
+ ? 'vec4_f16_to_vec2_f32_finite'
+ : 'vec4_f16_inf_nan_to_vec2_f32'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec2<f32>', t.params),
+ [TypeVec(4, TypeF16)],
+ TypeVec(2, TypeF32),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts
new file mode 100644
index 0000000000..282feea703
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts
@@ -0,0 +1,24 @@
+import {
+ abstractFloatShaderBuilder,
+ basicExpressionBuilder,
+ basicExpressionWithPredeclarationBuilder,
+ ShaderBuilder,
+} from '../../expression.js';
+
+/* @returns a ShaderBuilder that calls the builtin with the given name */
+export function builtin(name: string): ShaderBuilder {
+ return basicExpressionBuilder(values => `${name}(${values.join(', ')})`);
+}
+
+/* @returns a ShaderBuilder that calls the builtin with the given name that returns AbstractFloats */
+export function abstractBuiltin(name: string): ShaderBuilder {
+ return abstractFloatShaderBuilder(values => `${name}(${values.join(', ')})`);
+}
+
+/* @returns a ShaderBuilder that calls the builtin with the given name and has given predeclaration */
+export function builtinWithPredeclaration(name: string, predeclaration: string): ShaderBuilder {
+ return basicExpressionWithPredeclarationBuilder(
+ values => `${name}(${values.join(', ')})`,
+ predeclaration
+ );
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts
new file mode 100644
index 0000000000..6cdf90986b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts
@@ -0,0 +1,101 @@
+export const description = `
+Execution tests for the 'ceil' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn ceil(e: T ) -> T
+Returns the ceiling of e. Component-wise when T is a vector.
+
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('ceil', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // Small positive numbers
+ 0.1,
+ 0.9,
+ 1.0,
+ 1.1,
+ 1.9,
+ // Small negative numbers
+ -0.1,
+ -0.9,
+ -1.0,
+ -1.1,
+ -1.9,
+ 0x80000000, // https://github.com/gpuweb/cts/issues/2766
+ ...fullF32Range(),
+ ],
+ 'unfiltered',
+ FP.f32.ceilInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // Small positive numbers
+ 0.1,
+ 0.9,
+ 1.0,
+ 1.1,
+ 1.9,
+ // Small negative numbers
+ -0.1,
+ -0.9,
+ -1.0,
+ -1.1,
+ -1.9,
+ 0x8000, // https://github.com/gpuweb/cts/issues/2766
+ ...fullF16Range(),
+ ],
+ 'unfiltered',
+ FP.f16.ceilInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('ceil'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('ceil'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts
new file mode 100644
index 0000000000..0113fd656f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts
@@ -0,0 +1,195 @@
+export const description = `
+Execution tests for the 'clamp' builtin function
+
+S is AbstractInt, i32, or u32
+T is S or vecN<S>
+@const fn clamp(e: T , low: T, high: T) -> T
+Returns min(max(e,low),high). Component-wise when T is a vector.
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const clamp(e: T , low: T , high: T) -> T
+Returns either min(max(e,low),high), or the median of the three values e, low, high.
+Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import {
+ ScalarType,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeU32,
+ TypeAbstractFloat,
+} from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseF32Range, sparseF16Range, sparseF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, Case, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const u32Values = [0, 1, 2, 3, 0x70000000, 0x80000000, kValue.u32.max];
+
+const i32Values = [
+ kValue.i32.negative.min,
+ -3,
+ -2,
+ -1,
+ 0,
+ 1,
+ 2,
+ 3,
+ 0x70000000,
+ kValue.i32.positive.max,
+];
+
+export const d = makeCaseCache('clamp', {
+ u32_non_const: () => {
+ return generateIntegerTestCases(u32Values, TypeU32, 'non-const');
+ },
+ u32_const: () => {
+ return generateIntegerTestCases(u32Values, TypeU32, 'const');
+ },
+ i32_non_const: () => {
+ return generateIntegerTestCases(i32Values, TypeI32, 'non-const');
+ },
+ i32_const: () => {
+ return generateIntegerTestCases(i32Values, TypeI32, 'const');
+ },
+ f32_const: () => {
+ return generateFloatTestCases(sparseF32Range(), 'f32', 'const');
+ },
+ f32_non_const: () => {
+ return generateFloatTestCases(sparseF32Range(), 'f32', 'non-const');
+ },
+ f16_const: () => {
+ return generateFloatTestCases(sparseF16Range(), 'f16', 'const');
+ },
+ f16_non_const: () => {
+ return generateFloatTestCases(sparseF16Range(), 'f16', 'non-const');
+ },
+ abstract: () => {
+ return generateFloatTestCases(sparseF64Range(), 'abstract', 'const');
+ },
+});
+
+/** @returns a set of clamp test cases from an ascending list of integer values */
+function generateIntegerTestCases(
+ test_values: Array<number>,
+ type: ScalarType,
+ stage: 'const' | 'non-const'
+): Array<Case> {
+ return test_values.flatMap(low =>
+ test_values.flatMap(high =>
+ stage === 'const' && low > high
+ ? []
+ : test_values.map(e => ({
+ input: [type.create(e), type.create(low), type.create(high)],
+ expected: type.create(Math.min(Math.max(e, low), high)),
+ }))
+ )
+ );
+}
+
+function generateFloatTestCases(
+ test_values: readonly number[],
+ trait: 'f32' | 'f16' | 'abstract',
+ stage: 'const' | 'non-const'
+): Array<Case> {
+ return test_values.flatMap(low =>
+ test_values.flatMap(high =>
+ stage === 'const' && low > high
+ ? []
+ : test_values.flatMap(e => {
+ const c = FP[trait].makeScalarTripleToIntervalCase(
+ e,
+ low,
+ high,
+ stage === 'const' ? 'finite' : 'unfiltered',
+ ...FP[trait].clampIntervals
+ );
+ return c === undefined ? [] : [c];
+ })
+ )
+ );
+}
+
+g.test('abstract_int')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`abstract int tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`u32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
+ await run(t, builtin('clamp'), [TypeU32, TypeU32, TypeU32], TypeU32, t.params, cases);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`i32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'i32_const' : 'i32_non_const');
+ await run(t, builtin('clamp'), [TypeI32, TypeI32, TypeI32], TypeI32, t.params, cases);
+ });
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('clamp'),
+ [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('clamp'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('clamp'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts
new file mode 100644
index 0000000000..723bca2efd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts
@@ -0,0 +1,84 @@
+export const description = `
+Execution tests for the 'cos' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn cos(e: T ) -> T
+Returns the cosine of e. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('cos', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 1000),
+ ...fullF32Range(),
+ ],
+ 'unfiltered',
+ FP.f32.cosInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 1000),
+ ...fullF16Range(),
+ ],
+ 'unfiltered',
+ FP.f16.cosInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+f32 tests
+
+TODO(#792): Decide what the ground-truth is for these tests. [1]
+`
+ )
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('cos'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('cos'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts
new file mode 100644
index 0000000000..37fb961c98
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts
@@ -0,0 +1,68 @@
+export const description = `
+Execution tests for the 'cosh' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn cosh(e: T ) -> T
+Returns the hyperbolic cosine of e. Component-wise when T is a vector
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('cosh', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.coshInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.coshInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.coshInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.coshInterval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('cosh'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('cosh'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts
new file mode 100644
index 0000000000..cfae4bb6e0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts
@@ -0,0 +1,250 @@
+export const description = `
+Execution tests for the 'countLeadingZeros' builtin function
+
+S is i32 or u32
+T is S or vecN<S>
+@const fn countLeadingZeros(e: T ) -> T
+The number of consecutive 0 bits starting from the most significant bit of e,
+when T is a scalar type.
+Component-wise when T is a vector.
+Also known as "clz" in some languages.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeU32, u32Bits, u32, TypeI32, i32Bits, i32 } from '../../../../../util/conversion.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`u32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ await run(t, builtin('countLeadingZeros'), [TypeU32], TypeU32, cfg, [
+ // Zero
+ { input: u32Bits(0b00000000000000000000000000000000), expected: u32(32) },
+
+ // One
+ { input: u32Bits(0b00000000000000000000000000000001), expected: u32(31) },
+
+ // 0's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000010), expected: u32(30) },
+ { input: u32Bits(0b00000000000000000000000000000100), expected: u32(29) },
+ { input: u32Bits(0b00000000000000000000000000001000), expected: u32(28) },
+ { input: u32Bits(0b00000000000000000000000000010000), expected: u32(27) },
+ { input: u32Bits(0b00000000000000000000000000100000), expected: u32(26) },
+ { input: u32Bits(0b00000000000000000000000001000000), expected: u32(25) },
+ { input: u32Bits(0b00000000000000000000000010000000), expected: u32(24) },
+ { input: u32Bits(0b00000000000000000000000100000000), expected: u32(23) },
+ { input: u32Bits(0b00000000000000000000001000000000), expected: u32(22) },
+ { input: u32Bits(0b00000000000000000000010000000000), expected: u32(21) },
+ { input: u32Bits(0b00000000000000000000100000000000), expected: u32(20) },
+ { input: u32Bits(0b00000000000000000001000000000000), expected: u32(19) },
+ { input: u32Bits(0b00000000000000000010000000000000), expected: u32(18) },
+ { input: u32Bits(0b00000000000000000100000000000000), expected: u32(17) },
+ { input: u32Bits(0b00000000000000001000000000000000), expected: u32(16) },
+ { input: u32Bits(0b00000000000000010000000000000000), expected: u32(15) },
+ { input: u32Bits(0b00000000000000100000000000000000), expected: u32(14) },
+ { input: u32Bits(0b00000000000001000000000000000000), expected: u32(13) },
+ { input: u32Bits(0b00000000000010000000000000000000), expected: u32(12) },
+ { input: u32Bits(0b00000000000100000000000000000000), expected: u32(11) },
+ { input: u32Bits(0b00000000001000000000000000000000), expected: u32(10) },
+ { input: u32Bits(0b00000000010000000000000000000000), expected: u32(9) },
+ { input: u32Bits(0b00000000100000000000000000000000), expected: u32(8) },
+ { input: u32Bits(0b00000001000000000000000000000000), expected: u32(7) },
+ { input: u32Bits(0b00000010000000000000000000000000), expected: u32(6) },
+ { input: u32Bits(0b00000100000000000000000000000000), expected: u32(5) },
+ { input: u32Bits(0b00001000000000000000000000000000), expected: u32(4) },
+ { input: u32Bits(0b00010000000000000000000000000000), expected: u32(3) },
+ { input: u32Bits(0b00100000000000000000000000000000), expected: u32(2) },
+ { input: u32Bits(0b01000000000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b10000000000000000000000000000000), expected: u32(0) },
+
+ // 1's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000011), expected: u32(30) },
+ { input: u32Bits(0b00000000000000000000000000000111), expected: u32(29) },
+ { input: u32Bits(0b00000000000000000000000000001111), expected: u32(28) },
+ { input: u32Bits(0b00000000000000000000000000011111), expected: u32(27) },
+ { input: u32Bits(0b00000000000000000000000000111111), expected: u32(26) },
+ { input: u32Bits(0b00000000000000000000000001111111), expected: u32(25) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32(24) },
+ { input: u32Bits(0b00000000000000000000000111111111), expected: u32(23) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32(22) },
+ { input: u32Bits(0b00000000000000000000011111111111), expected: u32(21) },
+ { input: u32Bits(0b00000000000000000000111111111111), expected: u32(20) },
+ { input: u32Bits(0b00000000000000000001111111111111), expected: u32(19) },
+ { input: u32Bits(0b00000000000000000011111111111111), expected: u32(18) },
+ { input: u32Bits(0b00000000000000000111111111111111), expected: u32(17) },
+ { input: u32Bits(0b00000000000000001111111111111111), expected: u32(16) },
+ { input: u32Bits(0b00000000000000011111111111111111), expected: u32(15) },
+ { input: u32Bits(0b00000000000000111111111111111111), expected: u32(14) },
+ { input: u32Bits(0b00000000000001111111111111111111), expected: u32(13) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32(12) },
+ { input: u32Bits(0b00000000000111111111111111111111), expected: u32(11) },
+ { input: u32Bits(0b00000000001111111111111111111111), expected: u32(10) },
+ { input: u32Bits(0b00000000011111111111111111111111), expected: u32(9) },
+ { input: u32Bits(0b00000000111111111111111111111111), expected: u32(8) },
+ { input: u32Bits(0b00000001111111111111111111111111), expected: u32(7) },
+ { input: u32Bits(0b00000011111111111111111111111111), expected: u32(6) },
+ { input: u32Bits(0b00000111111111111111111111111111), expected: u32(5) },
+ { input: u32Bits(0b00001111111111111111111111111111), expected: u32(4) },
+ { input: u32Bits(0b00011111111111111111111111111111), expected: u32(3) },
+ { input: u32Bits(0b00111111111111111111111111111111), expected: u32(2) },
+ { input: u32Bits(0b01111111111111111111111111111111), expected: u32(1) },
+ { input: u32Bits(0b11111111111111111111111111111111), expected: u32(0) },
+
+ // random after leading 1
+ { input: u32Bits(0b00000000000000000000000000000110), expected: u32(29) },
+ { input: u32Bits(0b00000000000000000000000000001101), expected: u32(28) },
+ { input: u32Bits(0b00000000000000000000000000011101), expected: u32(27) },
+ { input: u32Bits(0b00000000000000000000000000111001), expected: u32(26) },
+ { input: u32Bits(0b00000000000000000000000001101111), expected: u32(25) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32(24) },
+ { input: u32Bits(0b00000000000000000000000111101111), expected: u32(23) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32(22) },
+ { input: u32Bits(0b00000000000000000000011111110001), expected: u32(21) },
+ { input: u32Bits(0b00000000000000000000111011011101), expected: u32(20) },
+ { input: u32Bits(0b00000000000000000001101101111111), expected: u32(19) },
+ { input: u32Bits(0b00000000000000000011111111011111), expected: u32(18) },
+ { input: u32Bits(0b00000000000000000101111001110101), expected: u32(17) },
+ { input: u32Bits(0b00000000000000001101111011110111), expected: u32(16) },
+ { input: u32Bits(0b00000000000000011111111111110011), expected: u32(15) },
+ { input: u32Bits(0b00000000000000111111111110111111), expected: u32(14) },
+ { input: u32Bits(0b00000000000001111111011111111111), expected: u32(13) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32(12) },
+ { input: u32Bits(0b00000000000111110101011110111111), expected: u32(11) },
+ { input: u32Bits(0b00000000001111101111111111110111), expected: u32(10) },
+ { input: u32Bits(0b00000000011111111111010000101111), expected: u32(9) },
+ { input: u32Bits(0b00000000111111111111001111111011), expected: u32(8) },
+ { input: u32Bits(0b00000001111111011111101111111111), expected: u32(7) },
+ { input: u32Bits(0b00000011101011111011110111111011), expected: u32(6) },
+ { input: u32Bits(0b00000111111110111111111111111111), expected: u32(5) },
+ { input: u32Bits(0b00001111000000011011011110111111), expected: u32(4) },
+ { input: u32Bits(0b00011110101111011111111111111111), expected: u32(3) },
+ { input: u32Bits(0b00110110111111100111111110111101), expected: u32(2) },
+ { input: u32Bits(0b01010111111101111111011111011111), expected: u32(1) },
+ { input: u32Bits(0b11100010011110101101101110101111), expected: u32(0) },
+ ]);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`i32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ await run(t, builtin('countLeadingZeros'), [TypeI32], TypeI32, cfg, [
+ // Zero
+ { input: i32Bits(0b00000000000000000000000000000000), expected: i32(32) },
+
+ // One
+ { input: i32Bits(0b00000000000000000000000000000001), expected: i32(31) },
+
+ // 0's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000010), expected: i32(30) },
+ { input: i32Bits(0b00000000000000000000000000000100), expected: i32(29) },
+ { input: i32Bits(0b00000000000000000000000000001000), expected: i32(28) },
+ { input: i32Bits(0b00000000000000000000000000010000), expected: i32(27) },
+ { input: i32Bits(0b00000000000000000000000000100000), expected: i32(26) },
+ { input: i32Bits(0b00000000000000000000000001000000), expected: i32(25) },
+ { input: i32Bits(0b00000000000000000000000010000000), expected: i32(24) },
+ { input: i32Bits(0b00000000000000000000000100000000), expected: i32(23) },
+ { input: i32Bits(0b00000000000000000000001000000000), expected: i32(22) },
+ { input: i32Bits(0b00000000000000000000010000000000), expected: i32(21) },
+ { input: i32Bits(0b00000000000000000000100000000000), expected: i32(20) },
+ { input: i32Bits(0b00000000000000000001000000000000), expected: i32(19) },
+ { input: i32Bits(0b00000000000000000010000000000000), expected: i32(18) },
+ { input: i32Bits(0b00000000000000000100000000000000), expected: i32(17) },
+ { input: i32Bits(0b00000000000000001000000000000000), expected: i32(16) },
+ { input: i32Bits(0b00000000000000010000000000000000), expected: i32(15) },
+ { input: i32Bits(0b00000000000000100000000000000000), expected: i32(14) },
+ { input: i32Bits(0b00000000000001000000000000000000), expected: i32(13) },
+ { input: i32Bits(0b00000000000010000000000000000000), expected: i32(12) },
+ { input: i32Bits(0b00000000000100000000000000000000), expected: i32(11) },
+ { input: i32Bits(0b00000000001000000000000000000000), expected: i32(10) },
+ { input: i32Bits(0b00000000010000000000000000000000), expected: i32(9) },
+ { input: i32Bits(0b00000000100000000000000000000000), expected: i32(8) },
+ { input: i32Bits(0b00000001000000000000000000000000), expected: i32(7) },
+ { input: i32Bits(0b00000010000000000000000000000000), expected: i32(6) },
+ { input: i32Bits(0b00000100000000000000000000000000), expected: i32(5) },
+ { input: i32Bits(0b00001000000000000000000000000000), expected: i32(4) },
+ { input: i32Bits(0b00010000000000000000000000000000), expected: i32(3) },
+ { input: i32Bits(0b00100000000000000000000000000000), expected: i32(2) },
+ { input: i32Bits(0b01000000000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b10000000000000000000000000000000), expected: i32(0) },
+
+ // 1's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000011), expected: i32(30) },
+ { input: i32Bits(0b00000000000000000000000000000111), expected: i32(29) },
+ { input: i32Bits(0b00000000000000000000000000001111), expected: i32(28) },
+ { input: i32Bits(0b00000000000000000000000000011111), expected: i32(27) },
+ { input: i32Bits(0b00000000000000000000000000111111), expected: i32(26) },
+ { input: i32Bits(0b00000000000000000000000001111111), expected: i32(25) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32(24) },
+ { input: i32Bits(0b00000000000000000000000111111111), expected: i32(23) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32(22) },
+ { input: i32Bits(0b00000000000000000000011111111111), expected: i32(21) },
+ { input: i32Bits(0b00000000000000000000111111111111), expected: i32(20) },
+ { input: i32Bits(0b00000000000000000001111111111111), expected: i32(19) },
+ { input: i32Bits(0b00000000000000000011111111111111), expected: i32(18) },
+ { input: i32Bits(0b00000000000000000111111111111111), expected: i32(17) },
+ { input: i32Bits(0b00000000000000001111111111111111), expected: i32(16) },
+ { input: i32Bits(0b00000000000000011111111111111111), expected: i32(15) },
+ { input: i32Bits(0b00000000000000111111111111111111), expected: i32(14) },
+ { input: i32Bits(0b00000000000001111111111111111111), expected: i32(13) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32(12) },
+ { input: i32Bits(0b00000000000111111111111111111111), expected: i32(11) },
+ { input: i32Bits(0b00000000001111111111111111111111), expected: i32(10) },
+ { input: i32Bits(0b00000000011111111111111111111111), expected: i32(9) },
+ { input: i32Bits(0b00000000111111111111111111111111), expected: i32(8) },
+ { input: i32Bits(0b00000001111111111111111111111111), expected: i32(7) },
+ { input: i32Bits(0b00000011111111111111111111111111), expected: i32(6) },
+ { input: i32Bits(0b00000111111111111111111111111111), expected: i32(5) },
+ { input: i32Bits(0b00001111111111111111111111111111), expected: i32(4) },
+ { input: i32Bits(0b00011111111111111111111111111111), expected: i32(3) },
+ { input: i32Bits(0b00111111111111111111111111111111), expected: i32(2) },
+ { input: i32Bits(0b01111111111111111111111111111111), expected: i32(1) },
+ { input: i32Bits(0b11111111111111111111111111111111), expected: i32(0) },
+
+ // random after leading 1
+ { input: i32Bits(0b00000000000000000000000000000110), expected: i32(29) },
+ { input: i32Bits(0b00000000000000000000000000001101), expected: i32(28) },
+ { input: i32Bits(0b00000000000000000000000000011101), expected: i32(27) },
+ { input: i32Bits(0b00000000000000000000000000111001), expected: i32(26) },
+ { input: i32Bits(0b00000000000000000000000001101111), expected: i32(25) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32(24) },
+ { input: i32Bits(0b00000000000000000000000111101111), expected: i32(23) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32(22) },
+ { input: i32Bits(0b00000000000000000000011111110001), expected: i32(21) },
+ { input: i32Bits(0b00000000000000000000111011011101), expected: i32(20) },
+ { input: i32Bits(0b00000000000000000001101101111111), expected: i32(19) },
+ { input: i32Bits(0b00000000000000000011111111011111), expected: i32(18) },
+ { input: i32Bits(0b00000000000000000101111001110101), expected: i32(17) },
+ { input: i32Bits(0b00000000000000001101111011110111), expected: i32(16) },
+ { input: i32Bits(0b00000000000000011111111111110011), expected: i32(15) },
+ { input: i32Bits(0b00000000000000111111111110111111), expected: i32(14) },
+ { input: i32Bits(0b00000000000001111111011111111111), expected: i32(13) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32(12) },
+ { input: i32Bits(0b00000000000111110101011110111111), expected: i32(11) },
+ { input: i32Bits(0b00000000001111101111111111110111), expected: i32(10) },
+ { input: i32Bits(0b00000000011111111111010000101111), expected: i32(9) },
+ { input: i32Bits(0b00000000111111111111001111111011), expected: i32(8) },
+ { input: i32Bits(0b00000001111111011111101111111111), expected: i32(7) },
+ { input: i32Bits(0b00000011101011111011110111111011), expected: i32(6) },
+ { input: i32Bits(0b00000111111110111111111111111111), expected: i32(5) },
+ { input: i32Bits(0b00001111000000011011011110111111), expected: i32(4) },
+ { input: i32Bits(0b00011110101111011111111111111111), expected: i32(3) },
+ { input: i32Bits(0b00110110111111100111111110111101), expected: i32(2) },
+ { input: i32Bits(0b01010111111101111111011111011111), expected: i32(1) },
+ { input: i32Bits(0b11100010011110101101101110101111), expected: i32(0) },
+ ]);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts
new file mode 100644
index 0000000000..f0be916285
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts
@@ -0,0 +1,249 @@
+export const description = `
+Execution tests for the 'countOneBits' builtin function
+
+S is i32 or u32
+T is S or vecN<S>
+@const fn countOneBits(e: T ) -> T
+The number of 1 bits in the representation of e.
+Also known as "population count".
+Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeU32, u32Bits, u32, TypeI32, i32Bits, i32 } from '../../../../../util/conversion.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`u32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ await run(t, builtin('countOneBits'), [TypeU32], TypeU32, cfg, [
+ // Zero
+ { input: u32Bits(0b00000000000000000000000000000000), expected: u32(0) },
+
+ // One
+ { input: u32Bits(0b00000000000000000000000000000001), expected: u32(1) },
+
+ // 0's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000010), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000000100), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000001000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000010000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000100000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000001000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000010000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000100000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000001000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000010000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000100000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000001000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000010000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000100000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000001000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000010000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000100000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000001000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000010000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000100000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000001000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000010000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000100000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000001000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000010000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000100000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00001000000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00010000000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00100000000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b01000000000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b10000000000000000000000000000000), expected: u32(1) },
+
+ // 1's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000011), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000000111), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000001111), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000011111), expected: u32(5) },
+ { input: u32Bits(0b00000000000000000000000000111111), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000001111111), expected: u32(7) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000000111111111), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000011111111111), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000000111111111111), expected: u32(12) },
+ { input: u32Bits(0b00000000000000000001111111111111), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000011111111111111), expected: u32(14) },
+ { input: u32Bits(0b00000000000000000111111111111111), expected: u32(15) },
+ { input: u32Bits(0b00000000000000001111111111111111), expected: u32(16) },
+ { input: u32Bits(0b00000000000000011111111111111111), expected: u32(17) },
+ { input: u32Bits(0b00000000000000111111111111111111), expected: u32(18) },
+ { input: u32Bits(0b00000000000001111111111111111111), expected: u32(19) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32(20) },
+ { input: u32Bits(0b00000000000111111111111111111111), expected: u32(21) },
+ { input: u32Bits(0b00000000001111111111111111111111), expected: u32(22) },
+ { input: u32Bits(0b00000000011111111111111111111111), expected: u32(23) },
+ { input: u32Bits(0b00000000111111111111111111111111), expected: u32(24) },
+ { input: u32Bits(0b00000001111111111111111111111111), expected: u32(25) },
+ { input: u32Bits(0b00000011111111111111111111111111), expected: u32(26) },
+ { input: u32Bits(0b00000111111111111111111111111111), expected: u32(27) },
+ { input: u32Bits(0b00001111111111111111111111111111), expected: u32(28) },
+ { input: u32Bits(0b00011111111111111111111111111111), expected: u32(29) },
+ { input: u32Bits(0b00111111111111111111111111111111), expected: u32(30) },
+ { input: u32Bits(0b01111111111111111111111111111111), expected: u32(31) },
+ { input: u32Bits(0b11111111111111111111111111111111), expected: u32(32) },
+
+ // random after leading 1
+ { input: u32Bits(0b00000000000000000000000000000110), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000001101), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000011101), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000111001), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000001101111), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000000111101111), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000011111110001), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000111011011101), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000001101101111111), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000011111111011111), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000101111001110101), expected: u32(10) },
+ { input: u32Bits(0b00000000000000001101111011110111), expected: u32(13) },
+ { input: u32Bits(0b00000000000000011111111111110011), expected: u32(15) },
+ { input: u32Bits(0b00000000000000111111111110111111), expected: u32(17) },
+ { input: u32Bits(0b00000000000001111111011111111111), expected: u32(18) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32(20) },
+ { input: u32Bits(0b00000000000111110101011110111111), expected: u32(17) },
+ { input: u32Bits(0b00000000001111101111111111110111), expected: u32(20) },
+ { input: u32Bits(0b00000000011111111111010000101111), expected: u32(17) },
+ { input: u32Bits(0b00000000111111111111001111111011), expected: u32(21) },
+ { input: u32Bits(0b00000001111111011111101111111111), expected: u32(23) },
+ { input: u32Bits(0b00000011101011111011110111111011), expected: u32(21) },
+ { input: u32Bits(0b00000111111110111111111111111111), expected: u32(26) },
+ { input: u32Bits(0b00001111000000011011011110111111), expected: u32(18) },
+ { input: u32Bits(0b00011110101111011111111111111111), expected: u32(26) },
+ { input: u32Bits(0b00110110111111100111111110111101), expected: u32(24) },
+ { input: u32Bits(0b01010111111101111111011111011111), expected: u32(26) },
+ { input: u32Bits(0b11100010011110101101101110101111), expected: u32(21) },
+ ]);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`i32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ await run(t, builtin('countOneBits'), [TypeI32], TypeI32, cfg, [
+ // Zero
+ { input: i32Bits(0b00000000000000000000000000000000), expected: i32(0) },
+
+ // One
+ { input: i32Bits(0b00000000000000000000000000000001), expected: i32(1) },
+
+ // 0's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000010), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000000100), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000001000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000010000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000100000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000001000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000010000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000100000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000001000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000010000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000100000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000001000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000010000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000100000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000001000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000010000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000100000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000001000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000010000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000100000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000001000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000010000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000100000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000001000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000010000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000100000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00001000000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00010000000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00100000000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b01000000000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b10000000000000000000000000000000), expected: i32(1) },
+
+ // 1's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000011), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000000111), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000001111), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000011111), expected: i32(5) },
+ { input: i32Bits(0b00000000000000000000000000111111), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000001111111), expected: i32(7) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000000111111111), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000011111111111), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000000111111111111), expected: i32(12) },
+ { input: i32Bits(0b00000000000000000001111111111111), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000011111111111111), expected: i32(14) },
+ { input: i32Bits(0b00000000000000000111111111111111), expected: i32(15) },
+ { input: i32Bits(0b00000000000000001111111111111111), expected: i32(16) },
+ { input: i32Bits(0b00000000000000011111111111111111), expected: i32(17) },
+ { input: i32Bits(0b00000000000000111111111111111111), expected: i32(18) },
+ { input: i32Bits(0b00000000000001111111111111111111), expected: i32(19) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32(20) },
+ { input: i32Bits(0b00000000000111111111111111111111), expected: i32(21) },
+ { input: i32Bits(0b00000000001111111111111111111111), expected: i32(22) },
+ { input: i32Bits(0b00000000011111111111111111111111), expected: i32(23) },
+ { input: i32Bits(0b00000000111111111111111111111111), expected: i32(24) },
+ { input: i32Bits(0b00000001111111111111111111111111), expected: i32(25) },
+ { input: i32Bits(0b00000011111111111111111111111111), expected: i32(26) },
+ { input: i32Bits(0b00000111111111111111111111111111), expected: i32(27) },
+ { input: i32Bits(0b00001111111111111111111111111111), expected: i32(28) },
+ { input: i32Bits(0b00011111111111111111111111111111), expected: i32(29) },
+ { input: i32Bits(0b00111111111111111111111111111111), expected: i32(30) },
+ { input: i32Bits(0b01111111111111111111111111111111), expected: i32(31) },
+ { input: i32Bits(0b11111111111111111111111111111111), expected: i32(32) },
+
+ // random after leading 1
+ { input: i32Bits(0b00000000000000000000000000000110), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000001101), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000011101), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000111001), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000001101111), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000000111101111), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000011111110001), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000111011011101), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000001101101111111), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000011111111011111), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000101111001110101), expected: i32(10) },
+ { input: i32Bits(0b00000000000000001101111011110111), expected: i32(13) },
+ { input: i32Bits(0b00000000000000011111111111110011), expected: i32(15) },
+ { input: i32Bits(0b00000000000000111111111110111111), expected: i32(17) },
+ { input: i32Bits(0b00000000000001111111011111111111), expected: i32(18) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32(20) },
+ { input: i32Bits(0b00000000000111110101011110111111), expected: i32(17) },
+ { input: i32Bits(0b00000000001111101111111111110111), expected: i32(20) },
+ { input: i32Bits(0b00000000011111111111010000101111), expected: i32(17) },
+ { input: i32Bits(0b00000000111111111111001111111011), expected: i32(21) },
+ { input: i32Bits(0b00000001111111011111101111111111), expected: i32(23) },
+ { input: i32Bits(0b00000011101011111011110111111011), expected: i32(21) },
+ { input: i32Bits(0b00000111111110111111111111111111), expected: i32(26) },
+ { input: i32Bits(0b00001111000000011011011110111111), expected: i32(18) },
+ { input: i32Bits(0b00011110101111011111111111111111), expected: i32(26) },
+ { input: i32Bits(0b00110110111111100111111110111101), expected: i32(24) },
+ { input: i32Bits(0b01010111111101111111011111011111), expected: i32(26) },
+ { input: i32Bits(0b11100010011110101101101110101111), expected: i32(21) },
+ ]);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts
new file mode 100644
index 0000000000..d0b3198f49
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts
@@ -0,0 +1,250 @@
+export const description = `
+Execution tests for the 'countTrailingZeros' builtin function
+
+S is i32 or u32
+T is S or vecN<S>
+@const fn countTrailingZeros(e: T ) -> T
+The number of consecutive 0 bits starting from the least significant bit of e,
+when T is a scalar type.
+Component-wise when T is a vector.
+Also known as "ctz" in some languages.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`u32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ await run(t, builtin('countTrailingZeros'), [TypeU32], TypeU32, cfg, [
+ // Zero
+ { input: u32Bits(0b00000000000000000000000000000000), expected: u32(32) },
+
+ // High bit
+ { input: u32Bits(0b10000000000000000000000000000000), expected: u32(31) },
+
+ // 0's before trailing 1
+ { input: u32Bits(0b00000000000000000000000000000001), expected: u32(0) },
+ { input: u32Bits(0b00000000000000000000000000000010), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000000100), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000001000), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000010000), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000100000), expected: u32(5) },
+ { input: u32Bits(0b00000000000000000000000001000000), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000010000000), expected: u32(7) },
+ { input: u32Bits(0b00000000000000000000000100000000), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000001000000000), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000000010000000000), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000100000000000), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000001000000000000), expected: u32(12) },
+ { input: u32Bits(0b00000000000000000010000000000000), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000100000000000000), expected: u32(14) },
+ { input: u32Bits(0b00000000000000001000000000000000), expected: u32(15) },
+ { input: u32Bits(0b00000000000000010000000000000000), expected: u32(16) },
+ { input: u32Bits(0b00000000000000100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b00000000000001000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b00000000000010000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b00000000000100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b00000000001000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b00000000010000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b00000000100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b00000001000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b00000010000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b00000100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b00001000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b00010000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b00100000000000000000000000000000), expected: u32(29) },
+ { input: u32Bits(0b01000000000000000000000000000000), expected: u32(30) },
+
+ // 1's before trailing 1
+ { input: u32Bits(0b11111111111111111111111111111111), expected: u32(0) },
+ { input: u32Bits(0b11111111111111111111111111111110), expected: u32(1) },
+ { input: u32Bits(0b11111111111111111111111111111100), expected: u32(2) },
+ { input: u32Bits(0b11111111111111111111111111111000), expected: u32(3) },
+ { input: u32Bits(0b11111111111111111111111111110000), expected: u32(4) },
+ { input: u32Bits(0b11111111111111111111111111100000), expected: u32(5) },
+ { input: u32Bits(0b11111111111111111111111111000000), expected: u32(6) },
+ { input: u32Bits(0b11111111111111111111111110000000), expected: u32(7) },
+ { input: u32Bits(0b11111111111111111111111100000000), expected: u32(8) },
+ { input: u32Bits(0b11111111111111111111111000000000), expected: u32(9) },
+ { input: u32Bits(0b11111111111111111111110000000000), expected: u32(10) },
+ { input: u32Bits(0b11111111111111111111100000000000), expected: u32(11) },
+ { input: u32Bits(0b11111111111111111111000000000000), expected: u32(12) },
+ { input: u32Bits(0b11111111111111111110000000000000), expected: u32(13) },
+ { input: u32Bits(0b11111111111111111100000000000000), expected: u32(14) },
+ { input: u32Bits(0b11111111111111111000000000000000), expected: u32(15) },
+ { input: u32Bits(0b11111111111111110000000000000000), expected: u32(16) },
+ { input: u32Bits(0b11111111111111100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b11111111111111000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b11111111111110000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b11111111111100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b11111111111000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b11111111110000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b11111111100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b11111111000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b11111110000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b11111100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b11111000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b11110000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b11100000000000000000000000000000), expected: u32(29) },
+ { input: u32Bits(0b11000000000000000000000000000000), expected: u32(30) },
+
+ // random before trailing 1
+ { input: u32Bits(0b11110000001111111101111010001111), expected: u32(0) },
+ { input: u32Bits(0b11011110111111100101110011110010), expected: u32(1) },
+ { input: u32Bits(0b11110111011011111111010000111100), expected: u32(2) },
+ { input: u32Bits(0b11010011011101111111010011101000), expected: u32(3) },
+ { input: u32Bits(0b11010111110111110001111110110000), expected: u32(4) },
+ { input: u32Bits(0b11111101111101111110101111100000), expected: u32(5) },
+ { input: u32Bits(0b11111001111011111001111011000000), expected: u32(6) },
+ { input: u32Bits(0b11001110110111110111111010000000), expected: u32(7) },
+ { input: u32Bits(0b11101111011111101110101100000000), expected: u32(8) },
+ { input: u32Bits(0b11111101111011111111111000000000), expected: u32(9) },
+ { input: u32Bits(0b10011111011101110110110000000000), expected: u32(10) },
+ { input: u32Bits(0b11111111101101111011100000000000), expected: u32(11) },
+ { input: u32Bits(0b11111011010110111011000000000000), expected: u32(12) },
+ { input: u32Bits(0b00111101010000111010000000000000), expected: u32(13) },
+ { input: u32Bits(0b11111011110001101100000000000000), expected: u32(14) },
+ { input: u32Bits(0b10111111010111111000000000000000), expected: u32(15) },
+ { input: u32Bits(0b11011101111010110000000000000000), expected: u32(16) },
+ { input: u32Bits(0b01110100110110100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b11100111001011000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b11111001110110000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b00110100100100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b11111010011000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b00000010110000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b11100111100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b00101101000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b11011010000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b11010100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b10111000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b01110000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b10100000000000000000000000000000), expected: u32(29) },
+ ]);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`i32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ await run(t, builtin('countTrailingZeros'), [TypeI32], TypeI32, cfg, [
+ // Zero
+ { input: i32Bits(0b00000000000000000000000000000000), expected: i32(32) },
+
+ // High bit
+ { input: i32Bits(0b10000000000000000000000000000000), expected: i32(31) },
+
+ // 0's before trailing 1
+ { input: i32Bits(0b00000000000000000000000000000001), expected: i32(0) },
+ { input: i32Bits(0b00000000000000000000000000000010), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000000100), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000001000), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000010000), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000100000), expected: i32(5) },
+ { input: i32Bits(0b00000000000000000000000001000000), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000010000000), expected: i32(7) },
+ { input: i32Bits(0b00000000000000000000000100000000), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000001000000000), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000000010000000000), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000100000000000), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000001000000000000), expected: i32(12) },
+ { input: i32Bits(0b00000000000000000010000000000000), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000100000000000000), expected: i32(14) },
+ { input: i32Bits(0b00000000000000001000000000000000), expected: i32(15) },
+ { input: i32Bits(0b00000000000000010000000000000000), expected: i32(16) },
+ { input: i32Bits(0b00000000000000100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b00000000000001000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b00000000000010000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b00000000000100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b00000000001000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b00000000010000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b00000000100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b00000001000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b00000010000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b00000100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b00001000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b00010000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b00100000000000000000000000000000), expected: i32(29) },
+ { input: i32Bits(0b01000000000000000000000000000000), expected: i32(30) },
+
+ // 1's before trailing 1
+ { input: i32Bits(0b11111111111111111111111111111111), expected: i32(0) },
+ { input: i32Bits(0b11111111111111111111111111111110), expected: i32(1) },
+ { input: i32Bits(0b11111111111111111111111111111100), expected: i32(2) },
+ { input: i32Bits(0b11111111111111111111111111111000), expected: i32(3) },
+ { input: i32Bits(0b11111111111111111111111111110000), expected: i32(4) },
+ { input: i32Bits(0b11111111111111111111111111100000), expected: i32(5) },
+ { input: i32Bits(0b11111111111111111111111111000000), expected: i32(6) },
+ { input: i32Bits(0b11111111111111111111111110000000), expected: i32(7) },
+ { input: i32Bits(0b11111111111111111111111100000000), expected: i32(8) },
+ { input: i32Bits(0b11111111111111111111111000000000), expected: i32(9) },
+ { input: i32Bits(0b11111111111111111111110000000000), expected: i32(10) },
+ { input: i32Bits(0b11111111111111111111100000000000), expected: i32(11) },
+ { input: i32Bits(0b11111111111111111111000000000000), expected: i32(12) },
+ { input: i32Bits(0b11111111111111111110000000000000), expected: i32(13) },
+ { input: i32Bits(0b11111111111111111100000000000000), expected: i32(14) },
+ { input: i32Bits(0b11111111111111111000000000000000), expected: i32(15) },
+ { input: i32Bits(0b11111111111111110000000000000000), expected: i32(16) },
+ { input: i32Bits(0b11111111111111100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b11111111111111000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b11111111111110000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b11111111111100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b11111111111000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b11111111110000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b11111111100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b11111111000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b11111110000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b11111100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b11111000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b11110000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b11100000000000000000000000000000), expected: i32(29) },
+ { input: i32Bits(0b11000000000000000000000000000000), expected: i32(30) },
+
+ // random before trailing 1
+ { input: i32Bits(0b11110000001111111101111010001111), expected: i32(0) },
+ { input: i32Bits(0b11011110111111100101110011110010), expected: i32(1) },
+ { input: i32Bits(0b11110111011011111111010000111100), expected: i32(2) },
+ { input: i32Bits(0b11010011011101111111010011101000), expected: i32(3) },
+ { input: i32Bits(0b11010111110111110001111110110000), expected: i32(4) },
+ { input: i32Bits(0b11111101111101111110101111100000), expected: i32(5) },
+ { input: i32Bits(0b11111001111011111001111011000000), expected: i32(6) },
+ { input: i32Bits(0b11001110110111110111111010000000), expected: i32(7) },
+ { input: i32Bits(0b11101111011111101110101100000000), expected: i32(8) },
+ { input: i32Bits(0b11111101111011111111111000000000), expected: i32(9) },
+ { input: i32Bits(0b10011111011101110110110000000000), expected: i32(10) },
+ { input: i32Bits(0b11111111101101111011100000000000), expected: i32(11) },
+ { input: i32Bits(0b11111011010110111011000000000000), expected: i32(12) },
+ { input: i32Bits(0b00111101010000111010000000000000), expected: i32(13) },
+ { input: i32Bits(0b11111011110001101100000000000000), expected: i32(14) },
+ { input: i32Bits(0b10111111010111111000000000000000), expected: i32(15) },
+ { input: i32Bits(0b11011101111010110000000000000000), expected: i32(16) },
+ { input: i32Bits(0b01110100110110100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b11100111001011000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b11111001110110000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b00110100100100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b11111010011000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b00000010110000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b11100111100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b00101101000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b11011010000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b11010100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b10111000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b01110000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b10100000000000000000000000000000), expected: i32(29) },
+ ]);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts
new file mode 100644
index 0000000000..2b0b3e58ce
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts
@@ -0,0 +1,113 @@
+export const description = `
+Execution tests for the 'cross' builtin function
+
+T is AbstractFloat, f32, or f16
+@const fn cross(e1: vec3<T> ,e2: vec3<T>) -> vec3<T>
+Returns the cross product of e1 and e2.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeF16, TypeF32, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseVectorF64Range, vectorF16Range, vectorF32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('cross', {
+ f32_const: () => {
+ return FP.f32.generateVectorPairToVectorCases(
+ vectorF32Range(3),
+ vectorF32Range(3),
+ 'finite',
+ FP.f32.crossInterval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateVectorPairToVectorCases(
+ vectorF32Range(3),
+ vectorF32Range(3),
+ 'unfiltered',
+ FP.f32.crossInterval
+ );
+ },
+ f16_const: () => {
+ return FP.f16.generateVectorPairToVectorCases(
+ vectorF16Range(3),
+ vectorF16Range(3),
+ 'finite',
+ FP.f16.crossInterval
+ );
+ },
+ f16_non_const: () => {
+ return FP.f16.generateVectorPairToVectorCases(
+ vectorF16Range(3),
+ vectorF16Range(3),
+ 'unfiltered',
+ FP.f16.crossInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateVectorPairToVectorCases(
+ sparseVectorF64Range(3),
+ sparseVectorF64Range(3),
+ 'finite',
+ FP.abstract.crossInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('cross'),
+ [TypeVec(3, TypeAbstractFloat), TypeVec(3, TypeAbstractFloat)],
+ TypeVec(3, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(
+ t,
+ builtin('cross'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
+ TypeVec(3, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(
+ t,
+ builtin('cross'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
+ TypeVec(3, TypeF16),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts
new file mode 100644
index 0000000000..f82153ffca
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts
@@ -0,0 +1,95 @@
+export const description = `
+Execution tests for the 'degrees' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<T>
+@const fn degrees(e1: T ) -> T
+Converts radians to degrees, approximating e1 × 180 ÷ π. Component-wise when T is a vector
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF16Range, fullF32Range, fullF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('degrees', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.degreesInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.degreesInterval
+ );
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.degreesInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.degreesInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ fullF64Range(),
+ 'finite',
+ FP.abstract.degreesInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('degrees'),
+ [TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('degrees'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('degrees'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts
new file mode 100644
index 0000000000..f08f4f0b6b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts
@@ -0,0 +1,137 @@
+export const description = `
+Execution tests for the 'determinant' builtin function
+
+T is AbstractFloat, f32, or f16
+@const determinant(e: matCxC<T> ) -> T
+Returns the determinant of e.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeMat } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Accuracy for determinant is only defined for e, where e is an integer and
+// |e| < quadroot(2**21) [~38],
+// due to computational complexity of calculating the general solution for 4x4,
+// so custom matrices are used.
+//
+// Note: For 2x2 and 3x3 the limits are squareroot and cuberoot instead of
+// quadroot, but using the tighter 4x4 limits for all cases for simplicity.
+const kDeterminantValues = [-38, -10, -5, -1, 0, 1, 5, 10, 38];
+
+const kDeterminantMatrixValues = {
+ 2: kDeterminantValues.map((f, idx) => [
+ [idx % 4 === 0 ? f : idx, idx % 4 === 1 ? f : -idx],
+ [idx % 4 === 2 ? f : -idx, idx % 4 === 3 ? f : idx],
+ ]),
+ 3: kDeterminantValues.map((f, idx) => [
+ [idx % 9 === 0 ? f : idx, idx % 9 === 1 ? f : -idx, idx % 9 === 2 ? f : idx],
+ [idx % 9 === 3 ? f : -idx, idx % 9 === 4 ? f : idx, idx % 9 === 5 ? f : -idx],
+ [idx % 9 === 6 ? f : idx, idx % 9 === 7 ? f : -idx, idx % 9 === 8 ? f : idx],
+ ]),
+ 4: kDeterminantValues.map((f, idx) => [
+ [
+ idx % 16 === 0 ? f : idx,
+ idx % 16 === 1 ? f : -idx,
+ idx % 16 === 2 ? f : idx,
+ idx % 16 === 3 ? f : -idx,
+ ],
+ [
+ idx % 16 === 4 ? f : -idx,
+ idx % 16 === 5 ? f : idx,
+ idx % 16 === 6 ? f : -idx,
+ idx % 16 === 7 ? f : idx,
+ ],
+ [
+ idx % 16 === 8 ? f : idx,
+ idx % 16 === 9 ? f : -idx,
+ idx % 16 === 10 ? f : idx,
+ idx % 16 === 11 ? f : -idx,
+ ],
+ [
+ idx % 16 === 12 ? f : -idx,
+ idx % 16 === 13 ? f : idx,
+ idx % 16 === 14 ? f : -idx,
+ idx % 16 === 15 ? f : idx,
+ ],
+ ]),
+};
+
+// Cases: f32_matDxD_[non_]const
+const f32_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixToScalarCases(
+ kDeterminantMatrixValues[dim],
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.determinantInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_matDxD_[non_]const
+const f16_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixToScalarCases(
+ kDeterminantMatrixValues[dim],
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.determinantInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('determinant', {
+ ...f32_cases,
+ ...f16_cases,
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('dimension', [2, 3, 4] as const))
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4] as const))
+ .fn(async t => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const'
+ ? `f32_mat${dim}x${dim}_const`
+ : `f32_mat${dim}x${dim}_non_const`
+ );
+ await run(t, builtin('determinant'), [TypeMat(dim, dim, TypeF32)], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4] as const))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const'
+ ? `f16_mat${dim}x${dim}_const`
+ : `f16_mat${dim}x${dim}_non_const`
+ );
+ await run(t, builtin('determinant'), [TypeMat(dim, dim, TypeF16)], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts
new file mode 100644
index 0000000000..13cddf6403
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts
@@ -0,0 +1,241 @@
+export const description = `
+Execution tests for the 'distance' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn distance(e1: T ,e2: T ) -> f32
+Returns the distance between e1 and e2 (e.g. length(e1-e2)).
+
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ fullF32Range,
+ fullF16Range,
+ sparseVectorF32Range,
+ sparseVectorF16Range,
+} from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_vecN_[non_]const
+const f32_vec_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorPairToIntervalCases(
+ sparseVectorF32Range(n),
+ sparseVectorF32Range(n),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.distanceInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_vecN_[non_]const
+const f16_vec_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorPairToIntervalCases(
+ sparseVectorF16Range(n),
+ sparseVectorF16Range(n),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.distanceInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('distance', {
+ f32_const: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ fullF32Range(),
+ fullF32Range(),
+ 'finite',
+ FP.f32.distanceInterval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ fullF32Range(),
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.distanceInterval
+ );
+ },
+ ...f32_vec_cases,
+ f16_const: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ fullF16Range(),
+ fullF16Range(),
+ 'finite',
+ FP.f16.distanceInterval
+ );
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ fullF16Range(),
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.distanceInterval
+ );
+ },
+ ...f16_vec_cases,
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('distance'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f32_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('distance'),
+ [TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
+ TypeF32,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('distance'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
+ TypeF32,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('distance'),
+ [TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
+ TypeF32,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('distance'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ });
+
+g.test('f16_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('distance'),
+ [TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
+ TypeF16,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('distance'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
+ TypeF16,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('distance'),
+ [TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
+ TypeF16,
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts
new file mode 100644
index 0000000000..2726546183
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts
@@ -0,0 +1,182 @@
+export const description = `
+Execution tests for the 'dot' builtin function
+
+T is AbstractInt, AbstractFloat, i32, u32, f32, or f16
+@const fn dot(e1: vecN<T>,e2: vecN<T>) -> T
+Returns the dot product of e1 and e2.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseVectorF32Range, vectorF32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: [f32|f16]_vecN_[non_]const
+const cases = (['f32', 'f16'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(N =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${N}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ // vec3 and vec4 require calculating all possible permutations, so their runtime is much
+ // longer per test, so only using sparse vectors for them.
+ return FP[trait].generateVectorPairToIntervalCases(
+ N === 2 ? vectorF32Range(2) : sparseVectorF32Range(N),
+ N === 2 ? vectorF32Range(2) : sparseVectorF32Range(N),
+ nonConst ? 'unfiltered' : 'finite',
+ FP[trait].dotInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('dot', cases);
+
+g.test('abstract_int')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract int tests`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .unimplemented();
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`i32 tests`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .unimplemented();
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`u32 tests`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .unimplemented();
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract float test`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .unimplemented();
+
+g.test('f32_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`f32 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('dot'),
+ [TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
+ TypeF32,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`f32 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('dot'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
+ TypeF32,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`f32 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('dot'),
+ [TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
+ TypeF32,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`f16 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('dot'),
+ [TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
+ TypeF16,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`f16 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('dot'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
+ TypeF16,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`f16 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('dot'),
+ [TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
+ TypeF16,
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts
new file mode 100644
index 0000000000..287a51c699
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts
@@ -0,0 +1,23 @@
+export const description = `
+Execution tests for the 'dpdx' builtin function
+
+T is f32 or vecN<f32>
+fn dpdx(e:T) -> T
+Partial derivative of e with respect to window x coordinates.
+The result is the same as either dpdxFine(e) or dpdxCoarse(e).
+
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts
new file mode 100644
index 0000000000..67a75bb010
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts
@@ -0,0 +1,22 @@
+export const description = `
+Execution tests for the 'dpdxCoarse' builtin function
+
+T is f32 or vecN<f32>
+fn dpdxCoarse(e:T) ->T
+Returns the partial derivative of e with respect to window x coordinates using local differences.
+This may result in fewer unique positions that dpdxFine(e).
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts
new file mode 100644
index 0000000000..91d65b990b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts
@@ -0,0 +1,21 @@
+export const description = `
+Execution tests for the 'dpdxFine' builtin function
+
+T is f32 or vecN<f32>
+fn dpdxFine(e:T) ->T
+Returns the partial derivative of e with respect to window x coordinates.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts
new file mode 100644
index 0000000000..0cd9cafdb9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts
@@ -0,0 +1,22 @@
+export const description = `
+Execution tests for the 'dpdy' builtin function
+
+T is f32 or vecN<f32>
+fn dpdy(e:T) ->T
+Partial derivative of e with respect to window y coordinates.
+The result is the same as either dpdyFine(e) or dpdyCoarse(e).
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts
new file mode 100644
index 0000000000..f06869fdc2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts
@@ -0,0 +1,22 @@
+export const description = `
+Execution tests for the 'dpdyCoarse' builtin function
+
+T is f32 or vecN<f32>
+fn dpdyCoarse(e:T) ->T
+Returns the partial derivative of e with respect to window y coordinates using local differences.
+This may result in fewer unique positions that dpdyFine(e).
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(`f32 test`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts
new file mode 100644
index 0000000000..e09761de95
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts
@@ -0,0 +1,21 @@
+export const description = `
+Execution tests for the 'dpdyFine' builtin function
+
+T is f32 or vecN<f32>
+fn dpdyFine(e:T) ->T
+Returns the partial derivative of e with respect to window y coordinates.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts
new file mode 100644
index 0000000000..8b1ced3cab
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts
@@ -0,0 +1,90 @@
+export const description = `
+Execution tests for the 'exp' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn exp(e1: T ) -> T
+Returns the natural exponentiation of e1 (e.g. e^e1). Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// floor(ln(max f32 value)) = 88, so exp(88) will be within range of a f32, but exp(89) will not
+// floor(ln(max f64 value)) = 709, so exp(709) can be handled by the testing framework, but exp(710) will misbehave
+const f32_inputs = [
+ 0, // Returns 1 by definition
+ -89, // Returns subnormal value
+ kValue.f32.negative.min, // Closest to returning 0 as possible
+ ...biasedRange(kValue.f32.negative.max, -88, 100),
+ ...biasedRange(kValue.f32.positive.min, 88, 100),
+ ...linearRange(89, 709, 10), // Overflows f32, but not f64
+];
+
+// floor(ln(max f16 value)) = 11, so exp(11) will be within range of a f16, but exp(12) will not
+const f16_inputs = [
+ 0, // Returns 1 by definition
+ -12, // Returns subnormal value
+ kValue.f16.negative.min, // Closest to returning 0 as possible
+ ...biasedRange(kValue.f16.negative.max, -11, 100),
+ ...biasedRange(kValue.f16.positive.min, 11, 100),
+ ...linearRange(12, 709, 10), // Overflows f16, but not f64
+];
+
+export const d = makeCaseCache('exp', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.expInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.expInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.expInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.expInterval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('exp'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('exp'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts
new file mode 100644
index 0000000000..67e123cb30
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts
@@ -0,0 +1,90 @@
+export const description = `
+Execution tests for the 'exp2' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn exp2(e: T ) -> T
+Returns 2 raised to the power e (e.g. 2^e). Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// floor(log2(max f32 value)) = 127, so exp2(127) will be within range of a f32, but exp2(128) will not
+// floor(ln(max f64 value)) = 1023, so exp2(1023) can be handled by the testing framework, but exp2(1024) will misbehave
+const f32_inputs = [
+ 0, // Returns 1 by definition
+ -128, // Returns subnormal value
+ kValue.f32.negative.min, // Closest to returning 0 as possible
+ ...biasedRange(kValue.f32.negative.max, -127, 100),
+ ...biasedRange(kValue.f32.positive.min, 127, 100),
+ ...linearRange(128, 1023, 10), // Overflows f32, but not f64
+];
+
+// floor(log2(max f16 value)) = 15, so exp2(15) will be within range of a f16, but exp2(15) will not
+const f16_inputs = [
+ 0, // Returns 1 by definition
+ -16, // Returns subnormal value
+ kValue.f16.negative.min, // Closest to returning 0 as possible
+ ...biasedRange(kValue.f16.negative.max, -15, 100),
+ ...biasedRange(kValue.f16.positive.min, 15, 100),
+ ...linearRange(16, 1023, 10), // Overflows f16, but not f64
+];
+
+export const d = makeCaseCache('exp2', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.exp2Interval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.exp2Interval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.exp2Interval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.exp2Interval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('exp2'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('exp2'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts
new file mode 100644
index 0000000000..d535bf5d74
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts
@@ -0,0 +1,337 @@
+export const description = `
+Execution tests for the 'extractBits' builtin function
+
+T is u32 or vecN<u32>
+@const fn extractBits(e: T, offset: u32, count: u32) -> T
+Reads bits from an integer, without sign extension.
+
+When T is a scalar type, then:
+ w is the bit width of T
+ o = min(offset,w)
+ c = min(count, w - o)
+
+The result is 0 if c is 0.
+Otherwise, bits 0..c-1 of the result are copied from bits o..o+c-1 of e.
+Other bits of the result are 0.
+Component-wise when T is a vector.
+
+
+T is i32 or vecN<i32>
+@const fn extractBits(e: T, offset: u32, count: u32) -> T
+Reads bits from an integer, with sign extension.
+
+When T is a scalar type, then:
+ w is the bit width of T
+ o = min(offset,w)
+ c = min(count, w - o)
+
+The result is 0 if c is 0.
+Otherwise, bits 0..c-1 of the result are copied from bits o..o+c-1 of e.
+Other bits of the result are the same as bit c-1 of the result.
+Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ i32Bits,
+ TypeI32,
+ u32,
+ TypeU32,
+ u32Bits,
+ vec2,
+ vec3,
+ vec4,
+ TypeVec,
+} from '../../../../../util/conversion.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`u32 tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('width', [1, 2, 3, 4]))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const T = t.params.width === 1 ? TypeU32 : TypeVec(t.params.width, TypeU32);
+
+ const V = (x: number, y?: number, z?: number, w?: number) => {
+ y = y === undefined ? x : y;
+ z = z === undefined ? x : z;
+ w = w === undefined ? x : w;
+
+ switch (t.params.width) {
+ case 1:
+ return u32Bits(x);
+ case 2:
+ return vec2(u32Bits(x), u32Bits(y));
+ case 3:
+ return vec3(u32Bits(x), u32Bits(y), u32Bits(z));
+ default:
+ return vec4(u32Bits(x), u32Bits(y), u32Bits(z), u32Bits(w));
+ }
+ };
+
+ const all_1 = V(0b11111111111111111111111111111111);
+ const all_0 = V(0b00000000000000000000000000000000);
+ const low_1 = V(0b00000000000000000000000000000001);
+ const high_1 = V(0b10000000000000000000000000000000);
+ const pattern = V(
+ 0b00000000000111011100000000000000,
+ 0b11111111111000000011111111111111,
+ 0b00000000010101010101000000000000,
+ 0b00000000001010101010100000000000
+ );
+
+ const cases = [
+ { input: [all_0, u32(0), u32(32)], expected: all_0 },
+ { input: [all_0, u32(1), u32(10)], expected: all_0 },
+ { input: [all_0, u32(2), u32(5)], expected: all_0 },
+ { input: [all_0, u32(0), u32(1)], expected: all_0 },
+ { input: [all_0, u32(31), u32(1)], expected: all_0 },
+
+ { input: [all_1, u32(0), u32(32)], expected: all_1 },
+ {
+ input: [all_1, u32(1), u32(10)],
+ expected: V(0b00000000000000000000001111111111),
+ },
+ {
+ input: [all_1, u32(2), u32(5)],
+ expected: V(0b00000000000000000000000000011111),
+ },
+ { input: [all_1, u32(0), u32(1)], expected: low_1 },
+ { input: [all_1, u32(31), u32(1)], expected: low_1 },
+
+ // Patterns
+ { input: [pattern, u32(0), u32(32)], expected: pattern },
+ {
+ input: [pattern, u32(1), u32(31)],
+ expected: V(
+ 0b00000000000011101110000000000000,
+ 0b01111111111100000001111111111111,
+ 0b00000000001010101010100000000000,
+ 0b00000000000101010101010000000000
+ ),
+ },
+ {
+ input: [pattern, u32(14), u32(18)],
+ expected: V(
+ 0b00000000000000000000000001110111,
+ 0b00000000000000111111111110000000,
+ 0b00000000000000000000000101010101,
+ 0b00000000000000000000000010101010
+ ),
+ },
+ {
+ input: [pattern, u32(14), u32(7)],
+ expected: V(
+ 0b00000000000000000000000001110111,
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000001010101,
+ 0b00000000000000000000000000101010
+ ),
+ },
+ {
+ input: [pattern, u32(14), u32(4)],
+ expected: V(
+ 0b00000000000000000000000000000111,
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000000000101,
+ 0b00000000000000000000000000001010
+ ),
+ },
+ {
+ input: [pattern, u32(14), u32(3)],
+ expected: V(
+ 0b00000000000000000000000000000111,
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000000000101,
+ 0b00000000000000000000000000000010
+ ),
+ },
+ {
+ input: [pattern, u32(18), u32(3)],
+ expected: V(
+ 0b00000000000000000000000000000111,
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000000000101,
+ 0b00000000000000000000000000000010
+ ),
+ },
+ { input: [low_1, u32(0), u32(1)], expected: low_1 },
+ { input: [high_1, u32(31), u32(1)], expected: low_1 },
+
+ // Zero count
+ { input: [all_1, u32(0), u32(0)], expected: all_0 },
+ { input: [all_0, u32(0), u32(0)], expected: all_0 },
+ { input: [low_1, u32(0), u32(0)], expected: all_0 },
+ { input: [high_1, u32(31), u32(0)], expected: all_0 },
+ { input: [pattern, u32(0), u32(0)], expected: all_0 },
+ ];
+
+ if (t.params.inputSource !== 'const') {
+ cases.push(
+ ...[
+ // End overflow
+ { input: [low_1, u32(0), u32(99)], expected: low_1 },
+ { input: [high_1, u32(31), u32(99)], expected: low_1 },
+ { input: [pattern, u32(0), u32(99)], expected: pattern },
+ {
+ input: [pattern, u32(14), u32(99)],
+ expected: V(
+ 0b00000000000000000000000001110111,
+ 0b00000000000000111111111110000000,
+ 0b00000000000000000000000101010101,
+ 0b00000000000000000000000010101010
+ ),
+ },
+ ]
+ );
+ }
+
+ await run(t, builtin('extractBits'), [T, TypeU32, TypeU32], T, cfg, cases);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`i32 tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('width', [1, 2, 3, 4]))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const T = t.params.width === 1 ? TypeI32 : TypeVec(t.params.width, TypeI32);
+
+ const V = (x: number, y?: number, z?: number, w?: number) => {
+ y = y === undefined ? x : y;
+ z = z === undefined ? x : z;
+ w = w === undefined ? x : w;
+
+ switch (t.params.width) {
+ case 1:
+ return i32Bits(x);
+ case 2:
+ return vec2(i32Bits(x), i32Bits(y));
+ case 3:
+ return vec3(i32Bits(x), i32Bits(y), i32Bits(z));
+ default:
+ return vec4(i32Bits(x), i32Bits(y), i32Bits(z), i32Bits(w));
+ }
+ };
+
+ const all_1 = V(0b11111111111111111111111111111111);
+ const all_0 = V(0b00000000000000000000000000000000);
+ const low_1 = V(0b00000000000000000000000000000001);
+ const high_1 = V(0b10000000000000000000000000000000);
+ const pattern = V(
+ 0b00000000000111011100000000000000,
+ 0b11111111111000000011111111111111,
+ 0b00000000010101010101000000000000,
+ 0b00000000001010101010100000000000
+ );
+
+ const cases = [
+ { input: [all_0, u32(0), u32(32)], expected: all_0 },
+ { input: [all_0, u32(1), u32(10)], expected: all_0 },
+ { input: [all_0, u32(2), u32(5)], expected: all_0 },
+ { input: [all_0, u32(0), u32(1)], expected: all_0 },
+ { input: [all_0, u32(31), u32(1)], expected: all_0 },
+
+ { input: [all_1, u32(0), u32(32)], expected: all_1 },
+ { input: [all_1, u32(1), u32(10)], expected: all_1 },
+ { input: [all_1, u32(2), u32(5)], expected: all_1 },
+ { input: [all_1, u32(0), u32(1)], expected: all_1 },
+ { input: [all_1, u32(31), u32(1)], expected: all_1 },
+
+ // Patterns
+ { input: [pattern, u32(0), u32(32)], expected: pattern },
+ {
+ input: [pattern, u32(1), u32(31)],
+ expected: V(
+ 0b00000000000011101110000000000000,
+ 0b11111111111100000001111111111111,
+ 0b00000000001010101010100000000000,
+ 0b00000000000101010101010000000000
+ ),
+ },
+ {
+ input: [pattern, u32(14), u32(18)],
+ expected: V(
+ 0b00000000000000000000000001110111,
+ 0b11111111111111111111111110000000,
+ 0b00000000000000000000000101010101,
+ 0b00000000000000000000000010101010
+ ),
+ },
+ {
+ input: [pattern, u32(14), u32(7)],
+ expected: V(
+ 0b11111111111111111111111111110111,
+ 0b00000000000000000000000000000000,
+ 0b11111111111111111111111111010101,
+ 0b00000000000000000000000000101010
+ ),
+ },
+ {
+ input: [pattern, u32(14), u32(4)],
+ expected: V(
+ 0b00000000000000000000000000000111,
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000000000101,
+ 0b11111111111111111111111111111010
+ ),
+ },
+ {
+ input: [pattern, u32(14), u32(3)],
+ expected: V(
+ 0b11111111111111111111111111111111,
+ 0b00000000000000000000000000000000,
+ 0b11111111111111111111111111111101,
+ 0b00000000000000000000000000000010
+ ),
+ },
+ {
+ input: [pattern, u32(18), u32(3)],
+ expected: V(
+ 0b11111111111111111111111111111111,
+ 0b00000000000000000000000000000000,
+ 0b11111111111111111111111111111101,
+ 0b00000000000000000000000000000010
+ ),
+ },
+ { input: [low_1, u32(0), u32(1)], expected: all_1 },
+ { input: [high_1, u32(31), u32(1)], expected: all_1 },
+
+ // Zero count
+ { input: [all_1, u32(0), u32(0)], expected: all_0 },
+ { input: [all_0, u32(0), u32(0)], expected: all_0 },
+ { input: [low_1, u32(0), u32(0)], expected: all_0 },
+ { input: [high_1, u32(31), u32(0)], expected: all_0 },
+ { input: [pattern, u32(0), u32(0)], expected: all_0 },
+ ];
+
+ if (t.params.inputSource !== 'const') {
+ cases.push(
+ ...[
+ // End overflow
+ { input: [low_1, u32(0), u32(99)], expected: low_1 },
+ { input: [high_1, u32(31), u32(99)], expected: all_1 },
+ { input: [pattern, u32(0), u32(99)], expected: pattern },
+ {
+ input: [pattern, u32(14), u32(99)],
+ expected: V(
+ 0b00000000000000000000000001110111,
+ 0b11111111111111111111111110000000,
+ 0b00000000000000000000000101010101,
+ 0b00000000000000000000000010101010
+ ),
+ },
+ ]
+ );
+ }
+
+ await run(t, builtin('extractBits'), [T, TypeU32, TypeU32], T, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts
new file mode 100644
index 0000000000..6b6794fb9f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts
@@ -0,0 +1,256 @@
+export const description = `
+Execution tests for the 'faceForward' builtin function
+
+T is vecN<AbstractFloat>, vecN<f32>, or vecN<f16>
+@const fn faceForward(e1: T ,e2: T ,e3: T ) -> T
+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';
+import { FP, FPKind, FPVector } from '../../../../../util/floating_point.js';
+import {
+ cartesianProduct,
+ sparseVectorF32Range,
+ sparseVectorF16Range,
+} from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, Case, IntervalFilter, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Using a bespoke implementation of make*Case and generate*Cases here
+// since faceForwardIntervals is the only builtin with the API signature
+// (vec, vec, vec) -> vec
+//
+// Additionally faceForward has significant complexities around it due to the
+// fact that `dot` is calculated in it s operation, but the result of dot isn't
+// used to calculate the builtin's result.
+
+/**
+ * @returns a Case for `faceForward`
+ * @param kind what kind of floating point numbers being operated on
+ * @param x the `x` param for the case
+ * @param y the `y` param for the case
+ * @param z the `z` param for the case
+ * @param check what interval checking to apply
+ * */
+function makeCase(
+ kind: FPKind,
+ x: readonly number[],
+ y: readonly number[],
+ z: readonly number[],
+ check: IntervalFilter
+): Case | undefined {
+ const fp = FP[kind];
+ x = x.map(fp.quantize);
+ y = y.map(fp.quantize);
+ z = z.map(fp.quantize);
+
+ const results = FP[kind].faceForwardIntervals(x, y, z);
+ if (check === 'finite' && results.some(r => r === undefined)) {
+ return undefined;
+ }
+
+ // Stripping the undefined results, since undefined is used to signal that an OOB
+ // could occur within the calculation that isn't reflected in the result
+ // intervals.
+ const define_results = results.filter((r): r is FPVector => r !== undefined);
+
+ return {
+ input: [
+ toVector(x, fp.scalarBuilder),
+ toVector(y, fp.scalarBuilder),
+ toVector(z, fp.scalarBuilder),
+ ],
+ expected: anyOf(...define_results),
+ };
+}
+
+/**
+ * @returns an array of Cases for `faceForward`
+ * @param kind what kind of floating point numbers being operated on
+ * @param xs array of inputs to try for the `x` param
+ * @param ys array of inputs to try for the `y` param
+ * @param zs array of inputs to try for the `z` param
+ * @param check what interval checking to apply
+ */
+function generateCases(
+ kind: FPKind,
+ xs: ROArrayArray<number>,
+ ys: ROArrayArray<number>,
+ zs: ROArrayArray<number>,
+ check: IntervalFilter
+): Case[] {
+ // Cannot use `cartesianProduct` here due to heterogeneous param types
+ return cartesianProduct(xs, ys, zs)
+ .map(e => makeCase(kind, e[0], e[1], e[2], check))
+ .filter((c): c is Case => c !== undefined);
+}
+
+// Cases: f32_vecN_[non_]const
+const f32_vec_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return generateCases(
+ 'f32',
+ sparseVectorF32Range(n),
+ sparseVectorF32Range(n),
+ sparseVectorF32Range(n),
+ nonConst ? 'unfiltered' : 'finite'
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_vecN_[non_]const
+const f16_vec_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return generateCases(
+ 'f16',
+ sparseVectorF16Range(n),
+ sparseVectorF16Range(n),
+ sparseVectorF16Range(n),
+ nonConst ? 'unfiltered' : 'finite'
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('faceForward', {
+ ...f32_vec_cases,
+ ...f16_vec_cases,
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const))
+ .unimplemented();
+
+g.test('f32_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('faceForward'),
+ [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
+ TypeVec(2, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('faceForward'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
+ TypeVec(3, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('faceForward'),
+ [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
+ TypeVec(4, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('faceForward'),
+ [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('faceForward'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
+ TypeVec(3, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('faceForward'),
+ [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts
new file mode 100644
index 0000000000..26216563cd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts
@@ -0,0 +1,350 @@
+export const description = `
+Execution tests for the 'firstLeadingBit' builtin function
+
+T is u32 or vecN<u32>
+@const fn firstLeadingBit(e: T ) -> T
+For scalar T, the result is: T(-1) if e is zero.
+Otherwise the position of the most significant 1 bit in e.
+Component-wise when T is a vector.
+
+T is i32 or vecN<i32>
+@const fn firstLeadingBit(e: T ) -> T
+For scalar T, the result is: -1 if e is 0 or -1.
+Otherwise the position of the most significant bit in e that is different from e’s sign bit.
+Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`u32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ await run(t, builtin('firstLeadingBit'), [TypeU32], TypeU32, cfg, [
+ // Zero
+ { input: u32Bits(0b00000000000000000000000000000000), expected: u32(-1) },
+
+ // One
+ { input: u32Bits(0b00000000000000000000000000000001), expected: u32(0) },
+
+ // 0's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000010), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000000100), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000001000), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000010000), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000100000), expected: u32(5) },
+ { input: u32Bits(0b00000000000000000000000001000000), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000010000000), expected: u32(7) },
+ { input: u32Bits(0b00000000000000000000000100000000), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000001000000000), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000000010000000000), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000100000000000), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000001000000000000), expected: u32(12) },
+ { input: u32Bits(0b00000000000000000010000000000000), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000100000000000000), expected: u32(14) },
+ { input: u32Bits(0b00000000000000001000000000000000), expected: u32(15) },
+ { input: u32Bits(0b00000000000000010000000000000000), expected: u32(16) },
+ { input: u32Bits(0b00000000000000100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b00000000000001000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b00000000000010000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b00000000000100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b00000000001000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b00000000010000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b00000000100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b00000001000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b00000010000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b00000100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b00001000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b00010000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b00100000000000000000000000000000), expected: u32(29) },
+ { input: u32Bits(0b01000000000000000000000000000000), expected: u32(30) },
+ { input: u32Bits(0b10000000000000000000000000000000), expected: u32(31) },
+
+ // 1's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000011), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000000111), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000001111), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000011111), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000111111), expected: u32(5) },
+ { input: u32Bits(0b00000000000000000000000001111111), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32(7) },
+ { input: u32Bits(0b00000000000000000000000111111111), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000000011111111111), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000111111111111), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000001111111111111), expected: u32(12) },
+ { input: u32Bits(0b00000000000000000011111111111111), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000111111111111111), expected: u32(14) },
+ { input: u32Bits(0b00000000000000001111111111111111), expected: u32(15) },
+ { input: u32Bits(0b00000000000000011111111111111111), expected: u32(16) },
+ { input: u32Bits(0b00000000000000111111111111111111), expected: u32(17) },
+ { input: u32Bits(0b00000000000001111111111111111111), expected: u32(18) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32(19) },
+ { input: u32Bits(0b00000000000111111111111111111111), expected: u32(20) },
+ { input: u32Bits(0b00000000001111111111111111111111), expected: u32(21) },
+ { input: u32Bits(0b00000000011111111111111111111111), expected: u32(22) },
+ { input: u32Bits(0b00000000111111111111111111111111), expected: u32(23) },
+ { input: u32Bits(0b00000001111111111111111111111111), expected: u32(24) },
+ { input: u32Bits(0b00000011111111111111111111111111), expected: u32(25) },
+ { input: u32Bits(0b00000111111111111111111111111111), expected: u32(26) },
+ { input: u32Bits(0b00001111111111111111111111111111), expected: u32(27) },
+ { input: u32Bits(0b00011111111111111111111111111111), expected: u32(28) },
+ { input: u32Bits(0b00111111111111111111111111111111), expected: u32(29) },
+ { input: u32Bits(0b01111111111111111111111111111111), expected: u32(30) },
+ { input: u32Bits(0b11111111111111111111111111111111), expected: u32(31) },
+
+ // random after leading 1
+ { input: u32Bits(0b00000000000000000000000000000110), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000001101), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000011101), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000111001), expected: u32(5) },
+ { input: u32Bits(0b00000000000000000000000001101111), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32(7) },
+ { input: u32Bits(0b00000000000000000000000111101111), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000000011111110001), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000111011011101), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000001101101111111), expected: u32(12) },
+ { input: u32Bits(0b00000000000000000011111111011111), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000101111001110101), expected: u32(14) },
+ { input: u32Bits(0b00000000000000001101111011110111), expected: u32(15) },
+ { input: u32Bits(0b00000000000000011111111111110011), expected: u32(16) },
+ { input: u32Bits(0b00000000000000111111111110111111), expected: u32(17) },
+ { input: u32Bits(0b00000000000001111111011111111111), expected: u32(18) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32(19) },
+ { input: u32Bits(0b00000000000111110101011110111111), expected: u32(20) },
+ { input: u32Bits(0b00000000001111101111111111110111), expected: u32(21) },
+ { input: u32Bits(0b00000000011111111111010000101111), expected: u32(22) },
+ { input: u32Bits(0b00000000111111111111001111111011), expected: u32(23) },
+ { input: u32Bits(0b00000001111111011111101111111111), expected: u32(24) },
+ { input: u32Bits(0b00000011101011111011110111111011), expected: u32(25) },
+ { input: u32Bits(0b00000111111110111111111111111111), expected: u32(26) },
+ { input: u32Bits(0b00001111000000011011011110111111), expected: u32(27) },
+ { input: u32Bits(0b00011110101111011111111111111111), expected: u32(28) },
+ { input: u32Bits(0b00110110111111100111111110111101), expected: u32(29) },
+ { input: u32Bits(0b01010111111101111111011111011111), expected: u32(30) },
+ { input: u32Bits(0b11100010011110101101101110101111), expected: u32(31) },
+ ]);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`i32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ await run(t, builtin('firstLeadingBit'), [TypeI32], TypeI32, cfg, [
+ // Zero
+ { input: i32Bits(0b00000000000000000000000000000000), expected: i32(-1) },
+
+ // Negative One
+ { input: i32Bits(0b11111111111111111111111111111111), expected: i32(-1) },
+
+ // One
+ { input: i32Bits(0b00000000000000000000000000000001), expected: i32(0) },
+
+ // Positive: 0's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000010), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000000100), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000001000), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000010000), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000100000), expected: i32(5) },
+ { input: i32Bits(0b00000000000000000000000001000000), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000010000000), expected: i32(7) },
+ { input: i32Bits(0b00000000000000000000000100000000), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000001000000000), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000000010000000000), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000100000000000), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000001000000000000), expected: i32(12) },
+ { input: i32Bits(0b00000000000000000010000000000000), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000100000000000000), expected: i32(14) },
+ { input: i32Bits(0b00000000000000001000000000000000), expected: i32(15) },
+ { input: i32Bits(0b00000000000000010000000000000000), expected: i32(16) },
+ { input: i32Bits(0b00000000000000100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b00000000000001000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b00000000000010000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b00000000000100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b00000000001000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b00000000010000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b00000000100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b00000001000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b00000010000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b00000100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b00001000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b00010000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b00100000000000000000000000000000), expected: i32(29) },
+ { input: i32Bits(0b01000000000000000000000000000000), expected: i32(30) },
+
+ // Negative: 0's after leading 0
+ { input: i32Bits(0b11111111111111111111111111111110), expected: i32(0) },
+ { input: i32Bits(0b11111111111111111111111111111100), expected: i32(1) },
+ { input: i32Bits(0b11111111111111111111111111111000), expected: i32(2) },
+ { input: i32Bits(0b11111111111111111111111111110000), expected: i32(3) },
+ { input: i32Bits(0b11111111111111111111111111100000), expected: i32(4) },
+ { input: i32Bits(0b11111111111111111111111111000000), expected: i32(5) },
+ { input: i32Bits(0b11111111111111111111111110000000), expected: i32(6) },
+ { input: i32Bits(0b11111111111111111111111100000000), expected: i32(7) },
+ { input: i32Bits(0b11111111111111111111111000000000), expected: i32(8) },
+ { input: i32Bits(0b11111111111111111111110000000000), expected: i32(9) },
+ { input: i32Bits(0b11111111111111111111100000000000), expected: i32(10) },
+ { input: i32Bits(0b11111111111111111111000000000000), expected: i32(11) },
+ { input: i32Bits(0b11111111111111111110000000000000), expected: i32(12) },
+ { input: i32Bits(0b11111111111111111100000000000000), expected: i32(13) },
+ { input: i32Bits(0b11111111111111111000000000000000), expected: i32(14) },
+ { input: i32Bits(0b11111111111111110000000000000000), expected: i32(15) },
+ { input: i32Bits(0b11111111111111100000000000000000), expected: i32(16) },
+ { input: i32Bits(0b11111111111111000000000000000000), expected: i32(17) },
+ { input: i32Bits(0b11111111111110000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b11111111111100000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b11111111111000000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b11111111110000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b11111111100000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b11111111000000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b11111110000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b11111100000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b11111000000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b11110000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b11100000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b11000000000000000000000000000000), expected: i32(29) },
+ { input: i32Bits(0b10000000000000000000000000000000), expected: i32(30) },
+
+ // Positive: 1's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000011), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000000111), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000001111), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000011111), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000111111), expected: i32(5) },
+ { input: i32Bits(0b00000000000000000000000001111111), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32(7) },
+ { input: i32Bits(0b00000000000000000000000111111111), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000000011111111111), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000111111111111), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000001111111111111), expected: i32(12) },
+ { input: i32Bits(0b00000000000000000011111111111111), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000111111111111111), expected: i32(14) },
+ { input: i32Bits(0b00000000000000001111111111111111), expected: i32(15) },
+ { input: i32Bits(0b00000000000000011111111111111111), expected: i32(16) },
+ { input: i32Bits(0b00000000000000111111111111111111), expected: i32(17) },
+ { input: i32Bits(0b00000000000001111111111111111111), expected: i32(18) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32(19) },
+ { input: i32Bits(0b00000000000111111111111111111111), expected: i32(20) },
+ { input: i32Bits(0b00000000001111111111111111111111), expected: i32(21) },
+ { input: i32Bits(0b00000000011111111111111111111111), expected: i32(22) },
+ { input: i32Bits(0b00000000111111111111111111111111), expected: i32(23) },
+ { input: i32Bits(0b00000001111111111111111111111111), expected: i32(24) },
+ { input: i32Bits(0b00000011111111111111111111111111), expected: i32(25) },
+ { input: i32Bits(0b00000111111111111111111111111111), expected: i32(26) },
+ { input: i32Bits(0b00001111111111111111111111111111), expected: i32(27) },
+ { input: i32Bits(0b00011111111111111111111111111111), expected: i32(28) },
+ { input: i32Bits(0b00111111111111111111111111111111), expected: i32(29) },
+ { input: i32Bits(0b01111111111111111111111111111111), expected: i32(30) },
+
+ // Negative: 1's after leading 0
+ { input: i32Bits(0b11111111111111111111111111111101), expected: i32(1) },
+ { input: i32Bits(0b11111111111111111111111111111011), expected: i32(2) },
+ { input: i32Bits(0b11111111111111111111111111110111), expected: i32(3) },
+ { input: i32Bits(0b11111111111111111111111111101111), expected: i32(4) },
+ { input: i32Bits(0b11111111111111111111111111011111), expected: i32(5) },
+ { input: i32Bits(0b11111111111111111111111110111111), expected: i32(6) },
+ { input: i32Bits(0b11111111111111111111111101111111), expected: i32(7) },
+ { input: i32Bits(0b11111111111111111111111011111111), expected: i32(8) },
+ { input: i32Bits(0b11111111111111111111110111111111), expected: i32(9) },
+ { input: i32Bits(0b11111111111111111111101111111111), expected: i32(10) },
+ { input: i32Bits(0b11111111111111111111011111111111), expected: i32(11) },
+ { input: i32Bits(0b11111111111111111110111111111111), expected: i32(12) },
+ { input: i32Bits(0b11111111111111111101111111111111), expected: i32(13) },
+ { input: i32Bits(0b11111111111111111011111111111111), expected: i32(14) },
+ { input: i32Bits(0b11111111111111110111111111111111), expected: i32(15) },
+ { input: i32Bits(0b11111111111111101111111111111111), expected: i32(16) },
+ { input: i32Bits(0b11111111111111011111111111111111), expected: i32(17) },
+ { input: i32Bits(0b11111111111110111111111111111111), expected: i32(18) },
+ { input: i32Bits(0b11111111111101111111111111111111), expected: i32(19) },
+ { input: i32Bits(0b11111111111011111111111111111111), expected: i32(20) },
+ { input: i32Bits(0b11111111110111111111111111111111), expected: i32(21) },
+ { input: i32Bits(0b11111111101111111111111111111111), expected: i32(22) },
+ { input: i32Bits(0b11111111011111111111111111111111), expected: i32(23) },
+ { input: i32Bits(0b11111110111111111111111111111111), expected: i32(24) },
+ { input: i32Bits(0b11111101111111111111111111111111), expected: i32(25) },
+ { input: i32Bits(0b11111011111111111111111111111111), expected: i32(26) },
+ { input: i32Bits(0b11110111111111111111111111111111), expected: i32(27) },
+ { input: i32Bits(0b11101111111111111111111111111111), expected: i32(28) },
+ { input: i32Bits(0b11011111111111111111111111111111), expected: i32(29) },
+ { input: i32Bits(0b10111111111111111111111111111111), expected: i32(30) },
+
+ // Positive: random after leading 1
+ { input: i32Bits(0b00000000000000000000000000000110), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000001101), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000011101), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000111001), expected: i32(5) },
+ { input: i32Bits(0b00000000000000000000000001101111), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32(7) },
+ { input: i32Bits(0b00000000000000000000000111101111), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000000011111110001), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000111011011101), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000001101101111111), expected: i32(12) },
+ { input: i32Bits(0b00000000000000000011111111011111), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000101111001110101), expected: i32(14) },
+ { input: i32Bits(0b00000000000000001101111011110111), expected: i32(15) },
+ { input: i32Bits(0b00000000000000011111111111110011), expected: i32(16) },
+ { input: i32Bits(0b00000000000000111111111110111111), expected: i32(17) },
+ { input: i32Bits(0b00000000000001111111011111111111), expected: i32(18) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32(19) },
+ { input: i32Bits(0b00000000000111110101011110111111), expected: i32(20) },
+ { input: i32Bits(0b00000000001111101111111111110111), expected: i32(21) },
+ { input: i32Bits(0b00000000011111111111010000101111), expected: i32(22) },
+ { input: i32Bits(0b00000000111111111111001111111011), expected: i32(23) },
+ { input: i32Bits(0b00000001111111011111101111111111), expected: i32(24) },
+ { input: i32Bits(0b00000011101011111011110111111011), expected: i32(25) },
+ { input: i32Bits(0b00000111111110111111111111111111), expected: i32(26) },
+ { input: i32Bits(0b00001111000000011011011110111111), expected: i32(27) },
+ { input: i32Bits(0b00011110101111011111111111111111), expected: i32(28) },
+ { input: i32Bits(0b00110110111111100111111110111101), expected: i32(29) },
+ { input: i32Bits(0b01010111111101111111011111011111), expected: i32(30) },
+
+ // Negative: random after leading 0
+ { input: i32Bits(0b11111111111111111111111111111010), expected: i32(2) },
+ { input: i32Bits(0b11111111111111111111111111110110), expected: i32(3) },
+ { input: i32Bits(0b11111111111111111111111111101101), expected: i32(4) },
+ { input: i32Bits(0b11111111111111111111111111011101), expected: i32(5) },
+ { input: i32Bits(0b11111111111111111111111110111001), expected: i32(6) },
+ { input: i32Bits(0b11111111111111111111111101101111), expected: i32(7) },
+ { input: i32Bits(0b11111111111111111111111011111111), expected: i32(8) },
+ { input: i32Bits(0b11111111111111111111110111101111), expected: i32(9) },
+ { input: i32Bits(0b11111111111111111111101111111111), expected: i32(10) },
+ { input: i32Bits(0b11111111111111111111011111110001), expected: i32(11) },
+ { input: i32Bits(0b11111111111111111110111011011101), expected: i32(12) },
+ { input: i32Bits(0b11111111111111111101101101111111), expected: i32(13) },
+ { input: i32Bits(0b11111111111111111011111111011111), expected: i32(14) },
+ { input: i32Bits(0b11111111111111110101111001110101), expected: i32(15) },
+ { input: i32Bits(0b11111111111111101101111011110111), expected: i32(16) },
+ { input: i32Bits(0b11111111111111011111111111110011), expected: i32(17) },
+ { input: i32Bits(0b11111111111110111111111110111111), expected: i32(18) },
+ { input: i32Bits(0b11111111111101111111011111111111), expected: i32(19) },
+ { input: i32Bits(0b11111111111011111111111111111111), expected: i32(20) },
+ { input: i32Bits(0b11111111110111110101011110111111), expected: i32(21) },
+ { input: i32Bits(0b11111111101111101111111111110111), expected: i32(22) },
+ { input: i32Bits(0b11111111011111111111010000101111), expected: i32(23) },
+ { input: i32Bits(0b11111110111111111111001111111011), expected: i32(24) },
+ { input: i32Bits(0b11111101111111011111101111111111), expected: i32(25) },
+ { input: i32Bits(0b11111011101011111011110111111011), expected: i32(26) },
+ { input: i32Bits(0b11110111111110111111111111111111), expected: i32(27) },
+ { input: i32Bits(0b11101111000000011011011110111111), expected: i32(28) },
+ { input: i32Bits(0b11011110101111011111111111111111), expected: i32(29) },
+ { input: i32Bits(0b10110110111111100111111110111101), expected: i32(30) },
+ ]);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts
new file mode 100644
index 0000000000..5c65f59d28
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts
@@ -0,0 +1,250 @@
+export const description = `
+Execution tests for the 'firstTrailingBit' builtin function
+
+S is i32, u32
+T is S or vecN<S>
+@const fn firstTrailingBit(e: T ) -> T
+For scalar T, the result is: T(-1) if e is zero.
+Otherwise the position of the least significant 1 bit in e.
+Component-wise when T is a vector.
+
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`u32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ await run(t, builtin('firstTrailingBit'), [TypeU32], TypeU32, cfg, [
+ // Zero
+ { input: u32Bits(0b00000000000000000000000000000000), expected: u32(-1) },
+
+ // High bit
+ { input: u32Bits(0b10000000000000000000000000000000), expected: u32(31) },
+
+ // 0's before trailing 1
+ { input: u32Bits(0b00000000000000000000000000000001), expected: u32(0) },
+ { input: u32Bits(0b00000000000000000000000000000010), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000000100), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000001000), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000010000), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000100000), expected: u32(5) },
+ { input: u32Bits(0b00000000000000000000000001000000), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000010000000), expected: u32(7) },
+ { input: u32Bits(0b00000000000000000000000100000000), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000001000000000), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000000010000000000), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000100000000000), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000001000000000000), expected: u32(12) },
+ { input: u32Bits(0b00000000000000000010000000000000), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000100000000000000), expected: u32(14) },
+ { input: u32Bits(0b00000000000000001000000000000000), expected: u32(15) },
+ { input: u32Bits(0b00000000000000010000000000000000), expected: u32(16) },
+ { input: u32Bits(0b00000000000000100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b00000000000001000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b00000000000010000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b00000000000100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b00000000001000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b00000000010000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b00000000100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b00000001000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b00000010000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b00000100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b00001000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b00010000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b00100000000000000000000000000000), expected: u32(29) },
+ { input: u32Bits(0b01000000000000000000000000000000), expected: u32(30) },
+
+ // 1's before trailing 1
+ { input: u32Bits(0b11111111111111111111111111111111), expected: u32(0) },
+ { input: u32Bits(0b11111111111111111111111111111110), expected: u32(1) },
+ { input: u32Bits(0b11111111111111111111111111111100), expected: u32(2) },
+ { input: u32Bits(0b11111111111111111111111111111000), expected: u32(3) },
+ { input: u32Bits(0b11111111111111111111111111110000), expected: u32(4) },
+ { input: u32Bits(0b11111111111111111111111111100000), expected: u32(5) },
+ { input: u32Bits(0b11111111111111111111111111000000), expected: u32(6) },
+ { input: u32Bits(0b11111111111111111111111110000000), expected: u32(7) },
+ { input: u32Bits(0b11111111111111111111111100000000), expected: u32(8) },
+ { input: u32Bits(0b11111111111111111111111000000000), expected: u32(9) },
+ { input: u32Bits(0b11111111111111111111110000000000), expected: u32(10) },
+ { input: u32Bits(0b11111111111111111111100000000000), expected: u32(11) },
+ { input: u32Bits(0b11111111111111111111000000000000), expected: u32(12) },
+ { input: u32Bits(0b11111111111111111110000000000000), expected: u32(13) },
+ { input: u32Bits(0b11111111111111111100000000000000), expected: u32(14) },
+ { input: u32Bits(0b11111111111111111000000000000000), expected: u32(15) },
+ { input: u32Bits(0b11111111111111110000000000000000), expected: u32(16) },
+ { input: u32Bits(0b11111111111111100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b11111111111111000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b11111111111110000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b11111111111100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b11111111111000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b11111111110000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b11111111100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b11111111000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b11111110000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b11111100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b11111000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b11110000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b11100000000000000000000000000000), expected: u32(29) },
+ { input: u32Bits(0b11000000000000000000000000000000), expected: u32(30) },
+
+ // random before trailing 1
+ { input: u32Bits(0b11110000001111111101111010001111), expected: u32(0) },
+ { input: u32Bits(0b11011110111111100101110011110010), expected: u32(1) },
+ { input: u32Bits(0b11110111011011111111010000111100), expected: u32(2) },
+ { input: u32Bits(0b11010011011101111111010011101000), expected: u32(3) },
+ { input: u32Bits(0b11010111110111110001111110110000), expected: u32(4) },
+ { input: u32Bits(0b11111101111101111110101111100000), expected: u32(5) },
+ { input: u32Bits(0b11111001111011111001111011000000), expected: u32(6) },
+ { input: u32Bits(0b11001110110111110111111010000000), expected: u32(7) },
+ { input: u32Bits(0b11101111011111101110101100000000), expected: u32(8) },
+ { input: u32Bits(0b11111101111011111111111000000000), expected: u32(9) },
+ { input: u32Bits(0b10011111011101110110110000000000), expected: u32(10) },
+ { input: u32Bits(0b11111111101101111011100000000000), expected: u32(11) },
+ { input: u32Bits(0b11111011010110111011000000000000), expected: u32(12) },
+ { input: u32Bits(0b00111101010000111010000000000000), expected: u32(13) },
+ { input: u32Bits(0b11111011110001101100000000000000), expected: u32(14) },
+ { input: u32Bits(0b10111111010111111000000000000000), expected: u32(15) },
+ { input: u32Bits(0b11011101111010110000000000000000), expected: u32(16) },
+ { input: u32Bits(0b01110100110110100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b11100111001011000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b11111001110110000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b00110100100100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b11111010011000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b00000010110000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b11100111100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b00101101000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b11011010000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b11010100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b10111000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b01110000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b10100000000000000000000000000000), expected: u32(29) },
+ ]);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`i32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ await run(t, builtin('firstTrailingBit'), [TypeI32], TypeI32, cfg, [
+ // Zero
+ { input: i32Bits(0b00000000000000000000000000000000), expected: i32(-1) },
+
+ // High bit
+ { input: i32Bits(0b10000000000000000000000000000000), expected: i32(31) },
+
+ // 0's before trailing 1
+ { input: i32Bits(0b00000000000000000000000000000001), expected: i32(0) },
+ { input: i32Bits(0b00000000000000000000000000000010), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000000100), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000001000), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000010000), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000100000), expected: i32(5) },
+ { input: i32Bits(0b00000000000000000000000001000000), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000010000000), expected: i32(7) },
+ { input: i32Bits(0b00000000000000000000000100000000), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000001000000000), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000000010000000000), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000100000000000), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000001000000000000), expected: i32(12) },
+ { input: i32Bits(0b00000000000000000010000000000000), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000100000000000000), expected: i32(14) },
+ { input: i32Bits(0b00000000000000001000000000000000), expected: i32(15) },
+ { input: i32Bits(0b00000000000000010000000000000000), expected: i32(16) },
+ { input: i32Bits(0b00000000000000100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b00000000000001000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b00000000000010000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b00000000000100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b00000000001000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b00000000010000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b00000000100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b00000001000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b00000010000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b00000100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b00001000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b00010000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b00100000000000000000000000000000), expected: i32(29) },
+ { input: i32Bits(0b01000000000000000000000000000000), expected: i32(30) },
+
+ // 1's before trailing 1
+ { input: i32Bits(0b11111111111111111111111111111111), expected: i32(0) },
+ { input: i32Bits(0b11111111111111111111111111111110), expected: i32(1) },
+ { input: i32Bits(0b11111111111111111111111111111100), expected: i32(2) },
+ { input: i32Bits(0b11111111111111111111111111111000), expected: i32(3) },
+ { input: i32Bits(0b11111111111111111111111111110000), expected: i32(4) },
+ { input: i32Bits(0b11111111111111111111111111100000), expected: i32(5) },
+ { input: i32Bits(0b11111111111111111111111111000000), expected: i32(6) },
+ { input: i32Bits(0b11111111111111111111111110000000), expected: i32(7) },
+ { input: i32Bits(0b11111111111111111111111100000000), expected: i32(8) },
+ { input: i32Bits(0b11111111111111111111111000000000), expected: i32(9) },
+ { input: i32Bits(0b11111111111111111111110000000000), expected: i32(10) },
+ { input: i32Bits(0b11111111111111111111100000000000), expected: i32(11) },
+ { input: i32Bits(0b11111111111111111111000000000000), expected: i32(12) },
+ { input: i32Bits(0b11111111111111111110000000000000), expected: i32(13) },
+ { input: i32Bits(0b11111111111111111100000000000000), expected: i32(14) },
+ { input: i32Bits(0b11111111111111111000000000000000), expected: i32(15) },
+ { input: i32Bits(0b11111111111111110000000000000000), expected: i32(16) },
+ { input: i32Bits(0b11111111111111100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b11111111111111000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b11111111111110000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b11111111111100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b11111111111000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b11111111110000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b11111111100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b11111111000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b11111110000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b11111100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b11111000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b11110000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b11100000000000000000000000000000), expected: i32(29) },
+ { input: i32Bits(0b11000000000000000000000000000000), expected: i32(30) },
+
+ // random before trailing 1
+ { input: i32Bits(0b11110000001111111101111010001111), expected: i32(0) },
+ { input: i32Bits(0b11011110111111100101110011110010), expected: i32(1) },
+ { input: i32Bits(0b11110111011011111111010000111100), expected: i32(2) },
+ { input: i32Bits(0b11010011011101111111010011101000), expected: i32(3) },
+ { input: i32Bits(0b11010111110111110001111110110000), expected: i32(4) },
+ { input: i32Bits(0b11111101111101111110101111100000), expected: i32(5) },
+ { input: i32Bits(0b11111001111011111001111011000000), expected: i32(6) },
+ { input: i32Bits(0b11001110110111110111111010000000), expected: i32(7) },
+ { input: i32Bits(0b11101111011111101110101100000000), expected: i32(8) },
+ { input: i32Bits(0b11111101111011111111111000000000), expected: i32(9) },
+ { input: i32Bits(0b10011111011101110110110000000000), expected: i32(10) },
+ { input: i32Bits(0b11111111101101111011100000000000), expected: i32(11) },
+ { input: i32Bits(0b11111011010110111011000000000000), expected: i32(12) },
+ { input: i32Bits(0b00111101010000111010000000000000), expected: i32(13) },
+ { input: i32Bits(0b11111011110001101100000000000000), expected: i32(14) },
+ { input: i32Bits(0b10111111010111111000000000000000), expected: i32(15) },
+ { input: i32Bits(0b11011101111010110000000000000000), expected: i32(16) },
+ { input: i32Bits(0b01110100110110100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b11100111001011000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b11111001110110000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b00110100100100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b11111010011000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b00000010110000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b11100111100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b00101101000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b11011010000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b11010100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b10111000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b01110000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b10100000000000000000000000000000), expected: i32(29) },
+ ]);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts
new file mode 100644
index 0000000000..873a6772c3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts
@@ -0,0 +1,96 @@
+export const description = `
+Execution tests for the 'floor' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn floor(e: T ) -> T
+Returns the floor of e. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeAbstractFloat } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, fullF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const kSmallMagnitudeTestValues = [0.1, 0.9, 1.0, 1.1, 1.9, -0.1, -0.9, -1.0, -1.1, -1.9];
+
+export const d = makeCaseCache('floor', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ ...kSmallMagnitudeTestValues,
+ ...fullF32Range(),
+ 0x8000_0000, // https://github.com/gpuweb/cts/issues/2766
+ ],
+ 'unfiltered',
+ FP.f32.floorInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ ...kSmallMagnitudeTestValues,
+ ...fullF16Range(),
+ 0x8000, // https://github.com/gpuweb/cts/issues/2766
+ ],
+ 'unfiltered',
+ FP.f16.floorInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ [
+ ...kSmallMagnitudeTestValues,
+ ...fullF64Range(),
+ 0x8000_0000_0000_0000, // https://github.com/gpuweb/cts/issues/2766
+ ],
+ 'unfiltered',
+ FP.abstract.floorInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(t, abstractBuiltin('floor'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ });
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('floor'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('floor'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts
new file mode 100644
index 0000000000..701f9d7ca9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts
@@ -0,0 +1,113 @@
+export const description = `
+Execution tests for the 'fma' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn fma(e1: T ,e2: T ,e3: T ) -> T
+Returns e1 * e2 + e3. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeAbstractFloat } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseF32Range, sparseF16Range, sparseF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('fma', {
+ f32_const: () => {
+ return FP.f32.generateScalarTripleToIntervalCases(
+ sparseF32Range(),
+ sparseF32Range(),
+ sparseF32Range(),
+ 'finite',
+ FP.f32.fmaInterval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarTripleToIntervalCases(
+ sparseF32Range(),
+ sparseF32Range(),
+ sparseF32Range(),
+ 'unfiltered',
+ FP.f32.fmaInterval
+ );
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarTripleToIntervalCases(
+ sparseF16Range(),
+ sparseF16Range(),
+ sparseF16Range(),
+ 'finite',
+ FP.f16.fmaInterval
+ );
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarTripleToIntervalCases(
+ sparseF16Range(),
+ sparseF16Range(),
+ sparseF16Range(),
+ 'unfiltered',
+ FP.f16.fmaInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarTripleToIntervalCases(
+ sparseF64Range(),
+ sparseF64Range(),
+ sparseF64Range(),
+ 'finite',
+ FP.abstract.fmaInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('fma'),
+ [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('fma'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('fma'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts
new file mode 100644
index 0000000000..44ea31fde2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts
@@ -0,0 +1,103 @@
+export const description = `
+Execution tests for the 'fract' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn fract(e: T ) -> T
+Returns the fractional part of e, computed as e - floor(e).
+Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('fract', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ 0.5, // 0.5 -> 0.5
+ 0.9, // ~0.9 -> ~0.9
+ 1, // 1 -> 0
+ 2, // 2 -> 0
+ 1.11, // ~1.11 -> ~0.11
+ 10.0001, // ~10.0001 -> ~0.0001
+ -0.1, // ~-0.1 -> ~0.9
+ -0.5, // -0.5 -> 0.5
+ -0.9, // ~-0.9 -> ~0.1
+ -1, // -1 -> 0
+ -2, // -2 -> 0
+ -1.11, // ~-1.11 -> ~0.89
+ -10.0001, // -10.0001 -> ~0.9999
+ 0x80000000, // https://github.com/gpuweb/cts/issues/2766
+ ...fullF32Range(),
+ ],
+ 'unfiltered',
+ FP.f32.fractInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ 0.5, // 0.5 -> 0.5
+ 0.9, // ~0.9 -> ~0.9
+ 1, // 1 -> 0
+ 2, // 2 -> 0
+ 1.11, // ~1.11 -> ~0.11
+ 10.0078125, // 10.0078125 -> 0.0078125
+ -0.1, // ~-0.1 -> ~0.9
+ -0.5, // -0.5 -> 0.5
+ -0.9, // ~-0.9 -> ~0.1
+ -1, // -1 -> 0
+ -2, // -2 -> 0
+ -1.11, // ~-1.11 -> ~0.89
+ -10.0078125, // -10.0078125 -> 0.9921875
+ 658.5, // 658.5 -> 0.5
+ ...fullF16Range(),
+ ],
+ 'unfiltered',
+ FP.f16.fractInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('fract'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('fract'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts
new file mode 100644
index 0000000000..ffe672b08c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts
@@ -0,0 +1,475 @@
+export const description = `
+Execution tests for the 'frexp' builtin function
+
+S is f32 or f16
+T is S or vecN<S>
+
+@const fn frexp(e: T) -> result_struct
+
+Splits e into a significand and exponent of the form significand * 2^exponent.
+Returns the result_struct for the appropriate overload.
+
+
+The magnitude of the significand is in the range of [0.5, 1.0) or 0.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { skipUndefined } from '../../../../../util/compare.js';
+import {
+ i32,
+ Scalar,
+ toVector,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeVec,
+ Vector,
+} from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ frexp,
+ fullF16Range,
+ fullF32Range,
+ vectorF16Range,
+ vectorF32Range,
+} from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import {
+ allInputSources,
+ basicExpressionBuilder,
+ Case,
+ run,
+ ShaderBuilder,
+} from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/* @returns an ShaderBuilder that evaluates frexp and returns .fract from the result structure */
+function fractBuilder(): ShaderBuilder {
+ return basicExpressionBuilder(value => `frexp(${value}).fract`);
+}
+
+/* @returns an ShaderBuilder that evaluates frexp and returns .exp from the result structure */
+function expBuilder(): ShaderBuilder {
+ return basicExpressionBuilder(value => `frexp(${value}).exp`);
+}
+
+/* @returns a fract Case for a given scalar or vector input */
+function makeVectorCaseFract(v: number | readonly number[], trait: 'f32' | 'f16'): Case {
+ const fp = FP[trait];
+ let toInput: (n: readonly number[]) => Scalar | Vector;
+ let toOutput: (n: readonly number[]) => Scalar | Vector;
+ if (v instanceof Array) {
+ // Input is vector
+ 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: readonly number[]) => fp.scalarBuilder(n[0]);
+ toOutput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
+ }
+
+ v = v.map(fp.quantize);
+ if (v.some(e => e !== 0 && fp.isSubnormal(e))) {
+ return { input: toInput(v), expected: skipUndefined(undefined) };
+ }
+
+ const fs = v.map(e => {
+ return frexp(e, trait).fract;
+ });
+
+ return { input: toInput(v), expected: toOutput(fs) };
+}
+
+/* @returns an exp Case for a given scalar or vector input */
+function makeVectorCaseExp(v: number | readonly number[], trait: 'f32' | 'f16'): Case {
+ const fp = FP[trait];
+ let toInput: (n: readonly number[]) => Scalar | Vector;
+ let toOutput: (n: readonly number[]) => Scalar | Vector;
+ if (v instanceof Array) {
+ // Input is vector
+ 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: readonly number[]) => fp.scalarBuilder(n[0]);
+ toOutput = (n: readonly number[]) => i32(n[0]);
+ }
+
+ v = v.map(fp.quantize);
+ if (v.some(e => e !== 0 && fp.isSubnormal(e))) {
+ return { input: toInput(v), expected: skipUndefined(undefined) };
+ }
+
+ const fs = v.map(e => {
+ return frexp(e, trait).exp;
+ });
+
+ return { input: toInput(v), expected: toOutput(fs) };
+}
+
+export const d = makeCaseCache('frexp', {
+ f32_fract: () => {
+ return fullF32Range().map(v => makeVectorCaseFract(v, 'f32'));
+ },
+ f32_exp: () => {
+ return fullF32Range().map(v => makeVectorCaseExp(v, 'f32'));
+ },
+ f32_vec2_fract: () => {
+ return vectorF32Range(2).map(v => makeVectorCaseFract(v, 'f32'));
+ },
+ f32_vec2_exp: () => {
+ return vectorF32Range(2).map(v => makeVectorCaseExp(v, 'f32'));
+ },
+ f32_vec3_fract: () => {
+ return vectorF32Range(3).map(v => makeVectorCaseFract(v, 'f32'));
+ },
+ f32_vec3_exp: () => {
+ return vectorF32Range(3).map(v => makeVectorCaseExp(v, 'f32'));
+ },
+ f32_vec4_fract: () => {
+ return vectorF32Range(4).map(v => makeVectorCaseFract(v, 'f32'));
+ },
+ f32_vec4_exp: () => {
+ return vectorF32Range(4).map(v => makeVectorCaseExp(v, 'f32'));
+ },
+ f16_fract: () => {
+ return fullF16Range().map(v => makeVectorCaseFract(v, 'f16'));
+ },
+ f16_exp: () => {
+ return fullF16Range().map(v => makeVectorCaseExp(v, 'f16'));
+ },
+ f16_vec2_fract: () => {
+ return vectorF16Range(2).map(v => makeVectorCaseFract(v, 'f16'));
+ },
+ f16_vec2_exp: () => {
+ return vectorF16Range(2).map(v => makeVectorCaseExp(v, 'f16'));
+ },
+ f16_vec3_fract: () => {
+ return vectorF16Range(3).map(v => makeVectorCaseFract(v, 'f16'));
+ },
+ f16_vec3_exp: () => {
+ return vectorF16Range(3).map(v => makeVectorCaseExp(v, 'f16'));
+ },
+ f16_vec4_fract: () => {
+ return vectorF16Range(4).map(v => makeVectorCaseFract(v, 'f16'));
+ },
+ f16_vec4_exp: () => {
+ return vectorF16Range(4).map(v => makeVectorCaseExp(v, 'f16'));
+ },
+});
+
+g.test('f32_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is f32
+
+struct __frexp_result_f32 {
+ fract : f32, // fract part
+ exp : i32 // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_fract');
+ await run(t, fractBuilder(), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f32_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is f32
+
+struct __frexp_result_f32 {
+ fract : f32, // fract part
+ exp : i32 // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_exp');
+ await run(t, expBuilder(), [TypeF32], TypeI32, t.params, cases);
+ });
+
+g.test('f32_vec2_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<f32>
+
+struct __frexp_result_vec2_f32 {
+ fract : vec2<f32>, // fract part
+ exp : vec2<i32> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_vec2_fract');
+ await run(t, fractBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+ });
+
+g.test('f32_vec2_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<f32>
+
+struct __frexp_result_vec2_f32 {
+ fract : vec2<f32>, // fractional part
+ exp : vec2<i32> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_vec2_exp');
+ await run(t, expBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeI32), t.params, cases);
+ });
+
+g.test('f32_vec3_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<f32>
+
+struct __frexp_result_vec3_f32 {
+ fract : vec3<f32>, // fractional part
+ exp : vec3<i32> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_vec3_fract');
+ await run(t, fractBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+ });
+
+g.test('f32_vec3_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<f32>
+
+struct __frexp_result_vec3_f32 {
+ fract : vec3<f32>, // fractional part
+ exp : vec3<i32> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_vec3_exp');
+ await run(t, expBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeI32), t.params, cases);
+ });
+
+g.test('f32_vec4_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<f32>
+
+struct __frexp_result_vec4_f32 {
+ fract : vec4<f32>, // fractional part
+ exp : vec4<i32> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_vec4_fract');
+ await run(t, fractBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+ });
+
+g.test('f32_vec4_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<f32>
+
+struct __frexp_result_vec4_f32 {
+ fract : vec4<f32>, // fractional part
+ exp : vec4<i32> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_vec4_exp');
+ await run(t, expBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeI32), t.params, cases);
+ });
+
+g.test('f16_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is f16
+
+struct __frexp_result_f16 {
+ fract : f16, // fract part
+ exp : i32 // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_fract');
+ await run(t, fractBuilder(), [TypeF16], TypeF16, t.params, cases);
+ });
+
+g.test('f16_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is f16
+
+struct __frexp_result_f16 {
+ fract : f16, // fract part
+ exp : i32 // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_exp');
+ await run(t, expBuilder(), [TypeF16], TypeI32, t.params, cases);
+ });
+
+g.test('f16_vec2_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<f16>
+
+struct __frexp_result_vec2_f16 {
+ fract : vec2<f16>, // fract part
+ exp : vec2<i32> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_vec2_fract');
+ await run(t, fractBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+ });
+
+g.test('f16_vec2_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<f16>
+
+struct __frexp_result_vec2_f16 {
+ fract : vec2<f16>, // fractional part
+ exp : vec2<i32> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_vec2_exp');
+ await run(t, expBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeI32), t.params, cases);
+ });
+
+g.test('f16_vec3_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<f16>
+
+struct __frexp_result_vec3_f16 {
+ fract : vec3<f16>, // fractional part
+ exp : vec3<i32> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_vec3_fract');
+ await run(t, fractBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+ });
+
+g.test('f16_vec3_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<f16>
+
+struct __frexp_result_vec3_f16 {
+ fract : vec3<f16>, // fractional part
+ exp : vec3<i32> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_vec3_exp');
+ await run(t, expBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeI32), t.params, cases);
+ });
+
+g.test('f16_vec4_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<f16>
+
+struct __frexp_result_vec4_f16 {
+ fract : vec4<f16>, // fractional part
+ exp : vec4<i32> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_vec4_fract');
+ await run(t, fractBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+ });
+
+g.test('f16_vec4_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<f16>
+
+struct __frexp_result_vec4_f16 {
+ fract : vec4<f16>, // fractional part
+ exp : vec4<i32> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_vec4_exp');
+ await run(t, expBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeI32), t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidth.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidth.spec.ts
new file mode 100644
index 0000000000..7c6f0232a9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidth.spec.ts
@@ -0,0 +1,21 @@
+export const description = `
+Execution tests for the 'fwidth' builtin function
+
+T is f32 or vecN<f32>
+fn fwidth(e:T) ->T
+Returns abs(dpdx(e)) + abs(dpdy(e)).
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthCoarse.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthCoarse.spec.ts
new file mode 100644
index 0000000000..9f93237934
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthCoarse.spec.ts
@@ -0,0 +1,21 @@
+export const description = `
+Execution tests for the 'fwidthCoarse' builtin function
+
+T is f32 or vecN<f32>
+fn fwidthCoarse(e:T) ->T
+Returns abs(dpdxCoarse(e)) + abs(dpdyCoarse(e)).
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthFine.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthFine.spec.ts
new file mode 100644
index 0000000000..b08c293228
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fwidthFine.spec.ts
@@ -0,0 +1,21 @@
+export const description = `
+Execution tests for the 'fwidthFine' builtin function
+
+T is f32 or vecN<f32>
+fn fwidthFine(e:T) ->T
+Returns abs(dpdxFine(e)) + abs(dpdyFine(e)).
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts
new file mode 100644
index 0000000000..1068e76252
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts
@@ -0,0 +1,386 @@
+export const description = `
+Execution tests for the 'insertBits' builtin function
+
+S is i32 or u32
+T is S or vecN<S>
+@const fn insertBits(e: T, newbits:T, offset: u32, count: u32) -> T Sets bits in an integer.
+
+When T is a scalar type, then:
+ w is the bit width of T
+ o = min(offset,w)
+ c = min(count, w - o)
+
+The result is e if c is 0.
+Otherwise, bits o..o+c-1 of the result are copied from bits 0..c-1 of newbits.
+Other bits of the result are copied from e.
+Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ i32Bits,
+ TypeI32,
+ u32,
+ TypeU32,
+ u32Bits,
+ vec2,
+ vec3,
+ vec4,
+ TypeVec,
+} from '../../../../../util/conversion.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('integer')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`integer tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('signed', [false, true])
+ .combine('width', [1, 2, 3, 4])
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ const scalarType = t.params.signed ? TypeI32 : TypeU32;
+ const T = t.params.width === 1 ? scalarType : TypeVec(t.params.width, scalarType);
+
+ const V = (x: number, y?: number, z?: number, w?: number) => {
+ y = y === undefined ? x : y;
+ z = z === undefined ? x : z;
+ w = w === undefined ? x : w;
+
+ if (t.params.signed) {
+ switch (t.params.width) {
+ case 1:
+ return i32Bits(x);
+ case 2:
+ return vec2(i32Bits(x), i32Bits(y));
+ case 3:
+ return vec3(i32Bits(x), i32Bits(y), i32Bits(z));
+ default:
+ return vec4(i32Bits(x), i32Bits(y), i32Bits(z), i32Bits(w));
+ }
+ } else {
+ switch (t.params.width) {
+ case 1:
+ return u32Bits(x);
+ case 2:
+ return vec2(u32Bits(x), u32Bits(y));
+ case 3:
+ return vec3(u32Bits(x), u32Bits(y), u32Bits(z));
+ default:
+ return vec4(u32Bits(x), u32Bits(y), u32Bits(z), u32Bits(w));
+ }
+ }
+ };
+
+ const all_1 = V(0b11111111111111111111111111111111);
+ const all_0 = V(0b00000000000000000000000000000000);
+ const low_1 = V(0b00000000000000000000000000000001);
+ const low_0 = V(0b11111111111111111111111111111110);
+ const high_1 = V(0b10000000000000000000000000000000);
+ const high_0 = V(0b01111111111111111111111111111111);
+ const pattern = V(
+ 0b10001001010100100010010100100010,
+ 0b11001110001100111000110011100011,
+ 0b10101010101010101010101010101010,
+ 0b01010101010101010101010101010101
+ );
+
+ const cases = [
+ { input: [all_0, all_0, u32(0), u32(32)], expected: all_0 },
+ { input: [all_0, all_0, u32(1), u32(10)], expected: all_0 },
+ { input: [all_0, all_0, u32(2), u32(5)], expected: all_0 },
+ { input: [all_0, all_0, u32(0), u32(1)], expected: all_0 },
+ { input: [all_0, all_0, u32(31), u32(1)], expected: all_0 },
+
+ { input: [all_0, all_1, u32(0), u32(32)], expected: all_1 },
+ { input: [all_1, all_0, u32(0), u32(32)], expected: all_0 },
+ { input: [all_0, all_1, u32(0), u32(1)], expected: low_1 },
+ { input: [all_1, all_0, u32(0), u32(1)], expected: low_0 },
+ { input: [all_0, all_1, u32(31), u32(1)], expected: high_1 },
+ { input: [all_1, all_0, u32(31), u32(1)], expected: high_0 },
+ { input: [all_0, all_1, u32(1), u32(10)], expected: V(0b00000000000000000000011111111110) },
+ { input: [all_1, all_0, u32(1), u32(10)], expected: V(0b11111111111111111111100000000001) },
+ { input: [all_0, all_1, u32(2), u32(5)], expected: V(0b00000000000000000000000001111100) },
+ { input: [all_1, all_0, u32(2), u32(5)], expected: V(0b11111111111111111111111110000011) },
+
+ // Patterns
+ { input: [all_0, pattern, u32(0), u32(32)], expected: pattern },
+ { input: [all_1, pattern, u32(0), u32(32)], expected: pattern },
+ {
+ input: [all_0, pattern, u32(1), u32(31)],
+ expected: V(
+ 0b00010010101001000100101001000100,
+ 0b10011100011001110001100111000110,
+ 0b01010101010101010101010101010100,
+ 0b10101010101010101010101010101010
+ ),
+ },
+ {
+ input: [all_1, pattern, u32(1), u32(31)],
+ expected: V(
+ 0b00010010101001000100101001000101,
+ 0b10011100011001110001100111000111,
+ 0b01010101010101010101010101010101,
+ 0b10101010101010101010101010101011
+ ),
+ },
+ {
+ input: [all_0, pattern, u32(14), u32(18)],
+ expected: V(
+ 0b10001001010010001000000000000000,
+ 0b11100011001110001100000000000000,
+ 0b10101010101010101000000000000000,
+ 0b01010101010101010100000000000000
+ ),
+ },
+ {
+ input: [all_1, pattern, u32(14), u32(18)],
+ expected: V(
+ 0b10001001010010001011111111111111,
+ 0b11100011001110001111111111111111,
+ 0b10101010101010101011111111111111,
+ 0b01010101010101010111111111111111
+ ),
+ },
+ {
+ input: [all_0, pattern, u32(14), u32(7)],
+ expected: V(
+ 0b00000000000010001000000000000000,
+ 0b00000000000110001100000000000000,
+ 0b00000000000010101000000000000000,
+ 0b00000000000101010100000000000000
+ ),
+ },
+ {
+ input: [all_1, pattern, u32(14), u32(7)],
+ expected: V(
+ 0b11111111111010001011111111111111,
+ 0b11111111111110001111111111111111,
+ 0b11111111111010101011111111111111,
+ 0b11111111111101010111111111111111
+ ),
+ },
+ {
+ input: [all_0, pattern, u32(14), u32(4)],
+ expected: V(
+ 0b00000000000000001000000000000000,
+ 0b00000000000000001100000000000000,
+ 0b00000000000000101000000000000000,
+ 0b00000000000000010100000000000000
+ ),
+ },
+ {
+ input: [all_1, pattern, u32(14), u32(4)],
+ expected: V(
+ 0b11111111111111001011111111111111,
+ 0b11111111111111001111111111111111,
+ 0b11111111111111101011111111111111,
+ 0b11111111111111010111111111111111
+ ),
+ },
+ {
+ input: [all_0, pattern, u32(14), u32(3)],
+ expected: V(
+ 0b00000000000000001000000000000000,
+ 0b00000000000000001100000000000000,
+ 0b00000000000000001000000000000000,
+ 0b00000000000000010100000000000000
+ ),
+ },
+ {
+ input: [all_1, pattern, u32(14), u32(3)],
+ expected: V(
+ 0b11111111111111101011111111111111,
+ 0b11111111111111101111111111111111,
+ 0b11111111111111101011111111111111,
+ 0b11111111111111110111111111111111
+ ),
+ },
+ {
+ input: [all_0, pattern, u32(18), u32(3)],
+ expected: V(
+ 0b00000000000010000000000000000000,
+ 0b00000000000011000000000000000000,
+ 0b00000000000010000000000000000000,
+ 0b00000000000101000000000000000000
+ ),
+ },
+ {
+ input: [all_1, pattern, u32(18), u32(3)],
+ expected: V(
+ 0b11111111111010111111111111111111,
+ 0b11111111111011111111111111111111,
+ 0b11111111111010111111111111111111,
+ 0b11111111111101111111111111111111
+ ),
+ },
+ {
+ input: [pattern, all_0, u32(1), u32(31)],
+ expected: V(
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000000000001,
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000000000001
+ ),
+ },
+ {
+ input: [pattern, all_1, u32(1), u32(31)],
+ expected: V(
+ 0b11111111111111111111111111111110,
+ 0b11111111111111111111111111111111,
+ 0b11111111111111111111111111111110,
+ 0b11111111111111111111111111111111
+ ),
+ },
+ {
+ input: [pattern, all_0, u32(14), u32(18)],
+ expected: V(
+ 0b00000000000000000010010100100010,
+ 0b00000000000000000000110011100011,
+ 0b00000000000000000010101010101010,
+ 0b00000000000000000001010101010101
+ ),
+ },
+ {
+ input: [pattern, all_1, u32(14), u32(18)],
+ expected: V(
+ 0b11111111111111111110010100100010,
+ 0b11111111111111111100110011100011,
+ 0b11111111111111111110101010101010,
+ 0b11111111111111111101010101010101
+ ),
+ },
+ {
+ input: [pattern, all_0, u32(14), u32(7)],
+ expected: V(
+ 0b10001001010000000010010100100010,
+ 0b11001110001000000000110011100011,
+ 0b10101010101000000010101010101010,
+ 0b01010101010000000001010101010101
+ ),
+ },
+ {
+ input: [pattern, all_1, u32(14), u32(7)],
+ expected: V(
+ 0b10001001010111111110010100100010,
+ 0b11001110001111111100110011100011,
+ 0b10101010101111111110101010101010,
+ 0b01010101010111111101010101010101
+ ),
+ },
+ {
+ input: [pattern, all_0, u32(14), u32(4)],
+ expected: V(
+ 0b10001001010100000010010100100010,
+ 0b11001110001100000000110011100011,
+ 0b10101010101010000010101010101010,
+ 0b01010101010101000001010101010101
+ ),
+ },
+ {
+ input: [pattern, all_1, u32(14), u32(4)],
+ expected: V(
+ 0b10001001010100111110010100100010,
+ 0b11001110001100111100110011100011,
+ 0b10101010101010111110101010101010,
+ 0b01010101010101111101010101010101
+ ),
+ },
+ {
+ input: [pattern, all_0, u32(14), u32(3)],
+ expected: V(
+ 0b10001001010100100010010100100010,
+ 0b11001110001100100000110011100011,
+ 0b10101010101010100010101010101010,
+ 0b01010101010101000001010101010101
+ ),
+ },
+ {
+ input: [pattern, all_1, u32(14), u32(3)],
+ expected: V(
+ 0b10001001010100111110010100100010,
+ 0b11001110001100111100110011100011,
+ 0b10101010101010111110101010101010,
+ 0b01010101010101011101010101010101
+ ),
+ },
+ {
+ input: [pattern, all_0, u32(18), u32(3)],
+ expected: V(
+ 0b10001001010000100010010100100010,
+ 0b11001110001000111000110011100011,
+ 0b10101010101000101010101010101010,
+ 0b01010101010000010101010101010101
+ ),
+ },
+ {
+ input: [pattern, all_1, u32(18), u32(3)],
+ expected: V(
+ 0b10001001010111100010010100100010,
+ 0b11001110001111111000110011100011,
+ 0b10101010101111101010101010101010,
+ 0b01010101010111010101010101010101
+ ),
+ },
+ {
+ input: [pattern, pattern, u32(18), u32(3)],
+ expected: V(
+ 0b10001001010010100010010100100010,
+ 0b11001110001011111000110011100011,
+ 0b10101010101010101010101010101010,
+ 0b01010101010101010101010101010101
+ ),
+ },
+ {
+ input: [pattern, pattern, u32(14), u32(7)],
+ expected: V(
+ 0b10001001010010001010010100100010,
+ 0b11001110001110001100110011100011,
+ 0b10101010101010101010101010101010,
+ 0b01010101010101010101010101010101
+ ),
+ },
+
+ // Zero count
+ { input: [pattern, all_1, u32(0), u32(0)], expected: pattern },
+ { input: [pattern, all_1, u32(1), u32(0)], expected: pattern },
+ { input: [pattern, all_1, u32(2), u32(0)], expected: pattern },
+ { input: [pattern, all_1, u32(31), u32(0)], expected: pattern },
+ { input: [pattern, all_1, u32(32), u32(0)], expected: pattern },
+ { input: [pattern, all_1, u32(0), u32(0)], expected: pattern },
+ ];
+
+ if (t.params.inputSource !== 'const') {
+ cases.push(
+ ...[
+ // Start overflow
+ { input: [all_0, pattern, u32(50), u32(3)], expected: all_0 },
+ { input: [all_1, pattern, u32(50), u32(3)], expected: all_1 },
+ { input: [pattern, pattern, u32(50), u32(3)], expected: pattern },
+
+ // End overflow
+ { input: [all_0, pattern, u32(0), u32(99)], expected: pattern },
+ { input: [all_1, pattern, u32(0), u32(99)], expected: pattern },
+ { input: [all_0, low_1, u32(31), u32(99)], expected: high_1 },
+ {
+ input: [pattern, pattern, u32(20), u32(99)],
+ expected: V(
+ 0b01010010001000100010010100100010,
+ 0b11001110001100111000110011100011,
+ 0b10101010101010101010101010101010,
+ 0b01010101010101010101010101010101
+ ),
+ },
+ ]
+ );
+ }
+
+ await run(t, builtin('insertBits'), [T, T, TypeU32, TypeU32], T, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts
new file mode 100644
index 0000000000..3e83816387
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts
@@ -0,0 +1,81 @@
+export const description = `
+Execution tests for the 'inverseSqrt' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn inverseSqrt(e: T ) -> T
+Returns the reciprocal of sqrt(e). Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('inverseSqrt', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // 0 < x <= 1 linearly spread
+ ...linearRange(kValue.f32.positive.min, 1, 100),
+ // 1 <= x < 2^32, biased towards 1
+ ...biasedRange(1, 2 ** 32, 1000),
+ ],
+ 'unfiltered',
+ FP.f32.inverseSqrtInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // 0 < x <= 1 linearly spread
+ ...linearRange(kValue.f16.positive.min, 1, 100),
+ // 1 <= x < 2^15, biased towards 1
+ ...biasedRange(1, 2 ** 15, 1000),
+ ],
+ 'unfiltered',
+ FP.f16.inverseSqrtInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('inverseSqrt'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('inverseSqrt'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts
new file mode 100644
index 0000000000..3829867752
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts
@@ -0,0 +1,121 @@
+export const description = `
+Execution tests for the 'ldexp' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+
+K is AbstractInt, i32
+I is K or vecN<K>, where
+ I is a scalar if T is a scalar, or a vector when T is a vector
+
+@const fn ldexp(e1: T ,e2: I ) -> T
+Returns e1 * 2^e2. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { assert } from '../../../../../../common/util/util.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { anyOf } from '../../../../../util/compare.js';
+import { i32, TypeF32, TypeF16, TypeI32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ biasedRange,
+ quantizeToI32,
+ sparseF32Range,
+ sparseI32Range,
+ sparseF16Range,
+} from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, Case, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const bias = {
+ f32: 127,
+ f16: 15,
+} as const;
+
+// ldexpInterval's return interval doesn't cover the flush-to-zero cases when e2 + bias <= 0, thus
+// special examination is required.
+// See the comment block on ldexpInterval for more details
+// e2 is an integer (i32) while e1 is float.
+const makeCase = (trait: 'f32' | 'f16', e1: number, e2: number): Case => {
+ const FPTrait = FP[trait];
+ e1 = FPTrait.quantize(e1);
+ // e2 should be in i32 range for the convinience.
+ assert(-2147483648 <= e2 && e2 <= 2147483647, 'e2 should be in i32 range');
+ e2 = quantizeToI32(e2);
+
+ const expected = FPTrait.ldexpInterval(e1, e2);
+
+ // Result may be zero if e2 + bias <= 0
+ if (e2 + bias[trait] <= 0) {
+ return {
+ input: [FPTrait.scalarBuilder(e1), i32(e2)],
+ expected: anyOf(expected, FPTrait.constants().zeroInterval),
+ };
+ }
+
+ return { input: [FPTrait.scalarBuilder(e1), i32(e2)], expected };
+};
+
+export const d = makeCaseCache('ldexp', {
+ f32_non_const: () => {
+ return sparseF32Range().flatMap(e1 => sparseI32Range().map(e2 => makeCase('f32', e1, e2)));
+ },
+ f32_const: () => {
+ return sparseF32Range().flatMap(e1 =>
+ biasedRange(-bias.f32 - 10, bias.f32 + 1, 10).flatMap(e2 =>
+ FP.f32.isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase('f32', e1, e2) : []
+ )
+ );
+ },
+ f16_non_const: () => {
+ return sparseF16Range().flatMap(e1 => sparseI32Range().map(e2 => makeCase('f16', e1, e2)));
+ },
+ f16_const: () => {
+ return sparseF16Range().flatMap(e1 =>
+ biasedRange(-bias.f16 - 10, bias.f16 + 1, 10).flatMap(e2 =>
+ FP.f16.isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase('f16', e1, e2) : []
+ )
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+`
+ )
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('ldexp'), [TypeF32, TypeI32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('ldexp'), [TypeF16, TypeI32], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts
new file mode 100644
index 0000000000..85c1f85169
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts
@@ -0,0 +1,178 @@
+export const description = `
+Execution tests for the 'length' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn length(e: T ) -> f32
+Returns the length of e (e.g. abs(e) if T is a scalar, or sqrt(e[0]^2 + e[1]^2 + ...) if T is a vector).
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ fullF32Range,
+ fullF16Range,
+ vectorF32Range,
+ vectorF16Range,
+} from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_vecN_[non_]const
+const f32_vec_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorToIntervalCases(
+ vectorF32Range(n),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.lengthInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_vecN_[non_]const
+const f16_vec_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorToIntervalCases(
+ vectorF16Range(n),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.lengthInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('length', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.lengthInterval
+ );
+ },
+ ...f32_vec_cases,
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.lengthInterval
+ );
+ },
+ ...f16_vec_cases,
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('length'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f32_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(t, builtin('length'), [TypeVec(2, TypeF32)], TypeF32, t.params, cases);
+ });
+
+g.test('f32_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(t, builtin('length'), [TypeVec(3, TypeF32)], TypeF32, t.params, cases);
+ });
+
+g.test('f32_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(t, builtin('length'), [TypeVec(4, TypeF32)], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('length'), [TypeF16], TypeF16, t.params, cases);
+ });
+
+g.test('f16_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(t, builtin('length'), [TypeVec(2, TypeF16)], TypeF16, t.params, cases);
+ });
+
+g.test('f16_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(t, builtin('length'), [TypeVec(3, TypeF16)], TypeF16, t.params, cases);
+ });
+
+g.test('f16_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(t, builtin('length'), [TypeVec(4, TypeF16)], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts
new file mode 100644
index 0000000000..ac60e2b1bc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts
@@ -0,0 +1,89 @@
+export const description = `
+Execution tests for the 'log' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn log(e: T ) -> T
+Returns the natural logarithm of e. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// log's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] }
+const f32_inputs = [
+ ...linearRange(kValue.f32.positive.min, 0.5, 20),
+ ...linearRange(0.5, 2.0, 20),
+ ...biasedRange(2.0, 2 ** 32, 1000),
+ ...fullF32Range(),
+];
+const f16_inputs = [
+ ...linearRange(kValue.f16.positive.min, 0.5, 20),
+ ...linearRange(0.5, 2.0, 20),
+ ...biasedRange(2.0, 2 ** 32, 1000),
+ ...fullF16Range(),
+];
+
+export const d = makeCaseCache('log', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.logInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.logInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.logInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.logInterval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+f32 tests
+
+TODO(#792): Decide what the ground-truth is for these tests. [1]
+`
+ )
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('log'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('log'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts
new file mode 100644
index 0000000000..37931579b9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts
@@ -0,0 +1,89 @@
+export const description = `
+Execution tests for the 'log2' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn log2(e: T ) -> T
+Returns the base-2 logarithm of e. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// log2's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] }
+const f32_inputs = [
+ ...linearRange(kValue.f32.positive.min, 0.5, 20),
+ ...linearRange(0.5, 2.0, 20),
+ ...biasedRange(2.0, 2 ** 32, 1000),
+ ...fullF32Range(),
+];
+const f16_inputs = [
+ ...linearRange(kValue.f16.positive.min, 0.5, 20),
+ ...linearRange(0.5, 2.0, 20),
+ ...biasedRange(2.0, 2 ** 32, 1000),
+ ...fullF16Range(),
+];
+
+export const d = makeCaseCache('log2', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.log2Interval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.log2Interval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.log2Interval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.log2Interval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+f32 tests
+
+TODO(#792): Decide what the ground-truth is for these tests. [1]
+`
+ )
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('log2'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('log2'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts
new file mode 100644
index 0000000000..6654b4951c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts
@@ -0,0 +1,165 @@
+export const description = `
+Execution tests for the 'max' builtin function
+
+S is AbstractInt, i32, or u32
+T is S or vecN<S>
+@const fn max(e1: T ,e2: T) -> T
+Returns e2 if e1 is less than e2, and e1 otherwise. Component-wise when T is a vector.
+
+S is AbstractFloat, f32, f16
+T is vecN<S>
+@const fn max(e1: T ,e2: T) -> T
+Returns e2 if e1 is less than e2, and e1 otherwise.
+If one operand is a NaN, the other is returned.
+If both operands are NaNs, a NaN is returned.
+Component-wise when T is a vector.
+
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ i32,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeU32,
+ u32,
+ TypeAbstractFloat,
+} from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, sparseF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, Case, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+/** Generate set of max test cases from list of interesting values */
+function generateTestCases(
+ values: Array<number>,
+ makeCase: (x: number, y: number) => Case
+): Array<Case> {
+ const cases = new Array<Case>();
+ values.forEach(e => {
+ values.forEach(f => {
+ cases.push(makeCase(e, f));
+ });
+ });
+ return cases;
+}
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('max', {
+ f32: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ fullF32Range(),
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.maxInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ fullF16Range(),
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.maxInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseF64Range(),
+ sparseF64Range(),
+ 'unfiltered',
+ FP.abstract.maxInterval
+ );
+ },
+});
+
+g.test('abstract_int')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`abstract int tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`u32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const makeCase = (x: number, y: number): Case => {
+ return { input: [u32(x), u32(y)], expected: u32(Math.max(x, y)) };
+ };
+
+ const test_values: Array<number> = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff];
+ const cases = generateTestCases(test_values, makeCase);
+
+ await run(t, builtin('max'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`i32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const makeCase = (x: number, y: number): Case => {
+ return { input: [i32(x), i32(y)], expected: i32(Math.max(x, y)) };
+ };
+
+ const test_values: Array<number> = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000];
+ const cases = generateTestCases(test_values, makeCase);
+
+ await run(t, builtin('max'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ });
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('max'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('max'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('max'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts
new file mode 100644
index 0000000000..6c05319546
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts
@@ -0,0 +1,164 @@
+export const description = `
+Execution tests for the 'min' builtin function
+
+S is AbstractInt, i32, or u32
+T is S or vecN<S>
+@const fn min(e1: T ,e2: T) -> T
+Returns e1 if e1 is less than e2, and e2 otherwise. Component-wise when T is a vector.
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn min(e1: T ,e2: T) -> T
+Returns e2 if e2 is less than e1, and e1 otherwise.
+If one operand is a NaN, the other is returned.
+If both operands are NaNs, a NaN is returned.
+Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ i32,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeU32,
+ u32,
+ TypeAbstractFloat,
+} from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, sparseF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, Case, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('min', {
+ f32: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ fullF32Range(),
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.minInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ fullF16Range(),
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.minInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseF64Range(),
+ sparseF64Range(),
+ 'unfiltered',
+ FP.abstract.minInterval
+ );
+ },
+});
+
+/** Generate set of min test cases from list of interesting values */
+function generateTestCases(
+ values: Array<number>,
+ makeCase: (x: number, y: number) => Case
+): Array<Case> {
+ const cases = new Array<Case>();
+ values.forEach(e => {
+ values.forEach(f => {
+ cases.push(makeCase(e, f));
+ });
+ });
+ return cases;
+}
+
+g.test('abstract_int')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`abstract int tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`u32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const makeCase = (x: number, y: number): Case => {
+ return { input: [u32(x), u32(y)], expected: u32(Math.min(x, y)) };
+ };
+
+ const test_values: Array<number> = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff];
+ const cases = generateTestCases(test_values, makeCase);
+
+ await run(t, builtin('min'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`i32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const makeCase = (x: number, y: number): Case => {
+ return { input: [i32(x), i32(y)], expected: i32(Math.min(x, y)) };
+ };
+
+ const test_values: Array<number> = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000];
+ const cases = generateTestCases(test_values, makeCase);
+
+ await run(t, builtin('min'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ });
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('min'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('min'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('min'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts
new file mode 100644
index 0000000000..95e9f6b310
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts
@@ -0,0 +1,275 @@
+export const description = `
+Execution tests for the 'mix' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn mix(e1: T, e2: T, e3: T) -> T
+Returns the linear blend of e1 and e2 (e.g. e1*(1-e3)+e2*e3). Component-wise when T is a vector.
+
+T is AbstractFloat, f32, or f16
+T2 is vecN<T>
+@const fn mix(e1: T2, e2: T2, e3: T) -> T2
+Returns the component-wise linear blend of e1 and e2, using scalar blending factor e3 for each component.
+Same as mix(e1,e2,T2(e3)).
+
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeVec, TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ sparseF32Range,
+ sparseF16Range,
+ sparseVectorF32Range,
+ sparseVectorF16Range,
+} from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_vecN_scalar_[non_]const
+const f32_vec_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_vec${n}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorPairScalarToVectorComponentWiseCase(
+ sparseVectorF32Range(n),
+ sparseVectorF32Range(n),
+ sparseF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ ...FP.f32.mixIntervals
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_vecN_scalar_[non_]const
+const f16_vec_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_vec${n}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorPairScalarToVectorComponentWiseCase(
+ sparseVectorF16Range(n),
+ sparseVectorF16Range(n),
+ sparseF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ ...FP.f16.mixIntervals
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('mix', {
+ f32_const: () => {
+ return FP.f32.generateScalarTripleToIntervalCases(
+ sparseF32Range(),
+ sparseF32Range(),
+ sparseF32Range(),
+ 'finite',
+ ...FP.f32.mixIntervals
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarTripleToIntervalCases(
+ sparseF32Range(),
+ sparseF32Range(),
+ sparseF32Range(),
+ 'unfiltered',
+ ...FP.f32.mixIntervals
+ );
+ },
+ ...f32_vec_scalar_cases,
+ f16_const: () => {
+ return FP.f16.generateScalarTripleToIntervalCases(
+ sparseF16Range(),
+ sparseF16Range(),
+ sparseF16Range(),
+ 'finite',
+ ...FP.f16.mixIntervals
+ );
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarTripleToIntervalCases(
+ sparseF16Range(),
+ sparseF16Range(),
+ sparseF16Range(),
+ 'unfiltered',
+ ...FP.f16.mixIntervals
+ );
+ },
+ ...f16_vec_scalar_cases,
+});
+
+g.test('abstract_float_matching')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract_float test with matching third param`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('abstract_float_nonmatching_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract_float tests with two vec2<abstract_float> params and scalar third param`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .unimplemented();
+
+g.test('abstract_float_nonmatching_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract_float tests with two vec3<abstract_float> params and scalar third param`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .unimplemented();
+
+g.test('abstract_float_nonmatching_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract_float tests with two vec4<abstract_float> params and scalar third param`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .unimplemented();
+
+g.test('f32_matching')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 test with matching third param`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('mix'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f32_nonmatching_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests with two vec2<f32> params and scalar third param`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_scalar_const' : 'f32_vec2_scalar_non_const'
+ );
+ await run(
+ t,
+ builtin('mix'),
+ [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeF32],
+ TypeVec(2, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_nonmatching_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests with two vec3<f32> params and scalar third param`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_scalar_const' : 'f32_vec3_scalar_non_const'
+ );
+ await run(
+ t,
+ builtin('mix'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeF32],
+ TypeVec(3, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_nonmatching_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests with two vec4<f32> params and scalar third param`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_scalar_const' : 'f32_vec4_scalar_non_const'
+ );
+ await run(
+ t,
+ builtin('mix'),
+ [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeF32],
+ TypeVec(4, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_matching')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 test with matching third param`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('mix'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+ });
+
+g.test('f16_nonmatching_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests with two vec2<f16> params and scalar third param`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_scalar_const' : 'f16_vec2_scalar_non_const'
+ );
+ await run(
+ t,
+ builtin('mix'),
+ [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeF16],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_nonmatching_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests with two vec3<f16> params and scalar third param`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_scalar_const' : 'f16_vec3_scalar_non_const'
+ );
+ await run(
+ t,
+ builtin('mix'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeF16],
+ TypeVec(3, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_nonmatching_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests with two vec4<f16> params and scalar third param`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_scalar_const' : 'f16_vec4_scalar_non_const'
+ );
+ await run(
+ t,
+ builtin('mix'),
+ [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeF16],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts
new file mode 100644
index 0000000000..1a3d8a2850
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts
@@ -0,0 +1,661 @@
+export const description = `
+Execution tests for the 'modf' builtin function
+
+T is f32 or f16 or AbstractFloat
+@const fn modf(e:T) -> result_struct
+Splits |e| into fractional and whole number parts.
+The whole part is (|e| % 1.0), and the fractional part is |e| minus the whole part.
+Returns the result_struct for the given type.
+
+S is f32 or f16 or AbstractFloat
+T is vecN<S>
+@const fn modf(e:T) -> result_struct
+Splits the components of |e| into fractional and whole number parts.
+The |i|'th component of the whole and fractional parts equal the whole and fractional parts of modf(e[i]).
+Returns the result_struct for the given type.
+
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ toVector,
+ TypeAbstractFloat,
+ TypeF16,
+ TypeF32,
+ TypeVec,
+} from '../../../../../util/conversion.js';
+import { FP, FPKind } from '../../../../../util/floating_point.js';
+import {
+ fullF16Range,
+ fullF32Range,
+ fullF64Range,
+ vectorF16Range,
+ vectorF32Range,
+ vectorF64Range,
+} from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import {
+ abstractFloatShaderBuilder,
+ allInputSources,
+ basicExpressionBuilder,
+ Case,
+ onlyConstInputSource,
+ run,
+ ShaderBuilder,
+} from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/** @returns an ShaderBuilder that evaluates modf and returns .whole from the result structure */
+function wholeBuilder(): ShaderBuilder {
+ return basicExpressionBuilder(value => `modf(${value}).whole`);
+}
+
+/** @returns an ShaderBuilder that evaluates modf and returns .fract from the result structure */
+function fractBuilder(): ShaderBuilder {
+ return basicExpressionBuilder(value => `modf(${value}).fract`);
+}
+
+/** @returns an ShaderBuilder that evaluates modf and returns .whole from the result structure for AbstractFloats */
+function abstractWholeBuilder(): ShaderBuilder {
+ return abstractFloatShaderBuilder(value => `modf(${value}).whole`);
+}
+
+/** @returns an ShaderBuilder that evaluates modf and returns .fract from the result structure for AbstractFloats */
+function abstractFractBuilder(): ShaderBuilder {
+ return abstractFloatShaderBuilder(value => `modf(${value}).fract`);
+}
+
+/** @returns a fract Case for a scalar vector input */
+function makeScalarCaseFract(kind: FPKind, n: number): Case {
+ const fp = FP[kind];
+ n = fp.quantize(n);
+ const result = fp.modfInterval(n).fract;
+
+ return { input: fp.scalarBuilder(n), expected: result };
+}
+
+/** @returns a whole Case for a scalar vector input */
+function makeScalarCaseWhole(kind: FPKind, n: number): Case {
+ const fp = FP[kind];
+ n = fp.quantize(n);
+ const result = fp.modfInterval(n).whole;
+
+ return { input: fp.scalarBuilder(n), expected: result };
+}
+
+/** @returns a fract Case for a given vector input */
+function makeVectorCaseFract(kind: FPKind, v: readonly number[]): Case {
+ const fp = FP[kind];
+ v = v.map(fp.quantize);
+ const fs = v.map(e => {
+ return fp.modfInterval(e).fract;
+ });
+
+ return { input: toVector(v, fp.scalarBuilder), expected: fs };
+}
+
+/** @returns a whole Case for a given vector input */
+function makeVectorCaseWhole(kind: FPKind, v: readonly number[]): Case {
+ const fp = FP[kind];
+ v = v.map(fp.quantize);
+ const ws = v.map(e => {
+ return fp.modfInterval(e).whole;
+ });
+
+ return { input: toVector(v, fp.scalarBuilder), expected: ws };
+}
+
+const scalar_range = {
+ f32: fullF32Range(),
+ f16: fullF16Range(),
+ abstract: fullF64Range(),
+};
+
+const vector_range = {
+ f32: {
+ 2: vectorF32Range(2),
+ 3: vectorF32Range(3),
+ 4: vectorF32Range(4),
+ },
+ f16: {
+ 2: vectorF16Range(2),
+ 3: vectorF16Range(3),
+ 4: vectorF16Range(4),
+ },
+ abstract: {
+ 2: vectorF64Range(2),
+ 3: vectorF64Range(3),
+ 4: vectorF64Range(4),
+ },
+};
+
+// Cases: [f32|f16|abstract]_[fract|whole]
+const scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(kind =>
+ (['whole', 'fract'] as const).map(portion => ({
+ [`${kind}_${portion}`]: () => {
+ const makeCase = portion === 'whole' ? makeScalarCaseWhole : makeScalarCaseFract;
+ return scalar_range[kind].map(makeCase.bind(null, kind));
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: [f32|f16|abstract]_vecN_[fract|whole]
+const vec_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(kind =>
+ ([2, 3, 4] as const).flatMap(n =>
+ (['whole', 'fract'] as const).map(portion => ({
+ [`${kind}_vec${n}_${portion}`]: () => {
+ const makeCase = portion === 'whole' ? makeVectorCaseWhole : makeVectorCaseFract;
+ return vector_range[kind][n].map(makeCase.bind(null, kind));
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('modf', {
+ ...scalar_cases,
+ ...vec_cases,
+});
+
+g.test('f32_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is f32
+
+struct __modf_result_f32 {
+ fract : f32, // fractional part
+ whole : f32 // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_fract');
+ await run(t, fractBuilder(), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f32_whole')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is f32
+
+struct __modf_result_f32 {
+ fract : f32, // fractional part
+ whole : f32 // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_whole');
+ await run(t, wholeBuilder(), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f32_vec2_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<f32>
+
+struct __modf_result_vec2_f32 {
+ fract : vec2<f32>, // fractional part
+ whole : vec2<f32> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_vec2_fract');
+ await run(t, fractBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+ });
+
+g.test('f32_vec2_whole')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<f32>
+
+struct __modf_result_vec2_f32 {
+ fract : vec2<f32>, // fractional part
+ whole : vec2<f32> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_vec2_whole');
+ await run(t, wholeBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+ });
+
+g.test('f32_vec3_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<f32>
+
+struct __modf_result_vec3_f32 {
+ fract : vec3<f32>, // fractional part
+ whole : vec3<f32> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_vec3_fract');
+ await run(t, fractBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+ });
+
+g.test('f32_vec3_whole')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<f32>
+
+struct __modf_result_vec3_f32 {
+ fract : vec3<f32>, // fractional part
+ whole : vec3<f32> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_vec3_whole');
+ await run(t, wholeBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+ });
+
+g.test('f32_vec4_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<f32>
+
+struct __modf_result_vec4_f32 {
+ fract : vec4<f32>, // fractional part
+ whole : vec4<f32> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_vec4_fract');
+ await run(t, fractBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+ });
+
+g.test('f32_vec4_whole')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<f32>
+
+struct __modf_result_vec4_f32 {
+ fract : vec4<f32>, // fractional part
+ whole : vec4<f32> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('f32_vec4_whole');
+ await run(t, wholeBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+ });
+
+g.test('f16_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is f16
+
+struct __modf_result_f16 {
+ fract : f16, // fractional part
+ whole : f16 // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_fract');
+ await run(t, fractBuilder(), [TypeF16], TypeF16, t.params, cases);
+ });
+
+g.test('f16_whole')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is f16
+
+struct __modf_result_f16 {
+ fract : f16, // fractional part
+ whole : f16 // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_whole');
+ await run(t, wholeBuilder(), [TypeF16], TypeF16, t.params, cases);
+ });
+
+g.test('f16_vec2_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<f16>
+
+struct __modf_result_vec2_f16 {
+ fract : vec2<f16>, // fractional part
+ whole : vec2<f16> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_vec2_fract');
+ await run(t, fractBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+ });
+
+g.test('f16_vec2_whole')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<f16>
+
+struct __modf_result_vec2_f16 {
+ fract : vec2<f16>, // fractional part
+ whole : vec2<f16> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_vec2_whole');
+ await run(t, wholeBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+ });
+
+g.test('f16_vec3_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<f16>
+
+struct __modf_result_vec3_f16 {
+ fract : vec3<f16>, // fractional part
+ whole : vec3<f16> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_vec3_fract');
+ await run(t, fractBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+ });
+
+g.test('f16_vec3_whole')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<f16>
+
+struct __modf_result_vec3_f16 {
+ fract : vec3<f16>, // fractional part
+ whole : vec3<f16> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_vec3_whole');
+ await run(t, wholeBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+ });
+
+g.test('f16_vec4_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<f16>
+
+struct __modf_result_vec4_f16 {
+ fract : vec4<f16>, // fractional part
+ whole : vec4<f16> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_vec4_fract');
+ await run(t, fractBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+ });
+
+g.test('f16_vec4_whole')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<f16>
+
+struct __modf_result_vec4_f16 {
+ fract : vec4<f16>, // fractional part
+ whole : vec4<f16> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('f16_vec4_whole');
+ await run(t, wholeBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+ });
+
+g.test('abstract_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is AbstractFloat
+
+struct __modf_result_abstract {
+ fract : AbstractFloat, // fractional part
+ whole : AbstractFloat // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_fract');
+ await run(t, abstractFractBuilder(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ });
+
+g.test('abstract_whole')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is AbstractFloat
+
+struct __modf_result_abstract {
+ fract : AbstractFloat, // fractional part
+ whole : AbstractFloat // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_whole');
+ await run(t, abstractWholeBuilder(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ });
+
+g.test('abstract_vec2_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<abstract>
+
+struct __modf_result_vec2_abstract {
+ fract : vec2<abstract>, // fractional part
+ whole : vec2<abstract> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_fract');
+ await run(
+ t,
+ abstractFractBuilder(),
+ [TypeVec(2, TypeAbstractFloat)],
+ TypeVec(2, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_vec2_whole')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<abstract>
+
+struct __modf_result_vec2_abstract {
+ fract : vec2<abstract>, // fractional part
+ whole : vec2<abstract> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_whole');
+ await run(
+ t,
+ abstractWholeBuilder(),
+ [TypeVec(2, TypeAbstractFloat)],
+ TypeVec(2, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_vec3_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<abstract>
+
+struct __modf_result_vec3_abstract {
+ fract : vec3<abstract>, // fractional part
+ whole : vec3<abstract> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_fract');
+ await run(
+ t,
+ abstractFractBuilder(),
+ [TypeVec(3, TypeAbstractFloat)],
+ TypeVec(3, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_vec3_whole')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<abstract>
+
+struct __modf_result_vec3_abstract {
+ fract : vec3<abstract>, // fractional part
+ whole : vec3<abstract> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_whole');
+ await run(
+ t,
+ abstractWholeBuilder(),
+ [TypeVec(3, TypeAbstractFloat)],
+ TypeVec(3, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_vec4_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<abstract>
+
+struct __modf_result_vec4_abstract {
+ fract : vec4<abstract>, // fractional part
+ whole : vec4<abstract> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_fract');
+ await run(
+ t,
+ abstractFractBuilder(),
+ [TypeVec(4, TypeAbstractFloat)],
+ TypeVec(4, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_vec4_whole')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<abstract>
+
+struct __modf_result_vec4_abstract {
+ fract : vec4<abstract>, // fractional part
+ whole : vec4<abstract> // whole part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_whole');
+ await run(
+ t,
+ abstractWholeBuilder(),
+ [TypeVec(4, TypeAbstractFloat)],
+ TypeVec(4, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts
new file mode 100644
index 0000000000..615617b448
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts
@@ -0,0 +1,137 @@
+export const description = `
+Execution tests for the 'normalize' builtin function
+
+T is AbstractFloat, f32, or f16
+@const fn normalize(e: vecN<T> ) -> vecN<T>
+Returns a unit vector in the same direction as e.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { vectorF32Range, vectorF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_vecN_[non_]const
+const f32_vec_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorToVectorCases(
+ vectorF32Range(n),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.normalizeInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_vecN_[non_]const
+const f16_vec_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorToVectorCases(
+ vectorF16Range(n),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.normalizeInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('normalize', {
+ ...f32_vec_cases,
+ ...f16_vec_cases,
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(t, builtin('normalize'), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+ });
+
+g.test('f32_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(t, builtin('normalize'), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+ });
+
+g.test('f32_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(t, builtin('normalize'), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+ });
+
+g.test('f16_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(t, builtin('normalize'), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+ });
+
+g.test('f16_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(t, builtin('normalize'), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+ });
+
+g.test('f16_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(t, builtin('normalize'), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts
new file mode 100644
index 0000000000..790e54720c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts
@@ -0,0 +1,88 @@
+export const description = `
+Converts two floating point values to half-precision floating point numbers, and then combines them into one u32 value.
+Component e[i] of the input is converted to a IEEE-754 binary16 value,
+which is then placed in bits 16 × i through 16 × i + 15 of the result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { anyOf, skipUndefined } from '../../../../../util/compare.js';
+import {
+ f32,
+ pack2x16float,
+ TypeF32,
+ TypeU32,
+ TypeVec,
+ u32,
+ vec2,
+} from '../../../../../util/conversion.js';
+import { cartesianProduct, fullF32Range, quantizeToF32 } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, Case, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// pack2x16float has somewhat unusual behaviour, specifically around how it is
+// supposed to behave when values go OOB and when they are considered to have
+// gone OOB, so has its own bespoke implementation.
+
+/**
+ * @returns a Case for `pack2x16float`
+ * @param param0 first param for the case
+ * @param param1 second param for the case
+ * @param filter_undefined should inputs that cause an undefined expectation be
+ * filtered out, needed for const-eval
+ */
+function makeCase(param0: number, param1: number, filter_undefined: boolean): Case | undefined {
+ param0 = quantizeToF32(param0);
+ param1 = quantizeToF32(param1);
+
+ const results = pack2x16float(param0, param1);
+ if (filter_undefined && results.some(r => r === undefined)) {
+ return undefined;
+ }
+
+ return {
+ input: [vec2(f32(param0), f32(param1))],
+ expected: anyOf(
+ ...results.map(r => (r === undefined ? skipUndefined(undefined) : skipUndefined(u32(r))))
+ ),
+ };
+}
+
+/**
+ * @returns an array of Cases for `pack2x16float`
+ * @param param0s array of inputs to try for the first param
+ * @param param1s array of inputs to try for the second param
+ * @param filter_undefined should inputs that cause an undefined expectation be
+ * filtered out, needed for const-eval
+ */
+function generateCases(param0s: number[], param1s: number[], filter_undefined: boolean): Case[] {
+ return cartesianProduct(param0s, param1s)
+ .map(e => makeCase(e[0], e[1], filter_undefined))
+ .filter((c): c is Case => c !== undefined);
+}
+
+export const d = makeCaseCache('pack2x16float', {
+ f32_const: () => {
+ return generateCases(fullF32Range(), fullF32Range(), true);
+ },
+ f32_non_const: () => {
+ return generateCases(fullF32Range(), fullF32Range(), false);
+ },
+});
+
+g.test('pack')
+ .specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions')
+ .desc(
+ `
+@const fn pack2x16float(e: vec2<f32>) -> u32
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('pack2x16float'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts
new file mode 100644
index 0000000000..54bb21f6c6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts
@@ -0,0 +1,55 @@
+export const description = `
+Converts two normalized floating point values to 16-bit signed integers, and then combines them into one u32 value.
+Component e[i] of the input is converted to a 16-bit twos complement integer value
+⌊ 0.5 + 32767 × min(1, max(-1, e[i])) ⌋ which is then placed in
+bits 16 × i through 16 × i + 15 of the result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import {
+ f32,
+ pack2x16snorm,
+ TypeF32,
+ TypeU32,
+ TypeVec,
+ u32,
+ vec2,
+} from '../../../../../util/conversion.js';
+import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
+import { allInputSources, Case, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('pack')
+ .specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions')
+ .desc(
+ `
+@const fn pack2x16snorm(e: vec2<f32>) -> u32
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const makeCase = (x: number, y: number): Case => {
+ x = quantizeToF32(x);
+ y = quantizeToF32(y);
+ return { input: [vec2(f32(x), f32(y))], expected: u32(pack2x16snorm(x, y)) };
+ };
+
+ // Returns a value normalized to [-1, 1].
+ const normalizeF32 = (n: number): number => {
+ return n / kValue.f32.positive.max;
+ };
+
+ const cases: Array<Case> = vectorF32Range(2).flatMap(v => {
+ return [
+ makeCase(...(v as [number, number])),
+ makeCase(...(v.map(normalizeF32) as [number, number])),
+ ];
+ });
+
+ await run(t, builtin('pack2x16snorm'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts
new file mode 100644
index 0000000000..a875a9c7e1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts
@@ -0,0 +1,55 @@
+export const description = `
+Converts two normalized floating point values to 16-bit unsigned integers, and then combines them into one u32 value.
+Component e[i] of the input is converted to a 16-bit unsigned integer value
+⌊ 0.5 + 65535 × min(1, max(0, e[i])) ⌋ which is then placed in
+bits 16 × i through 16 × i + 15 of the result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import {
+ f32,
+ pack2x16unorm,
+ TypeF32,
+ TypeU32,
+ TypeVec,
+ u32,
+ vec2,
+} from '../../../../../util/conversion.js';
+import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
+import { allInputSources, Case, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('pack')
+ .specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions')
+ .desc(
+ `
+@const fn pack2x16unorm(e: vec2<f32>) -> u32
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const makeCase = (x: number, y: number): Case => {
+ x = quantizeToF32(x);
+ y = quantizeToF32(y);
+ return { input: [vec2(f32(x), f32(y))], expected: u32(pack2x16unorm(x, y)) };
+ };
+
+ // Returns a value normalized to [0, 1].
+ const normalizeF32 = (n: number): number => {
+ return n > 0 ? n / kValue.f32.positive.max : n / kValue.f32.negative.min;
+ };
+
+ const cases: Array<Case> = vectorF32Range(2).flatMap(v => {
+ return [
+ makeCase(...(v as [number, number])),
+ makeCase(...(v.map(normalizeF32) as [number, number])),
+ ];
+ });
+
+ await run(t, builtin('pack2x16unorm'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts
new file mode 100644
index 0000000000..de0463e9fc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts
@@ -0,0 +1,60 @@
+export const description = `
+Converts four normalized floating point values to 8-bit signed integers, and then combines them into one u32 value.
+Component e[i] of the input is converted to an 8-bit twos complement integer value
+⌊ 0.5 + 127 × min(1, max(-1, e[i])) ⌋ which is then placed in
+bits 8 × i through 8 × i + 7 of the result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import {
+ f32,
+ pack4x8snorm,
+ Scalar,
+ TypeF32,
+ TypeU32,
+ TypeVec,
+ u32,
+ vec4,
+} from '../../../../../util/conversion.js';
+import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
+import { allInputSources, Case, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('pack')
+ .specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions')
+ .desc(
+ `
+@const fn pack4x8snorm(e: vec4<f32>) -> u32
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const makeCase = (vals: [number, number, number, number]): Case => {
+ const vals_f32 = new Array<Scalar>(4) as [Scalar, Scalar, Scalar, Scalar];
+ for (const idx in vals) {
+ vals[idx] = quantizeToF32(vals[idx]);
+ vals_f32[idx] = f32(vals[idx]);
+ }
+
+ return { input: [vec4(...vals_f32)], expected: u32(pack4x8snorm(...vals)) };
+ };
+
+ // Returns a value normalized to [-1, 1].
+ const normalizeF32 = (n: number): number => {
+ return n / kValue.f32.positive.max;
+ };
+
+ const cases: Array<Case> = vectorF32Range(4).flatMap(v => {
+ return [
+ makeCase(v as [number, number, number, number]),
+ makeCase(v.map(normalizeF32) as [number, number, number, number]),
+ ];
+ });
+
+ await run(t, builtin('pack4x8snorm'), [TypeVec(4, TypeF32)], TypeU32, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts
new file mode 100644
index 0000000000..b670e92fbb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts
@@ -0,0 +1,60 @@
+export const description = `
+Converts four normalized floating point values to 8-bit unsigned integers, and then combines them into one u32 value.
+Component e[i] of the input is converted to an 8-bit unsigned integer value
+⌊ 0.5 + 255 × min(1, max(0, e[i])) ⌋ which is then placed in
+bits 8 × i through 8 × i + 7 of the result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import {
+ f32,
+ pack4x8unorm,
+ Scalar,
+ TypeF32,
+ TypeU32,
+ TypeVec,
+ u32,
+ vec4,
+} from '../../../../../util/conversion.js';
+import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
+import { allInputSources, Case, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('pack')
+ .specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions')
+ .desc(
+ `
+@const fn pack4x8unorm(e: vec4<f32>) -> u32
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const makeCase = (vals: [number, number, number, number]): Case => {
+ const vals_f32 = new Array<Scalar>(4) as [Scalar, Scalar, Scalar, Scalar];
+ for (const idx in vals) {
+ vals[idx] = quantizeToF32(vals[idx]);
+ vals_f32[idx] = f32(vals[idx]);
+ }
+
+ return { input: [vec4(...vals_f32)], expected: u32(pack4x8unorm(...vals)) };
+ };
+
+ // Returns a value normalized to [0, 1].
+ const normalizeF32 = (n: number): number => {
+ return n > 0 ? n / kValue.f32.positive.max : n / kValue.f32.negative.min;
+ };
+
+ const cases: Array<Case> = vectorF32Range(4).flatMap(v => {
+ return [
+ makeCase(v as [number, number, number, number]),
+ makeCase(v.map(normalizeF32) as [number, number, number, number]),
+ ];
+ });
+
+ await run(t, builtin('pack4x8unorm'), [TypeVec(4, TypeF32)], TypeU32, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts
new file mode 100644
index 0000000000..f9b4fe1cfa
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts
@@ -0,0 +1,88 @@
+export const description = `
+Execution tests for the 'pow' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn pow(e1: T ,e2: T ) -> T
+Returns e1 raised to the power e2. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('pow', {
+ f32_const: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ fullF32Range(),
+ fullF32Range(),
+ 'finite',
+ FP.f32.powInterval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ fullF32Range(),
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.powInterval
+ );
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ fullF16Range(),
+ fullF16Range(),
+ 'finite',
+ FP.f16.powInterval
+ );
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ fullF16Range(),
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.powInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('pow'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('pow'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts
new file mode 100644
index 0000000000..b37d4c5afb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts
@@ -0,0 +1,70 @@
+export const description = `
+Execution tests for the 'quantizeToF16' builtin function
+
+T is f32 or vecN<f32>
+@const fn quantizeToF16(e: T ) -> T
+Quantizes a 32-bit floating point value e as if e were converted to a IEEE 754
+binary16 value, and then converted back to a IEEE 754 binary32 value.
+Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF16Range, fullF32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('quantizeToF16', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ kValue.f16.negative.min,
+ kValue.f16.negative.max,
+ kValue.f16.negative.subnormal.min,
+ kValue.f16.negative.subnormal.max,
+ kValue.f16.positive.subnormal.min,
+ kValue.f16.positive.subnormal.max,
+ kValue.f16.positive.min,
+ kValue.f16.positive.max,
+ ...fullF16Range(),
+ ],
+ 'finite',
+ FP.f32.quantizeToF16Interval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ kValue.f16.negative.min,
+ kValue.f16.negative.max,
+ kValue.f16.negative.subnormal.min,
+ kValue.f16.negative.subnormal.max,
+ kValue.f16.positive.subnormal.min,
+ kValue.f16.positive.subnormal.max,
+ kValue.f16.positive.min,
+ kValue.f16.positive.max,
+ ...fullF32Range(),
+ ],
+ 'unfiltered',
+ FP.f32.quantizeToF16Interval
+ );
+ },
+});
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('quantizeToF16'), [TypeF32], TypeF32, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts
new file mode 100644
index 0000000000..63ae45b656
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts
@@ -0,0 +1,90 @@
+export const description = `
+Execution tests for the 'radians' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn radians(e1: T ) -> T
+Converts degrees to radians, approximating e1 * π / 180.
+Component-wise when T is a vector
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF16Range, fullF32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('radians', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.radiansInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.radiansInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ fullF16Range(),
+ 'unfiltered',
+ FP.abstract.radiansInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('radians'),
+ [TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('radians'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('radians'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts
new file mode 100644
index 0000000000..2614c4e686
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts
@@ -0,0 +1,180 @@
+export const description = `
+Execution tests for the 'reflect' builtin function
+
+T is vecN<AbstractFloat>, vecN<f32>, or vecN<f16>
+@const fn reflect(e1: T, e2: T ) -> T
+For the incident vector e1 and surface orientation e2, returns the reflection
+direction e1-2*dot(e2,e1)*e2.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseVectorF32Range, sparseVectorF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_vecN_[non_]const
+const f32_vec_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorPairToVectorCases(
+ sparseVectorF32Range(n),
+ sparseVectorF32Range(n),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.reflectInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_vecN_[non_]const
+const f16_vec_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorPairToVectorCases(
+ sparseVectorF16Range(n),
+ sparseVectorF16Range(n),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.reflectInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('reflect', {
+ ...f32_vec_cases,
+ ...f16_vec_cases,
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const))
+ .unimplemented();
+
+g.test('f32_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('reflect'),
+ [TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
+ TypeVec(2, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('reflect'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
+ TypeVec(3, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('reflect'),
+ [TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
+ TypeVec(4, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('reflect'),
+ [TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('reflect'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
+ TypeVec(3, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('reflect'),
+ [TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts
new file mode 100644
index 0000000000..be1a76b437
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts
@@ -0,0 +1,253 @@
+export const description = `
+Execution tests for the 'refract' builtin function
+
+T is vecN<I>
+I is AbstractFloat, f32, or f16
+@const fn refract(e1: T ,e2: T ,e3: I ) -> T
+For the incident vector e1 and surface normal e2, and the ratio of indices of
+refraction e3, let k = 1.0 -e3*e3* (1.0 - dot(e2,e1) * dot(e2,e1)).
+If k < 0.0, returns the refraction vector 0.0, otherwise return the refraction
+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';
+import {
+ sparseVectorF32Range,
+ sparseVectorF16Range,
+ sparseF32Range,
+ sparseF16Range,
+} from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, Case, IntervalFilter, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Using a bespoke implementation of make*Case and generate*Cases here
+// since refract is the only builtin with the API signature
+// (vec, vec, scalar) -> vec
+
+/**
+ * @returns a Case for `refract`
+ * @param kind what type of floating point numbers to operate on
+ * @param i the `i` param for the case
+ * @param s the `s` param for the case
+ * @param r the `r` param for the case
+ * @param check what interval checking to apply
+ * */
+function makeCase(
+ kind: FPKind,
+ i: readonly number[],
+ s: readonly number[],
+ r: number,
+ check: IntervalFilter
+): Case | undefined {
+ const fp = FP[kind];
+ i = i.map(fp.quantize);
+ s = s.map(fp.quantize);
+ r = fp.quantize(r);
+
+ const vectors = fp.refractInterval(i, s, r);
+ if (check === 'finite' && vectors.some(e => !e.isFinite())) {
+ return undefined;
+ }
+
+ return {
+ input: [toVector(i, fp.scalarBuilder), toVector(s, fp.scalarBuilder), fp.scalarBuilder(r)],
+ expected: fp.refractInterval(i, s, r),
+ };
+}
+
+/**
+ * @returns an array of Cases for `refract`
+ * @param kind what type of floating point numbers to operate on
+ * @param param_is array of inputs to try for the `i` param
+ * @param param_ss array of inputs to try for the `s` param
+ * @param param_rs array of inputs to try for the `r` param
+ * @param check what interval checking to apply
+ */
+function generateCases(
+ kind: FPKind,
+ param_is: ROArrayArray<number>,
+ param_ss: ROArrayArray<number>,
+ param_rs: readonly number[],
+ check: IntervalFilter
+): Case[] {
+ // Cannot use `cartesianProduct` here due to heterogeneous param types
+ return param_is
+ .flatMap(i => {
+ return param_ss.flatMap(s => {
+ return param_rs.map(r => {
+ return makeCase(kind, i, s, r, check);
+ });
+ });
+ })
+ .filter((c): c is Case => c !== undefined);
+}
+
+// Cases: f32_vecN_[non_]const
+const f32_vec_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return generateCases(
+ 'f32',
+ sparseVectorF32Range(n),
+ sparseVectorF32Range(n),
+ sparseF32Range(),
+ nonConst ? 'unfiltered' : 'finite'
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_vecN_[non_]const
+const f16_vec_cases = ([2, 3, 4] as const)
+ .flatMap(n =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return generateCases(
+ 'f16',
+ sparseVectorF16Range(n),
+ sparseVectorF16Range(n),
+ sparseF16Range(),
+ nonConst ? 'unfiltered' : 'finite'
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('refract', {
+ ...f32_vec_cases,
+ ...f16_vec_cases,
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const))
+ .unimplemented();
+
+g.test('f32_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('refract'),
+ [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeF32],
+ TypeVec(2, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('refract'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeF32],
+ TypeVec(3, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f32 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('refract'),
+ [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeF32],
+ TypeVec(4, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('refract'),
+ [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeF16],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec3s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('refract'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeF16],
+ TypeVec(3, TypeF16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`f16 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('refract'),
+ [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeF16],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts
new file mode 100644
index 0000000000..6acb359822
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts
@@ -0,0 +1,250 @@
+export const description = `
+Execution tests for the 'reversBits' builtin function
+
+S is i32, u32
+T is S or vecN<S>
+@const fn reverseBits(e: T ) -> T
+Reverses the bits in e: The bit at position k of the result equals the bit at position 31-k of e.
+Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeU32, u32Bits, TypeI32, i32Bits } from '../../../../../util/conversion.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
+ .desc(`u32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ // prettier-ignore
+ await run(t, builtin('reverseBits'), [TypeU32], TypeU32, cfg, [
+ // Zero
+ { input: u32Bits(0b00000000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000000) },
+
+ // One
+ { input: u32Bits(0b00000000000000000000000000000001), expected: u32Bits(0b10000000000000000000000000000000) },
+
+ // 0's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000010), expected: u32Bits(0b01000000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000000100), expected: u32Bits(0b00100000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000001000), expected: u32Bits(0b00010000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000010000), expected: u32Bits(0b00001000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000100000), expected: u32Bits(0b00000100000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000001000000), expected: u32Bits(0b00000010000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000010000000), expected: u32Bits(0b00000001000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000100000000), expected: u32Bits(0b00000000100000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000001000000000), expected: u32Bits(0b00000000010000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000010000000000), expected: u32Bits(0b00000000001000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000100000000000), expected: u32Bits(0b00000000000100000000000000000000) },
+ { input: u32Bits(0b00000000000000000001000000000000), expected: u32Bits(0b00000000000010000000000000000000) },
+ { input: u32Bits(0b00000000000000000010000000000000), expected: u32Bits(0b00000000000001000000000000000000) },
+ { input: u32Bits(0b00000000000000000100000000000000), expected: u32Bits(0b00000000000000100000000000000000) },
+ { input: u32Bits(0b00000000000000001000000000000000), expected: u32Bits(0b00000000000000010000000000000000) },
+ { input: u32Bits(0b00000000000000010000000000000000), expected: u32Bits(0b00000000000000001000000000000000) },
+ { input: u32Bits(0b00000000000000100000000000000000), expected: u32Bits(0b00000000000000000100000000000000) },
+ { input: u32Bits(0b00000000000001000000000000000000), expected: u32Bits(0b00000000000000000010000000000000) },
+ { input: u32Bits(0b00000000000010000000000000000000), expected: u32Bits(0b00000000000000000001000000000000) },
+ { input: u32Bits(0b00000000000100000000000000000000), expected: u32Bits(0b00000000000000000000100000000000) },
+ { input: u32Bits(0b00000000001000000000000000000000), expected: u32Bits(0b00000000000000000000010000000000) },
+ { input: u32Bits(0b00000000010000000000000000000000), expected: u32Bits(0b00000000000000000000001000000000) },
+ { input: u32Bits(0b00000000100000000000000000000000), expected: u32Bits(0b00000000000000000000000100000000) },
+ { input: u32Bits(0b00000001000000000000000000000000), expected: u32Bits(0b00000000000000000000000010000000) },
+ { input: u32Bits(0b00000010000000000000000000000000), expected: u32Bits(0b00000000000000000000000001000000) },
+ { input: u32Bits(0b00000100000000000000000000000000), expected: u32Bits(0b00000000000000000000000000100000) },
+ { input: u32Bits(0b00001000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000010000) },
+ { input: u32Bits(0b00010000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000001000) },
+ { input: u32Bits(0b00100000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000100) },
+ { input: u32Bits(0b01000000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000010) },
+ { input: u32Bits(0b10000000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000001) },
+
+ // 1's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000011), expected: u32Bits(0b11000000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000000111), expected: u32Bits(0b11100000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000001111), expected: u32Bits(0b11110000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000011111), expected: u32Bits(0b11111000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000111111), expected: u32Bits(0b11111100000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000001111111), expected: u32Bits(0b11111110000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32Bits(0b11111111000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000111111111), expected: u32Bits(0b11111111100000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32Bits(0b11111111110000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000011111111111), expected: u32Bits(0b11111111111000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000111111111111), expected: u32Bits(0b11111111111100000000000000000000) },
+ { input: u32Bits(0b00000000000000000001111111111111), expected: u32Bits(0b11111111111110000000000000000000) },
+ { input: u32Bits(0b00000000000000000011111111111111), expected: u32Bits(0b11111111111111000000000000000000) },
+ { input: u32Bits(0b00000000000000000111111111111111), expected: u32Bits(0b11111111111111100000000000000000) },
+ { input: u32Bits(0b00000000000000001111111111111111), expected: u32Bits(0b11111111111111110000000000000000) },
+ { input: u32Bits(0b00000000000000011111111111111111), expected: u32Bits(0b11111111111111111000000000000000) },
+ { input: u32Bits(0b00000000000000111111111111111111), expected: u32Bits(0b11111111111111111100000000000000) },
+ { input: u32Bits(0b00000000000001111111111111111111), expected: u32Bits(0b11111111111111111110000000000000) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32Bits(0b11111111111111111111000000000000) },
+ { input: u32Bits(0b00000000000111111111111111111111), expected: u32Bits(0b11111111111111111111100000000000) },
+ { input: u32Bits(0b00000000001111111111111111111111), expected: u32Bits(0b11111111111111111111110000000000) },
+ { input: u32Bits(0b00000000011111111111111111111111), expected: u32Bits(0b11111111111111111111111000000000) },
+ { input: u32Bits(0b00000000111111111111111111111111), expected: u32Bits(0b11111111111111111111111100000000) },
+ { input: u32Bits(0b00000001111111111111111111111111), expected: u32Bits(0b11111111111111111111111110000000) },
+ { input: u32Bits(0b00000011111111111111111111111111), expected: u32Bits(0b11111111111111111111111111000000) },
+ { input: u32Bits(0b00000111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111100000) },
+ { input: u32Bits(0b00001111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111110000) },
+ { input: u32Bits(0b00011111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111111000) },
+ { input: u32Bits(0b00111111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111111100) },
+ { input: u32Bits(0b01111111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111111110) },
+ { input: u32Bits(0b11111111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111111111) },
+
+ // random after leading 1
+ { input: u32Bits(0b00000000000000000000000000000110), expected: u32Bits(0b01100000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000001101), expected: u32Bits(0b10110000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000011101), expected: u32Bits(0b10111000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000111001), expected: u32Bits(0b10011100000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000001101111), expected: u32Bits(0b11110110000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32Bits(0b11111111000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000111101111), expected: u32Bits(0b11110111100000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32Bits(0b11111111110000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000011111110001), expected: u32Bits(0b10001111111000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000111011011101), expected: u32Bits(0b10111011011100000000000000000000) },
+ { input: u32Bits(0b00000000000000000001101101111111), expected: u32Bits(0b11111110110110000000000000000000) },
+ { input: u32Bits(0b00000000000000000011111111011111), expected: u32Bits(0b11111011111111000000000000000000) },
+ { input: u32Bits(0b00000000000000000101111001110101), expected: u32Bits(0b10101110011110100000000000000000) },
+ { input: u32Bits(0b00000000000000001101111011110111), expected: u32Bits(0b11101111011110110000000000000000) },
+ { input: u32Bits(0b00000000000000011111111111110011), expected: u32Bits(0b11001111111111111000000000000000) },
+ { input: u32Bits(0b00000000000000111111111110111111), expected: u32Bits(0b11111101111111111100000000000000) },
+ { input: u32Bits(0b00000000000001111111011111111111), expected: u32Bits(0b11111111111011111110000000000000) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32Bits(0b11111111111111111111000000000000) },
+ { input: u32Bits(0b00000000000111110101011110111111), expected: u32Bits(0b11111101111010101111100000000000) },
+ { input: u32Bits(0b00000000001111101111111111110111), expected: u32Bits(0b11101111111111110111110000000000) },
+ { input: u32Bits(0b00000000011111111111010000101111), expected: u32Bits(0b11110100001011111111111000000000) },
+ { input: u32Bits(0b00000000111111111111001111111011), expected: u32Bits(0b11011111110011111111111100000000) },
+ { input: u32Bits(0b00000001111111011111101111111111), expected: u32Bits(0b11111111110111111011111110000000) },
+ { input: u32Bits(0b00000011101011111011110111111011), expected: u32Bits(0b11011111101111011111010111000000) },
+ { input: u32Bits(0b00000111111110111111111111111111), expected: u32Bits(0b11111111111111111101111111100000) },
+ { input: u32Bits(0b00001111000000011011011110111111), expected: u32Bits(0b11111101111011011000000011110000) },
+ { input: u32Bits(0b00011110101111011111111111111111), expected: u32Bits(0b11111111111111111011110101111000) },
+ { input: u32Bits(0b00110110111111100111111110111101), expected: u32Bits(0b10111101111111100111111101101100) },
+ { input: u32Bits(0b01010111111101111111011111011111), expected: u32Bits(0b11111011111011111110111111101010) },
+ { input: u32Bits(0b11100010011110101101101110101111), expected: u32Bits(0b11110101110110110101111001000111) },
+ ]);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions')
+ .desc(`i32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cfg: Config = t.params;
+ // prettier-ignore
+ await run(t, builtin('reverseBits'), [TypeI32], TypeI32, cfg, [
+ // Zero
+ { input: i32Bits(0b00000000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000000) },
+
+ // One
+ { input: i32Bits(0b00000000000000000000000000000001), expected: i32Bits(0b10000000000000000000000000000000) },
+
+ // 0's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000010), expected: i32Bits(0b01000000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000000100), expected: i32Bits(0b00100000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000001000), expected: i32Bits(0b00010000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000010000), expected: i32Bits(0b00001000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000100000), expected: i32Bits(0b00000100000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000001000000), expected: i32Bits(0b00000010000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000010000000), expected: i32Bits(0b00000001000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000100000000), expected: i32Bits(0b00000000100000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000001000000000), expected: i32Bits(0b00000000010000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000010000000000), expected: i32Bits(0b00000000001000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000100000000000), expected: i32Bits(0b00000000000100000000000000000000) },
+ { input: i32Bits(0b00000000000000000001000000000000), expected: i32Bits(0b00000000000010000000000000000000) },
+ { input: i32Bits(0b00000000000000000010000000000000), expected: i32Bits(0b00000000000001000000000000000000) },
+ { input: i32Bits(0b00000000000000000100000000000000), expected: i32Bits(0b00000000000000100000000000000000) },
+ { input: i32Bits(0b00000000000000001000000000000000), expected: i32Bits(0b00000000000000010000000000000000) },
+ { input: i32Bits(0b00000000000000010000000000000000), expected: i32Bits(0b00000000000000001000000000000000) },
+ { input: i32Bits(0b00000000000000100000000000000000), expected: i32Bits(0b00000000000000000100000000000000) },
+ { input: i32Bits(0b00000000000001000000000000000000), expected: i32Bits(0b00000000000000000010000000000000) },
+ { input: i32Bits(0b00000000000010000000000000000000), expected: i32Bits(0b00000000000000000001000000000000) },
+ { input: i32Bits(0b00000000000100000000000000000000), expected: i32Bits(0b00000000000000000000100000000000) },
+ { input: i32Bits(0b00000000001000000000000000000000), expected: i32Bits(0b00000000000000000000010000000000) },
+ { input: i32Bits(0b00000000010000000000000000000000), expected: i32Bits(0b00000000000000000000001000000000) },
+ { input: i32Bits(0b00000000100000000000000000000000), expected: i32Bits(0b00000000000000000000000100000000) },
+ { input: i32Bits(0b00000001000000000000000000000000), expected: i32Bits(0b00000000000000000000000010000000) },
+ { input: i32Bits(0b00000010000000000000000000000000), expected: i32Bits(0b00000000000000000000000001000000) },
+ { input: i32Bits(0b00000100000000000000000000000000), expected: i32Bits(0b00000000000000000000000000100000) },
+ { input: i32Bits(0b00001000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000010000) },
+ { input: i32Bits(0b00010000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000001000) },
+ { input: i32Bits(0b00100000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000100) },
+ { input: i32Bits(0b01000000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000010) },
+ { input: i32Bits(0b10000000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000001) },
+
+ // 1's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000011), expected: i32Bits(0b11000000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000000111), expected: i32Bits(0b11100000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000001111), expected: i32Bits(0b11110000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000011111), expected: i32Bits(0b11111000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000111111), expected: i32Bits(0b11111100000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000001111111), expected: i32Bits(0b11111110000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32Bits(0b11111111000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000111111111), expected: i32Bits(0b11111111100000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32Bits(0b11111111110000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000011111111111), expected: i32Bits(0b11111111111000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000111111111111), expected: i32Bits(0b11111111111100000000000000000000) },
+ { input: i32Bits(0b00000000000000000001111111111111), expected: i32Bits(0b11111111111110000000000000000000) },
+ { input: i32Bits(0b00000000000000000011111111111111), expected: i32Bits(0b11111111111111000000000000000000) },
+ { input: i32Bits(0b00000000000000000111111111111111), expected: i32Bits(0b11111111111111100000000000000000) },
+ { input: i32Bits(0b00000000000000001111111111111111), expected: i32Bits(0b11111111111111110000000000000000) },
+ { input: i32Bits(0b00000000000000011111111111111111), expected: i32Bits(0b11111111111111111000000000000000) },
+ { input: i32Bits(0b00000000000000111111111111111111), expected: i32Bits(0b11111111111111111100000000000000) },
+ { input: i32Bits(0b00000000000001111111111111111111), expected: i32Bits(0b11111111111111111110000000000000) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32Bits(0b11111111111111111111000000000000) },
+ { input: i32Bits(0b00000000000111111111111111111111), expected: i32Bits(0b11111111111111111111100000000000) },
+ { input: i32Bits(0b00000000001111111111111111111111), expected: i32Bits(0b11111111111111111111110000000000) },
+ { input: i32Bits(0b00000000011111111111111111111111), expected: i32Bits(0b11111111111111111111111000000000) },
+ { input: i32Bits(0b00000000111111111111111111111111), expected: i32Bits(0b11111111111111111111111100000000) },
+ { input: i32Bits(0b00000001111111111111111111111111), expected: i32Bits(0b11111111111111111111111110000000) },
+ { input: i32Bits(0b00000011111111111111111111111111), expected: i32Bits(0b11111111111111111111111111000000) },
+ { input: i32Bits(0b00000111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111100000) },
+ { input: i32Bits(0b00001111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111110000) },
+ { input: i32Bits(0b00011111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111111000) },
+ { input: i32Bits(0b00111111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111111100) },
+ { input: i32Bits(0b01111111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111111110) },
+ { input: i32Bits(0b11111111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111111111) },
+
+ // random after leading 1
+ { input: i32Bits(0b00000000000000000000000000000110), expected: i32Bits(0b01100000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000001101), expected: i32Bits(0b10110000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000011101), expected: i32Bits(0b10111000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000111001), expected: i32Bits(0b10011100000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000001101111), expected: i32Bits(0b11110110000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32Bits(0b11111111000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000111101111), expected: i32Bits(0b11110111100000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32Bits(0b11111111110000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000011111110001), expected: i32Bits(0b10001111111000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000111011011101), expected: i32Bits(0b10111011011100000000000000000000) },
+ { input: i32Bits(0b00000000000000000001101101111111), expected: i32Bits(0b11111110110110000000000000000000) },
+ { input: i32Bits(0b00000000000000000011111111011111), expected: i32Bits(0b11111011111111000000000000000000) },
+ { input: i32Bits(0b00000000000000000101111001110101), expected: i32Bits(0b10101110011110100000000000000000) },
+ { input: i32Bits(0b00000000000000001101111011110111), expected: i32Bits(0b11101111011110110000000000000000) },
+ { input: i32Bits(0b00000000000000011111111111110011), expected: i32Bits(0b11001111111111111000000000000000) },
+ { input: i32Bits(0b00000000000000111111111110111111), expected: i32Bits(0b11111101111111111100000000000000) },
+ { input: i32Bits(0b00000000000001111111011111111111), expected: i32Bits(0b11111111111011111110000000000000) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32Bits(0b11111111111111111111000000000000) },
+ { input: i32Bits(0b00000000000111110101011110111111), expected: i32Bits(0b11111101111010101111100000000000) },
+ { input: i32Bits(0b00000000001111101111111111110111), expected: i32Bits(0b11101111111111110111110000000000) },
+ { input: i32Bits(0b00000000011111111111010000101111), expected: i32Bits(0b11110100001011111111111000000000) },
+ { input: i32Bits(0b00000000111111111111001111111011), expected: i32Bits(0b11011111110011111111111100000000) },
+ { input: i32Bits(0b00000001111111011111101111111111), expected: i32Bits(0b11111111110111111011111110000000) },
+ { input: i32Bits(0b00000011101011111011110111111011), expected: i32Bits(0b11011111101111011111010111000000) },
+ { input: i32Bits(0b00000111111110111111111111111111), expected: i32Bits(0b11111111111111111101111111100000) },
+ { input: i32Bits(0b00001111000000011011011110111111), expected: i32Bits(0b11111101111011011000000011110000) },
+ { input: i32Bits(0b00011110101111011111111111111111), expected: i32Bits(0b11111111111111111011110101111000) },
+ { input: i32Bits(0b00110110111111100111111110111101), expected: i32Bits(0b10111101111111100111111101101100) },
+ { input: i32Bits(0b01010111111101111111011111011111), expected: i32Bits(0b11111011111011111110111111101010) },
+ { input: i32Bits(0b11100010011110101101101110101111), expected: i32Bits(0b11110101110110110101111001000111) },
+ ]);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts
new file mode 100644
index 0000000000..bd40ed4b2a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts
@@ -0,0 +1,79 @@
+export const description = `
+Execution tests for the 'round' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn round(e: T) -> T
+Result is the integer k nearest to e, as a floating point value.
+When e lies halfway between integers k and k+1, the result is k when k is even,
+and k+1 when k is odd.
+Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('round', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ 0x80000000, // https://github.com/gpuweb/cts/issues/2766,
+ ...fullF32Range(),
+ ],
+ 'unfiltered',
+ FP.f32.roundInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ 0x8000, // https://github.com/gpuweb/cts/issues/2766
+ ...fullF16Range(),
+ ],
+ 'unfiltered',
+ FP.f16.roundInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('round'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('round'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts
new file mode 100644
index 0000000000..2f16502921
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts
@@ -0,0 +1,100 @@
+export const description = `
+Execution tests for the 'saturate' builtin function
+
+S is AbstractFloat, f32, or f16
+T is S or vecN<S>
+@const fn saturate(e: T) -> T
+Returns clamp(e, 0.0, 1.0). Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF16Range, fullF32Range, fullF64Range, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('saturate', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // Non-clamped values
+ ...linearRange(0.0, 1.0, 20),
+ ...fullF32Range(),
+ ],
+ 'unfiltered',
+ FP.f32.saturateInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // Non-clamped values
+ ...linearRange(0.0, 1.0, 20),
+ ...fullF16Range(),
+ ],
+ 'unfiltered',
+ FP.f16.saturateInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ [
+ // Non-clamped values
+ ...linearRange(0.0, 1.0, 20),
+ ...fullF64Range(),
+ ],
+ 'unfiltered',
+ FP.abstract.saturateInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('saturate'),
+ [TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+ });
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('saturate'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('saturate'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts
new file mode 100644
index 0000000000..c64f989f42
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts
@@ -0,0 +1,253 @@
+export const description = `
+Execution tests for the 'select' builtin function
+
+T is scalar, abstract numeric type, or vector
+@const fn select(f: T, t: T, cond: bool) -> T
+Returns t when cond is true, and f otherwise.
+
+T is scalar or abstract numeric type
+@const fn select(f: vecN<T>, t: vecN<T>, cond: vecN<bool>) -> vecN<T>
+Component-wise selection. Result component i is evaluated as select(f[i],t[i],cond[i]).
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ VectorType,
+ TypeVec,
+ TypeBool,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeU32,
+ f32,
+ f16,
+ i32,
+ u32,
+ False,
+ True,
+ bool,
+ vec2,
+ vec3,
+ vec4,
+ abstractFloat,
+ TypeAbstractFloat,
+} from '../../../../../util/conversion.js';
+import { run, CaseList, allInputSources } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+function makeBool(n: number) {
+ return bool((n & 1) === 1);
+}
+
+type scalarKind = 'b' | 'af' | 'f' | 'h' | 'i' | 'u';
+
+const dataType = {
+ b: {
+ type: TypeBool,
+ constructor: makeBool,
+ },
+ af: {
+ type: TypeAbstractFloat,
+ constructor: abstractFloat,
+ },
+ f: {
+ type: TypeF32,
+ constructor: f32,
+ },
+ h: {
+ type: TypeF16,
+ constructor: f16,
+ },
+ i: {
+ type: TypeI32,
+ constructor: i32,
+ },
+ u: {
+ type: TypeU32,
+ constructor: u32,
+ },
+};
+
+g.test('scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#logical-builtin-functions')
+ .desc(`scalar tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('component', ['b', 'af', 'f', 'h', 'i', 'u'] as const)
+ .combine('overload', ['scalar', 'vec2', 'vec3', 'vec4'] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.component === 'h') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ t.skipIf(t.params.component === 'af' && t.params.inputSource !== 'const');
+ })
+ .fn(async t => {
+ const componentType = dataType[t.params.component as scalarKind].type;
+ const cons = dataType[t.params.component as scalarKind].constructor;
+
+ // Create the scalar values that will be selected from, either as scalars
+ // or vectors.
+ //
+ // Each boolean will select between c[k] and c[k+4]. Those values must
+ // always compare as different. The tricky case is boolean, where the parity
+ // has to be different, i.e. c[k]-c[k+4] must be odd.
+ const c = [0, 1, 2, 3, 5, 6, 7, 8].map(i => cons(i));
+ // Now form vectors that will have different components from each other.
+ const v2a = vec2(c[0], c[1]);
+ const v2b = vec2(c[4], c[5]);
+ const v3a = vec3(c[0], c[1], c[2]);
+ const v3b = vec3(c[4], c[5], c[6]);
+ const v4a = vec4(c[0], c[1], c[2], c[3]);
+ const v4b = vec4(c[4], c[5], c[6], c[7]);
+
+ const overloads = {
+ scalar: {
+ type: componentType,
+ cases: [
+ { input: [c[0], c[1], False], expected: c[0] },
+ { input: [c[0], c[1], True], expected: c[1] },
+ ],
+ },
+ vec2: {
+ type: TypeVec(2, componentType),
+ cases: [
+ { input: [v2a, v2b, False], expected: v2a },
+ { input: [v2a, v2b, True], expected: v2b },
+ ],
+ },
+ vec3: {
+ type: TypeVec(3, componentType),
+ cases: [
+ { input: [v3a, v3b, False], expected: v3a },
+ { input: [v3a, v3b, True], expected: v3b },
+ ],
+ },
+ vec4: {
+ type: TypeVec(4, componentType),
+ cases: [
+ { input: [v4a, v4b, False], expected: v4a },
+ { input: [v4a, v4b, True], expected: v4b },
+ ],
+ },
+ };
+ const overload = overloads[t.params.overload];
+
+ await run(
+ t,
+ t.params.component === 'af' ? abstractBuiltin('select') : builtin('select'),
+ [overload.type, overload.type, TypeBool],
+ overload.type,
+ t.params,
+ overload.cases
+ );
+ });
+
+g.test('vector')
+ .specURL('https://www.w3.org/TR/WGSL/#logical-builtin-functions')
+ .desc(`vector tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('component', ['b', 'af', 'f', 'h', 'i', 'u'] as const)
+ .combine('overload', ['vec2', 'vec3', 'vec4'] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.component === 'h') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ t.skipIf(t.params.component === 'af' && t.params.inputSource !== 'const');
+ })
+ .fn(async t => {
+ const componentType = dataType[t.params.component as scalarKind].type;
+ const cons = dataType[t.params.component as scalarKind].constructor;
+
+ // Create the scalar values that will be selected from.
+ //
+ // Each boolean will select between c[k] and c[k+4]. Those values must
+ // always compare as different. The tricky case is boolean, where the parity
+ // has to be different, i.e. c[k]-c[k+4] must be odd.
+ const c = [0, 1, 2, 3, 5, 6, 7, 8].map(i => cons(i));
+ const T = True;
+ const F = False;
+
+ let tests: { dataType: VectorType; boolType: VectorType; cases: CaseList };
+
+ switch (t.params.overload) {
+ case 'vec2': {
+ const a = vec2(c[0], c[1]);
+ const b = vec2(c[4], c[5]);
+ tests = {
+ dataType: TypeVec(2, componentType),
+ boolType: TypeVec(2, TypeBool),
+ cases: [
+ { input: [a, b, vec2(F, F)], expected: vec2(a.x, a.y) },
+ { input: [a, b, vec2(F, T)], expected: vec2(a.x, b.y) },
+ { input: [a, b, vec2(T, F)], expected: vec2(b.x, a.y) },
+ { input: [a, b, vec2(T, T)], expected: vec2(b.x, b.y) },
+ ],
+ };
+ break;
+ }
+ case 'vec3': {
+ const a = vec3(c[0], c[1], c[2]);
+ const b = vec3(c[4], c[5], c[6]);
+ tests = {
+ dataType: TypeVec(3, componentType),
+ boolType: TypeVec(3, TypeBool),
+ cases: [
+ { input: [a, b, vec3(F, F, F)], expected: vec3(a.x, a.y, a.z) },
+ { input: [a, b, vec3(F, F, T)], expected: vec3(a.x, a.y, b.z) },
+ { input: [a, b, vec3(F, T, F)], expected: vec3(a.x, b.y, a.z) },
+ { input: [a, b, vec3(F, T, T)], expected: vec3(a.x, b.y, b.z) },
+ { input: [a, b, vec3(T, F, F)], expected: vec3(b.x, a.y, a.z) },
+ { input: [a, b, vec3(T, F, T)], expected: vec3(b.x, a.y, b.z) },
+ { input: [a, b, vec3(T, T, F)], expected: vec3(b.x, b.y, a.z) },
+ { input: [a, b, vec3(T, T, T)], expected: vec3(b.x, b.y, b.z) },
+ ],
+ };
+ break;
+ }
+ case 'vec4': {
+ const a = vec4(c[0], c[1], c[2], c[3]);
+ const b = vec4(c[4], c[5], c[6], c[7]);
+ tests = {
+ dataType: TypeVec(4, componentType),
+ boolType: TypeVec(4, TypeBool),
+ cases: [
+ { input: [a, b, vec4(F, F, F, F)], expected: vec4(a.x, a.y, a.z, a.w) },
+ { input: [a, b, vec4(F, F, F, T)], expected: vec4(a.x, a.y, a.z, b.w) },
+ { input: [a, b, vec4(F, F, T, F)], expected: vec4(a.x, a.y, b.z, a.w) },
+ { input: [a, b, vec4(F, F, T, T)], expected: vec4(a.x, a.y, b.z, b.w) },
+ { input: [a, b, vec4(F, T, F, F)], expected: vec4(a.x, b.y, a.z, a.w) },
+ { input: [a, b, vec4(F, T, F, T)], expected: vec4(a.x, b.y, a.z, b.w) },
+ { input: [a, b, vec4(F, T, T, F)], expected: vec4(a.x, b.y, b.z, a.w) },
+ { input: [a, b, vec4(F, T, T, T)], expected: vec4(a.x, b.y, b.z, b.w) },
+ { input: [a, b, vec4(T, F, F, F)], expected: vec4(b.x, a.y, a.z, a.w) },
+ { input: [a, b, vec4(T, F, F, T)], expected: vec4(b.x, a.y, a.z, b.w) },
+ { input: [a, b, vec4(T, F, T, F)], expected: vec4(b.x, a.y, b.z, a.w) },
+ { input: [a, b, vec4(T, F, T, T)], expected: vec4(b.x, a.y, b.z, b.w) },
+ { input: [a, b, vec4(T, T, F, F)], expected: vec4(b.x, b.y, a.z, a.w) },
+ { input: [a, b, vec4(T, T, F, T)], expected: vec4(b.x, b.y, a.z, b.w) },
+ { input: [a, b, vec4(T, T, T, F)], expected: vec4(b.x, b.y, b.z, a.w) },
+ { input: [a, b, vec4(T, T, T, T)], expected: vec4(b.x, b.y, b.z, b.w) },
+ ],
+ };
+ break;
+ }
+ }
+
+ await run(
+ t,
+ t.params.component === 'af' ? abstractBuiltin('select') : builtin('select'),
+ [tests.dataType, tests.dataType, tests.boolType],
+ tests.dataType,
+ t.params,
+ tests.cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts
new file mode 100644
index 0000000000..a147acf6fb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts
@@ -0,0 +1,109 @@
+export const description = `
+Execution tests for the 'sign' builtin function
+
+S is AbstractFloat, AbstractInt, i32, f32, f16
+T is S or vecN<S>
+@const fn sign(e: T ) -> T
+Returns the sign of e. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ i32,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeAbstractFloat,
+} from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ fullF32Range,
+ fullF16Range,
+ fullI32Range,
+ fullF64Range,
+} from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('sign', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.signInterval);
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.signInterval);
+ },
+ abstract_float: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ fullF64Range(),
+ 'unfiltered',
+ FP.abstract.signInterval
+ );
+ },
+ i32: () =>
+ fullI32Range().map(i => {
+ const signFunc = (i: number): number => (i < 0 ? -1 : i > 0 ? 1 : 0);
+ return { input: [i32(i)], expected: i32(signFunc(i)) };
+ }),
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#sign-builtin')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstract_float');
+ await run(t, abstractBuiltin('sign'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ });
+
+g.test('abstract_int')
+ .specURL('https://www.w3.org/TR/WGSL/#sign-builtin')
+ .desc(`abstract int tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#sign-builtin')
+ .desc(`i32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('i32');
+ await run(t, builtin('sign'), [TypeI32], TypeI32, t.params, cases);
+ });
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#sign-builtin')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('sign'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#sign-builtin')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('sign'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts
new file mode 100644
index 0000000000..4ab3ae7a3d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts
@@ -0,0 +1,84 @@
+export const description = `
+Execution tests for the 'sin' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn sin(e: T ) -> T
+Returns the sine of e. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('sin', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 1000),
+ ...fullF32Range(),
+ ],
+ 'unfiltered',
+ FP.f32.sinInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 1000),
+ ...fullF16Range(),
+ ],
+ 'unfiltered',
+ FP.f16.sinInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+f32 tests
+
+TODO(#792): Decide what the ground-truth is for these tests. [1]
+`
+ )
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('sin'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('sin'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts
new file mode 100644
index 0000000000..d9b93a3dc8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts
@@ -0,0 +1,68 @@
+export const description = `
+Execution tests for the 'sinh' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn sinh(e: T ) -> T
+Returns the hyperbolic sine of e. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('sinh', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.sinhInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.sinhInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.sinhInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.sinhInterval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('sinh'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('sinh'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts
new file mode 100644
index 0000000000..20d2a4edbc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts
@@ -0,0 +1,94 @@
+export const description = `
+Execution tests for the 'smoothstep' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn smoothstep(low: T , high: T , x: T ) -> T
+Returns the smooth Hermite interpolation between 0 and 1.
+Component-wise when T is a vector.
+For scalar T, the result is t * t * (3.0 - 2.0 * t), where t = clamp((x - low) / (high - low), 0.0, 1.0).
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseF32Range, sparseF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('smoothstep', {
+ f32_const: () => {
+ return FP.f32.generateScalarTripleToIntervalCases(
+ sparseF32Range(),
+ sparseF32Range(),
+ sparseF32Range(),
+ 'finite',
+ FP.f32.smoothStepInterval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarTripleToIntervalCases(
+ sparseF32Range(),
+ sparseF32Range(),
+ sparseF32Range(),
+ 'unfiltered',
+ FP.f32.smoothStepInterval
+ );
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarTripleToIntervalCases(
+ sparseF16Range(),
+ sparseF16Range(),
+ sparseF16Range(),
+ 'finite',
+ FP.f16.smoothStepInterval
+ );
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarTripleToIntervalCases(
+ sparseF16Range(),
+ sparseF16Range(),
+ sparseF16Range(),
+ 'unfiltered',
+ FP.f16.smoothStepInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('smoothstep'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('smoothstep'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts
new file mode 100644
index 0000000000..a092438043
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts
@@ -0,0 +1,68 @@
+export const description = `
+Execution tests for the 'sqrt' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn sqrt(e: T ) -> T
+Returns the square root of e. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('sqrt', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.sqrtInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.sqrtInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.sqrtInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.sqrtInterval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('sqrt'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('sqrt'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts
new file mode 100644
index 0000000000..752e2676e6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts
@@ -0,0 +1,87 @@
+export const description = `
+Execution tests for the 'step' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn step(edge: T ,x: T ) -> T
+Returns 1.0 if edge ≤ x, and 0.0 otherwise. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { anyOf } from '../../../../../util/compare.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, Case, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// stepInterval's return value can't always be interpreted as a single acceptance
+// interval, valid result may be 0.0 or 1.0 or both of them, but will never be a
+// value in interval (0.0, 1.0).
+// See the comment block on stepInterval for more details
+const makeCase = (trait: 'f32' | 'f16', edge: number, x: number): Case => {
+ const FPTrait = FP[trait];
+ edge = FPTrait.quantize(edge);
+ x = FPTrait.quantize(x);
+ const expected = FPTrait.stepInterval(edge, x);
+
+ // [0, 0], [1, 1], or [-∞, +∞] cases
+ if (expected.isPoint() || !expected.isFinite()) {
+ return { input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)], expected };
+ }
+
+ // [0, 1] case, valid result is either 0.0 or 1.0.
+ const zeroInterval = FPTrait.toInterval(0);
+ const oneInterval = FPTrait.toInterval(1);
+ return {
+ input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)],
+ expected: anyOf(zeroInterval, oneInterval),
+ };
+};
+
+export const d = makeCaseCache('step', {
+ f32: () => {
+ return fullF32Range().flatMap(edge => fullF32Range().map(x => makeCase('f32', edge, x)));
+ },
+ f16: () => {
+ return fullF16Range().flatMap(edge => fullF16Range().map(x => makeCase('f16', edge, x)));
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('step'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('step'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/storageBarrier.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/storageBarrier.spec.ts
new file mode 100644
index 0000000000..f376db4472
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/storageBarrier.spec.ts
@@ -0,0 +1,38 @@
+export const description = `
+'storageBarrier' affects memory and atomic operations in the storage address space.
+
+All synchronization functions execute a control barrier with Acquire/Release memory ordering.
+That is, all synchronization functions, and affected memory and atomic operations are ordered
+in program order relative to the synchronization function. Additionally, the affected memory
+and atomic operations program-ordered before the synchronization function must be visible to
+all other threads in the workgroup before any affected memory or atomic operation program-ordered
+after the synchronization function is executed by a member of the workgroup. All synchronization
+functions use the Workgroup memory scope. All synchronization functions have a Workgroup
+execution scope.
+
+All synchronization functions must only be used in the compute shader stage.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stage')
+ .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions')
+ .desc(
+ `
+All synchronization functions must only be used in the compute shader stage.
+`
+ )
+ .params(u => u.combine('stage', ['vertex', 'fragment', 'compute'] as const))
+ .unimplemented();
+
+g.test('barrier')
+ .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions')
+ .desc(
+ `
+fn storageBarrier()
+`
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts
new file mode 100644
index 0000000000..be3bdee046
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts
@@ -0,0 +1,78 @@
+export const description = `
+Execution tests for the 'tan' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn tan(e: T ) -> T
+Returns the tangent of e. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('tan', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // Defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 100),
+ ...fullF32Range(),
+ ],
+ 'unfiltered',
+ FP.f32.tanInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // Defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 100),
+ ...fullF16Range(),
+ ],
+ 'unfiltered',
+ FP.f16.tanInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('tan'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('tan'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts
new file mode 100644
index 0000000000..3aca5b924b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts
@@ -0,0 +1,62 @@
+export const description = `
+Execution tests for the 'tanh' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn tanh(e: T ) -> T
+Returns the hyperbolic tangent of e. Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('tanh', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.tanhInterval);
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.tanhInterval);
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .unimplemented();
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('tanh'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('tanh'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts
new file mode 100644
index 0000000000..0ecb9964cf
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts
@@ -0,0 +1,160 @@
+export const description = `
+Execution tests for the 'textureDimension' builtin function
+
+The dimensions of the texture in texels.
+For textures based on cubes, the results are the dimensions of each face of the cube.
+Cube faces are square, so the x and y components of the result are equal.
+If level is outside the range [0, textureNumLevels(t)) then any valid value for the return type may be returned.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled')
+ .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
+ .desc(
+ `
+T: f32, i32, u32
+
+fn textureDimensions(t: texture_1d<T>) -> u32
+fn textureDimensions(t: texture_1d<T>, level: u32) -> u32
+fn textureDimensions(t: texture_2d<T>) -> vec2<u32>
+fn textureDimensions(t: texture_2d<T>, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_2d_array<T>) -> vec2<u32>
+fn textureDimensions(t: texture_2d_array<T>, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_3d<T>) -> vec3<u32>
+fn textureDimensions(t: texture_3d<T>, level: u32) -> vec3<u32>
+fn textureDimensions(t: texture_cube<T>) -> vec2<u32>
+fn textureDimensions(t: texture_cube<T>, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_cube_array<T>) -> vec2<u32>
+fn textureDimensions(t: texture_cube_array<T>, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_multisampled_2d<T>)-> vec2<u32>
+
+Parameters:
+ * t: the sampled texture
+ * level:
+ - The mip level, with level 0 containing a full size version of the texture.
+ - If omitted, the dimensions of level 0 are returned.
+`
+ )
+ .params(u =>
+ u
+ .combine('texture_type', [
+ 'texture_1d',
+ 'texture_2d',
+ 'texture_2d_array',
+ 'texture_3d',
+ 'texture_cube',
+ 'texture_cube_array',
+ 'texture_multisampled_2d',
+ ] as const)
+ .beginSubcases()
+ .combine('sampled_type', ['f32-only', 'i32', 'u32'] as const)
+ .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
+ )
+ .unimplemented();
+
+g.test('depth')
+ .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
+ .desc(
+ `
+fn textureDimensions(t: texture_depth_2d) -> vec2<u32>
+fn textureDimensions(t: texture_depth_2d, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_depth_2d_array) -> vec2<u32>
+fn textureDimensions(t: texture_depth_2d_array, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_depth_cube) -> vec2<u32>
+fn textureDimensions(t: texture_depth_cube, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_depth_cube_array) -> vec2<u32>
+fn textureDimensions(t: texture_depth_cube_array, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_depth_multisampled_2d)-> vec2<u32>
+
+Parameters:
+ * t: the depth or multisampled texture
+ * level:
+ - The mip level, with level 0 containing a full size version of the texture.
+ - If omitted, the dimensions of level 0 are returned.
+`
+ )
+ .params(u =>
+ u
+ .combine('texture_type', [
+ 'texture_depth_2d',
+ 'texture_depth_2d_array',
+ 'texture_depth_cube',
+ 'texture_depth_cube_array',
+ 'texture_depth_multisampled_2d',
+ ])
+ .beginSubcases()
+ .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
+ )
+ .unimplemented();
+
+g.test('storage')
+ .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
+ .desc(
+ `
+F: rgba8unorm
+ rgba8snorm
+ rgba8uint
+ rgba8sint
+ rgba16uint
+ rgba16sint
+ rgba16float
+ r32uint
+ r32sint
+ r32float
+ rg32uint
+ rg32sint
+ rg32float
+ rgba32uint
+ rgba32sint
+ rgba32float
+A: read, write, read_write
+
+fn textureDimensions(t: texture_storage_1d<F,A>) -> u32
+fn textureDimensions(t: texture_storage_2d<F,A>) -> vec2<u32>
+fn textureDimensions(t: texture_storage_2d_array<F,A>) -> vec2<u32>
+fn textureDimensions(t: texture_storage_3d<F,A>) -> vec3<u32>
+
+Parameters:
+ * t: the storage texture
+`
+ )
+ .params(u =>
+ u
+ .combine('texel_format', [
+ 'rgba8unorm',
+ 'rgba8snorm',
+ 'rgba8uint',
+ 'rgba8sint',
+ 'rgba16uint',
+ 'rgba16sint',
+ 'rgba16float',
+ 'r32uint',
+ 'r32sint',
+ 'r32float',
+ 'rg32uint',
+ 'rg32sint',
+ 'rg32float',
+ 'rgba32uint',
+ 'rgba32sint',
+ 'rgba32float',
+ ] as const)
+ .beginSubcases()
+ .combine('access_mode', ['read', 'write', 'read_write'] as const)
+ )
+ .unimplemented();
+
+g.test('external')
+ .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
+ .desc(
+ `
+fn textureDimensions(t: texture_external) -> vec2<u32>
+
+Parameters:
+ * t: the external texture
+`
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGather.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGather.spec.ts
new file mode 100644
index 0000000000..40b331efab
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGather.spec.ts
@@ -0,0 +1,270 @@
+export const description = `
+Execution tests for the 'textureGather' builtin function
+
+A texture gather operation reads from a 2D, 2D array, cube, or cube array texture, computing a four-component vector as follows:
+ * Find the four texels that would be used in a sampling operation with linear filtering, from mip level 0:
+ - Use the specified coordinate, array index (when present), and offset (when present).
+ - The texels are adjacent, forming a square, when considering their texture space coordinates (u,v).
+ - Selected texels at the texture edge, cube face edge, or cube corners are handled as in ordinary texture sampling.
+ * For each texel, read one channel and convert it into a scalar value.
+ - For non-depth textures, a zero-based component parameter specifies the channel to use.
+ * If the texture format supports the specified channel, i.e. has more than component channels:
+ - Yield scalar value v[component] when the texel value is v.
+ * Otherwise:
+ - Yield 0.0 when component is 1 or 2.
+ - Yield 1.0 when component is 3 (the alpha channel).
+ - For depth textures, yield the texel value. (Depth textures only have one channel.)
+ * Yield the four-component vector, arranging scalars produced by the previous step into components according to the relative coordinates of the texels, as follows:
+ - Result component Relative texel coordinate
+ x (umin,vmax)
+ y (umax,vmax)
+ z (umax,vmin)
+ w (umin,vmin)
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturegather')
+ .desc(
+ `
+C: i32, u32
+T: i32, u32, f32
+
+fn textureGather(component: C, t: texture_2d<T>, s: sampler, coords: vec2<f32>) -> vec4<T>
+fn textureGather(component: C, t: texture_2d<T>, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<T>
+
+Parameters:
+ * component:
+ - The index of the channel to read from the selected texels.
+ - When provided, the component expression must a creation-time expression (e.g. 1).
+ - Its value must be at least 0 and at most 3. Values outside of this range will result in a shader-creation error.
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('T', ['f32-only', 'i32', 'u32'] as const)
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('sampled_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturegather')
+ .desc(
+ `
+C: i32, u32
+T: i32, u32, f32
+
+fn textureGather(component: C, t: texture_cube<T>, s: sampler, coords: vec3<f32>) -> vec4<T>
+
+Parameters:
+ * component:
+ - The index of the channel to read from the selected texels.
+ - When provided, the component expression must a creation-time expression (e.g. 1).
+ - Its value must be at least 0 and at most 3. Values outside of this range will result in a shader-creation error.
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('T', ['f32-only', 'i32', 'u32'] as const)
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('coords', generateCoordBoundaries(3))
+ )
+ .unimplemented();
+
+g.test('sampled_array_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturegather')
+ .desc(
+ `
+C: i32, u32
+T: i32, u32, f32
+
+fn textureGather(component: C, t: texture_2d_array<T>, s: sampler, coords: vec2<f32>, array_index: C) -> vec4<T>
+fn textureGather(component: C, t: texture_2d_array<T>, s: sampler, coords: vec2<f32>, array_index: C, offset: vec2<i32>) -> vec4<T>
+
+Parameters:
+ * component:
+ - The index of the channel to read from the selected texels.
+ - When provided, the component expression must a creation-time expression (e.g. 1).
+ - Its value must be at least 0 and at most 3. Values outside of this range will result in a shader-creation error.
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * array_index: The 0-based texture array index
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('T', ['f32-only', 'i32', 'u32'] as const)
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('sampled_array_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturegather')
+ .desc(
+ `
+C: i32, u32
+T: i32, u32, f32
+
+fn textureGather(component: C, t: texture_cube_array<T>, s: sampler, coords: vec3<f32>, array_index: C) -> vec4<T>
+
+Parameters:
+ * component:
+ - The index of the channel to read from the selected texels.
+ - When provided, the component expression must a creation-time expression (e.g. 1).
+ - Its value must be at least 0 and at most 3. Values outside of this range will result in a shader-creation error.
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * array_index: The 0-based texture array index
+`
+ )
+ .paramsSubcasesOnly(
+ u =>
+ u
+ .combine('T', ['f32-only', 'i32', 'u32'] as const)
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('coords', generateCoordBoundaries(3))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ )
+ .unimplemented();
+
+g.test('depth_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturegather')
+ .desc(
+ `
+fn textureGather(t: texture_depth_2d, s: sampler, coords: vec2<f32>) -> vec4<f32>
+fn textureGather(t: texture_depth_2d, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('depth_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturegather')
+ .desc(
+ `
+fn textureGather(t: texture_depth_cube, s: sampler, coords: vec3<f32>) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(3))
+ )
+ .unimplemented();
+
+g.test('depth_array_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturegather')
+ .desc(
+ `
+C: i32, u32
+
+fn textureGather(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C) -> vec4<f32>
+fn textureGather(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * array_index: The 0-based texture array index
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('depth_array_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturegather')
+ .desc(
+ `
+C: i32, u32
+
+fn textureGather(t: texture_depth_cube_array, s: sampler, coords: vec3<f32>, array_index: C) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * array_index: The 0-based texture array index
+`
+ )
+ .paramsSubcasesOnly(
+ u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('coords', generateCoordBoundaries(3))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.ts
new file mode 100644
index 0000000000..c743883ce8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.ts
@@ -0,0 +1,134 @@
+export const description = `
+Execution tests for the 'textureGatherCompare' builtin function
+
+A texture gather compare operation performs a depth comparison on four texels in a depth texture and collects the results into a single vector, as follows:
+ * Find the four texels that would be used in a depth sampling operation with linear filtering, from mip level 0:
+ - Use the specified coordinate, array index (when present), and offset (when present).
+ - The texels are adjacent, forming a square, when considering their texture space coordinates (u,v).
+ - Selected texels at the texture edge, cube face edge, or cube corners are handled as in ordinary texture sampling.
+ * For each texel, perform a comparison against the depth reference value, yielding a 0.0 or 1.0 value, as controlled by the comparison sampler parameters.
+ * Yield the four-component vector where the components are the comparison results with the texels with relative texel coordinates as follows:
+
+ Result component Relative texel coordinate
+ x (umin,vmax)
+ y (umax,vmax)
+ z (umax,vmin)
+ w (umin,vmin)
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('array_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturegathercompare')
+ .desc(
+ `
+C: i32, u32
+
+fn textureGatherCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32) -> vec4<f32>
+fn textureGatherCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler_comparison
+ * coords: The texture coordinates
+ * array_index: The 0-based array index.
+ * depth_ref: The reference value to compare the sampled depth value against
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4])
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const)
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('array_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturegathercompare')
+ .desc(
+ `
+C: i32, u32
+
+fn textureGatherCompare(t: texture_depth_cube_array, s: sampler_comparison, coords: vec3<f32>, array_index: C, depth_ref: f32) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler_comparison
+ * coords: The texture coordinates
+ * array_index: The 0-based array index.
+ * depth_ref: The reference value to compare the sampled depth value against
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4])
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const)
+ )
+ .unimplemented();
+
+g.test('sampled_array_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturegathercompare')
+ .desc(
+ `
+fn textureGatherCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> vec4<f32>
+fn textureGatherCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler_comparison
+ * coords: The texture coordinates
+ * depth_ref: The reference value to compare the sampled depth value against
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const)
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('sampled_array_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturegathercompare')
+ .desc(
+ `
+fn textureGatherCompare(t: texture_depth_cube, s: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler_comparison
+ * coords: The texture coordinates
+ * depth_ref: The reference value to compare the sampled depth value against
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.ts
new file mode 100644
index 0000000000..30cc4fff52
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.ts
@@ -0,0 +1,185 @@
+export const description = `
+Execution tests for the 'textureLoad' builtin function
+
+Reads a single texel from a texture without sampling or filtering.
+
+Returns the unfiltered texel data.
+
+An out of bounds access occurs if:
+ * any element of coords is outside the range [0, textureDimensions(t, level)) for the corresponding element, or
+ * array_index is outside the range [0, textureNumLayers(t)), or
+ * level is outside the range [0, textureNumLevels(t))
+
+If an out of bounds access occurs, the built-in function returns one of:
+ * The data for some texel within bounds of the texture
+ * A vector (0,0,0,0) or (0,0,0,1) of the appropriate type for non-depth textures
+ * 0.0 for depth textures
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled_1d')
+ .specURL('https://www.w3.org/TR/WGSL/#textureload')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_1d<T>, coords: C, level: C) -> vec4<T>
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+ * level: The mip level, with level 0 containing a full size version of the texture
+`
+ )
+ .params(u =>
+ u
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('coords', generateCoordBoundaries(1))
+ .combine('level', [-1, 0, `numlevels-1`, `numlevels`] as const)
+ )
+ .unimplemented();
+
+g.test('sampled_2d')
+ .specURL('https://www.w3.org/TR/WGSL/#textureload')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_2d<T>, coords: vec2<C>, level: C) -> vec4<T>
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+ * level: The mip level, with level 0 containing a full size version of the texture
+`
+ )
+ .params(u =>
+ u
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('level', [-1, 0, `numlevels-1`, `numlevels`] as const)
+ )
+ .unimplemented();
+
+g.test('sampled_3d')
+ .specURL('https://www.w3.org/TR/WGSL/#textureload')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_3d<T>, coords: vec3<C>, level: C) -> vec4<T>
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+ * level: The mip level, with level 0 containing a full size version of the texture
+`
+ )
+ .params(u =>
+ u
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('level', [-1, 0, `numlevels-1`, `numlevels`] as const)
+ )
+ .unimplemented();
+
+g.test('multisampled')
+ .specURL('https://www.w3.org/TR/WGSL/#textureload')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_multisampled_2d<T>, coords: vec2<C>, sample_index: C)-> vec4<T>
+fn textureLoad(t: texture_depth_multisampled_2d, coords: vec2<C>, sample_index: C)-> f32
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+ * sample_index: The 0-based sample index of the multisampled texture
+`
+ )
+ .params(u =>
+ u
+ .combine('texture_type', [
+ 'texture_multisampled_2d',
+ 'texture_depth_multisampled_2d',
+ ] as const)
+ .beginSubcases()
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('sample_index', [-1, 0, `sampleCount-1`, `sampleCount`] as const)
+ )
+ .unimplemented();
+
+g.test('depth')
+ .specURL('https://www.w3.org/TR/WGSL/#textureload')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_depth_2d, coords: vec2<C>, level: C) -> f32
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+ * level: The mip level, with level 0 containing a full size version of the texture
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('level', [-1, 0, `numlevels-1`, `numlevels`] as const)
+ )
+ .unimplemented();
+
+g.test('external')
+ .specURL('https://www.w3.org/TR/WGSL/#textureload')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_external, coords: vec2<C>) -> vec4<f32>
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u.combine('C', ['i32', 'u32'] as const).combine('coords', generateCoordBoundaries(2))
+ )
+ .unimplemented();
+
+g.test('arrayed')
+ .specURL('https://www.w3.org/TR/WGSL/#textureload')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_2d_array<T>, coords: vec2<C>, array_index: C, level: C) -> vec4<T>
+fn textureLoad(t: texture_depth_2d_array, coords: vec2<C>, array_index: C, level: C) -> f32
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+ * array_index: The 0-based texture array index
+ * level: The mip level, with level 0 containing a full size version of the texture
+`
+ )
+ .params(u =>
+ u
+ .combine('texture_type', ['texture_2d_array', 'texture_depth_2d_array'] as const)
+ .beginSubcases()
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('array_index', [-1, 0, `numlayers-1`, `numlayers`] as const)
+ .combine('level', [-1, 0, `numlevels-1`, `numlevels`] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLayers.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLayers.spec.ts
new file mode 100644
index 0000000000..b845301161
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLayers.spec.ts
@@ -0,0 +1,100 @@
+export const description = `
+Execution tests for the 'textureNumLayers' builtin function
+
+Returns the number of layers (elements) of an array texture.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled')
+ .specURL('https://www.w3.org/TR/WGSL/#texturenumlayers')
+ .desc(
+ `
+T, a sampled type.
+
+fn textureNumLayers(t: texture_2d_array<T>) -> u32
+fn textureNumLayers(t: texture_cube_array<T>) -> u32
+
+Parameters
+ * t The sampled array texture.
+`
+ )
+ .params(u =>
+ u
+ .combine('texture_type', ['texture_2d_array', 'texture_cube_array'] as const)
+ .beginSubcases()
+ .combine('sampled_type', ['f32-only', 'i32', 'u32'] as const)
+ )
+ .unimplemented();
+
+g.test('arrayed')
+ .specURL('https://www.w3.org/TR/WGSL/#texturenumlayers')
+ .desc(
+ `
+fn textureNumLayers(t: texture_depth_2d_array) -> u32
+fn textureNumLayers(t: texture_depth_cube_array) -> u32
+
+Parameters
+ * t The depth array texture.
+`
+ )
+ .params(u =>
+ u.combine('texture_type', ['texture_depth_2d_array', 'texture_depth_cube_array'] as const)
+ )
+ .unimplemented();
+
+g.test('storage')
+ .specURL('https://www.w3.org/TR/WGSL/#texturenumlayers')
+ .desc(
+ `
+F: rgba8unorm
+ rgba8snorm
+ rgba8uint
+ rgba8sint
+ rgba16uint
+ rgba16sint
+ rgba16float
+ r32uint
+ r32sint
+ r32float
+ rg32uint
+ rg32sint
+ rg32float
+ rgba32uint
+ rgba32sint
+ rgba32float
+A: read, write, read_write
+
+fn textureNumLayers(t: texture_storage_2d_array<F,A>) -> u32
+
+Parameters
+ * t The sampled storage array texture.
+`
+ )
+ .params(u =>
+ u
+ .beginSubcases()
+ .combine('texel_format', [
+ 'rgba8unorm',
+ 'rgba8snorm',
+ 'rgba8uint',
+ 'rgba8sint',
+ 'rgba16uint',
+ 'rgba16sint',
+ 'rgba16float',
+ 'r32uint',
+ 'r32sint',
+ 'r32float',
+ 'rg32uint',
+ 'rg32sint',
+ 'rg32float',
+ 'rgba32uint',
+ 'rgba32sint',
+ 'rgba32float',
+ ] as const)
+ .combine('access_mode', ['read', 'write', 'read_write'] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLevels.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLevels.spec.ts
new file mode 100644
index 0000000000..4204397b23
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumLevels.spec.ts
@@ -0,0 +1,65 @@
+export const description = `
+Execution tests for the 'textureNumLevels' builtin function
+
+Returns the number of mip levels of a texture.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled')
+ .specURL('https://www.w3.org/TR/WGSL/#texturenumlevels')
+ .desc(
+ `
+T, a sampled type.
+
+fn textureNumLevels(t: texture_1d<T>) -> u32
+fn textureNumLevels(t: texture_2d<T>) -> u32
+fn textureNumLevels(t: texture_2d_array<T>) -> u32
+fn textureNumLevels(t: texture_3d<T>) -> u32
+fn textureNumLevels(t: texture_cube<T>) -> u32
+fn textureNumLevels(t: texture_cube_array<T>) -> u32
+
+Parameters
+ * t The sampled array texture.
+`
+ )
+ .params(u =>
+ u
+ .combine('texture_type', [
+ 'texture_1d',
+ 'texture_2d',
+ 'texture_2d_array',
+ 'texture_3d',
+ 'texture_cube',
+ 'texture_cube_array`',
+ ] as const)
+ .beginSubcases()
+ .combine('sampled_type', ['f32-only', 'i32', 'u32'] as const)
+ )
+ .unimplemented();
+
+g.test('depth')
+ .specURL('https://www.w3.org/TR/WGSL/#texturenumlevels')
+ .desc(
+ `
+fn textureNumLevels(t: texture_depth_2d) -> u32
+fn textureNumLevels(t: texture_depth_2d_array) -> u32
+fn textureNumLevels(t: texture_depth_cube) -> u32
+fn textureNumLevels(t: texture_depth_cube_array) -> u32
+
+Parameters
+ * t The depth array texture.
+`
+ )
+ .params(u =>
+ u.combine('texture_type', [
+ 'texture_depth_2d',
+ 'texture_depth_2d_array',
+ 'texture_depth_cube',
+ 'texture_depth_cube_array',
+ ] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumSamples.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumSamples.spec.ts
new file mode 100644
index 0000000000..26bda6cd48
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureNumSamples.spec.ts
@@ -0,0 +1,37 @@
+export const description = `
+Execution tests for the 'textureNumSamples' builtin function
+
+Returns the number samples per texel in a multisampled texture.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled')
+ .specURL('https://www.w3.org/TR/WGSL/#texturenumsamples')
+ .desc(
+ `
+T, a sampled type.
+
+fn textureNumSamples(t: texture_multisampled_2d<T>) -> u32
+
+Parameters
+ * t The multisampled texture.
+`
+ )
+ .params(u => u.beginSubcases().combine('sampled_type', ['f32-only', 'i32', 'u32'] as const))
+ .unimplemented();
+
+g.test('depth')
+ .specURL('https://www.w3.org/TR/WGSL/#texturenumsamples')
+ .desc(
+ `
+fn textureNumSamples(t: texture_depth_multisampled_2d) -> u32
+
+Parameters
+ * t The multisampled texture.
+`
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts
new file mode 100644
index 0000000000..f5b01dfc63
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts
@@ -0,0 +1,273 @@
+export const description = `
+Samples a texture.
+
+Must only be used in a fragment shader stage.
+Must only be invoked in uniform control flow.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stage')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+Tests that 'textureSample' can only be called in 'fragment' shaders.
+`
+ )
+ .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
+ .unimplemented();
+
+g.test('control_flow')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+Tests that 'textureSample' can only be called in uniform control flow.
+`
+ )
+ .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
+ .unimplemented();
+
+g.test('sampled_1d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+fn textureSample(t: texture_1d<f32>, s: sampler, coords: f32) -> vec4<f32>
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('coords', generateCoordBoundaries(1))
+ )
+ .unimplemented();
+
+g.test('sampled_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>) -> vec4<f32>
+fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('sampled_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+fn textureSample(t: texture_3d<f32>, s: sampler, coords: vec3<f32>) -> vec4<f32>
+fn textureSample(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, offset: vec3<i32>) -> vec4<f32>
+fn textureSample(t: texture_cube<f32>, s: sampler, coords: vec3<f32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .params(u =>
+ u
+ .combine('texture_type', ['texture_3d', 'texture_cube'] as const)
+ .beginSubcases()
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('offset', generateOffsets(3))
+ )
+ .unimplemented();
+
+g.test('depth_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+fn textureSample(t: texture_depth_2d, s: sampler, coords: vec2<f32>) -> f32
+fn textureSample(t: texture_depth_2d, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('sampled_array_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSample(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C) -> vec4<f32>
+fn textureSample(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('sampled_array_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSample(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: C) -> vec4<f32>
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+`
+ )
+ .paramsSubcasesOnly(
+ u =>
+ u
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('coords', generateCoordBoundaries(3))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ )
+ .unimplemented();
+
+g.test('depth_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+fn textureSample(t: texture_depth_cube, s: sampler, coords: vec3<f32>) -> f32
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('coords', generateCoordBoundaries(3))
+ )
+ .unimplemented();
+
+g.test('depth_array_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSample(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C) -> f32
+fn textureSample(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('depth_array_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSample(t: texture_depth_cube_array, s: sampler, coords: vec3<f32>, array_index: C) -> f32
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+`
+ )
+ .paramsSubcasesOnly(
+ u =>
+ u
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('coords', generateCoordBoundaries(3))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts
new file mode 100644
index 0000000000..786bce4830
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts
@@ -0,0 +1,163 @@
+export const description = `
+Execution tests for the 'textureSampleBias' builtin function
+
+Samples a texture with a bias to the mip level.
+Must only be used in a fragment shader stage.
+Must only be invoked in uniform control flow.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stage')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias')
+ .desc(
+ `
+Tests that 'textureSampleBias' can only be called in 'fragment' shaders.
+`
+ )
+ .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
+ .unimplemented();
+
+g.test('control_flow')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias')
+ .desc(
+ `
+Tests that 'textureSampleBias' can only be called in uniform control flow.
+`
+ )
+ .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
+ .unimplemented();
+
+g.test('sampled_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias')
+ .desc(
+ `
+fn textureSampleBias(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, bias: f32) -> vec4<f32>
+fn textureSampleBias(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, bias: f32, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * bias: The bias to apply to the mip level before sampling. bias must be between -16.0 and 15.99.
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('bias', [-16.1, -16, 0, 1, 15.99, 16] as const)
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('sampled_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias')
+ .desc(
+ `
+fn textureSampleBias(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, bias: f32) -> vec4<f32>
+fn textureSampleBias(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, bias: f32, offset: vec3<i32>) -> vec4<f32>
+fn textureSampleBias(t: texture_cube<f32>, s: sampler, coords: vec3<f32>, bias: f32) -> vec4<f32>
+
+Parameters:
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * bias: The bias to apply to the mip level before sampling. bias must be between -16.0 and 15.99.
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .params(u =>
+ u
+ .combine('texture_type', ['texture_3d', 'texture_cube'] as const)
+ .beginSubcases()
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('bias', [-16.1, -16, 0, 1, 15.99, 16] as const)
+ .combine('offset', generateOffsets(3))
+ )
+ .unimplemented();
+
+g.test('arrayed_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias')
+ .desc(
+ `
+C: i32, u32
+
+fn textureSampleBias(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, bias: f32) -> vec4<f32>
+fn textureSampleBias(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, bias: f32, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * array_index: The 0-based texture array index to sample.
+ * bias: The bias to apply to the mip level before sampling. bias must be between -16.0 and 15.99.
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('bias', [-16.1, -16, 0, 1, 15.99, 16] as const)
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('arrayed_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias')
+ .desc(
+ `
+C: i32, u32
+
+fn textureSampleBias(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: C, bias: f32) -> vec4<f32>
+
+Parameters:
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * array_index: The 0-based texture array index to sample.
+ * bias: The bias to apply to the mip level before sampling. bias must be between -16.0 and 15.99.
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('bias', [-16.1, -16, 0, 1, 15.99, 16] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts
new file mode 100644
index 0000000000..9f723fac2e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts
@@ -0,0 +1,145 @@
+export const description = `
+Samples a depth texture and compares the sampled depth values against a reference value.
+
+Must only be used in a fragment shader stage.
+Must only be invoked in uniform control flow.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stage')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare')
+ .desc(
+ `
+Tests that 'textureSampleCompare' can only be called in 'fragment' shaders.
+`
+ )
+ .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
+ .unimplemented();
+
+g.test('control_flow')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare')
+ .desc(
+ `
+Tests that 'textureSampleCompare' can only be called in uniform control flow.
+`
+ )
+ .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
+ .unimplemented();
+
+g.test('2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare')
+ .desc(
+ `
+fn textureSampleCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32
+fn textureSampleCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * depth_ref The reference value to compare the sampled depth value against.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const)
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare')
+ .desc(
+ `
+fn textureSampleCompare(t: texture_depth_cube, s: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * depth_ref The reference value to compare the sampled depth value against.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const)
+ )
+ .unimplemented();
+
+g.test('arrayed_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSampleCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32) -> f32
+fn textureSampleCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * array_index: The 0-based texture array index to sample.
+ * depth_ref The reference value to compare the sampled depth value against.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const)
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('arrayed_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSampleCompare(t: texture_depth_cube_array, s: sampler_comparison, coords: vec3<f32>, array_index: C, depth_ref: f32) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * array_index: The 0-based texture array index to sample.
+ * depth_ref The reference value to compare the sampled depth value against.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel.spec.ts
new file mode 100644
index 0000000000..500df8a6ec
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel.spec.ts
@@ -0,0 +1,149 @@
+export const description = `
+Samples a depth texture and compares the sampled depth values against a reference value.
+
+The textureSampleCompareLevel function is the same as textureSampleCompare, except that:
+
+ * textureSampleCompareLevel always samples texels from mip level 0.
+ * The function does not compute derivatives.
+ * There is no requirement for textureSampleCompareLevel to be invoked in uniform control flow.
+ * textureSampleCompareLevel may be invoked in any shader stage.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stage')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel')
+ .desc(
+ `
+Tests that 'textureSampleCompareLevel' maybe called in any shader stage.
+`
+ )
+ .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
+ .unimplemented();
+
+g.test('control_flow')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel')
+ .desc(
+ `
+Tests that 'textureSampleCompareLevel' maybe called in non-uniform control flow.
+`
+ )
+ .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
+ .unimplemented();
+
+g.test('2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel')
+ .desc(
+ `
+fn textureSampleCompareLevel(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32
+fn textureSampleCompareLevel(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * depth_ref The reference value to compare the sampled depth value against.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const)
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel')
+ .desc(
+ `
+fn textureSampleCompareLevel(t: texture_depth_cube, s: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * depth_ref The reference value to compare the sampled depth value against.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const)
+ )
+ .unimplemented();
+
+g.test('arrayed_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSampleCompareLevel(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32) -> f32
+fn textureSampleCompareLevel(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * array_index: The 0-based texture array index to sample.
+ * depth_ref The reference value to compare the sampled depth value against.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const)
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('arrayed_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSampleCompareLevel(t: texture_depth_cube_array, s: sampler_comparison, coords: vec3<f32>, array_index: C, depth_ref: f32) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * array_index: The 0-based texture array index to sample.
+ * depth_ref The reference value to compare the sampled depth value against.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleGrad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleGrad.spec.ts
new file mode 100644
index 0000000000..e0d754ece3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleGrad.spec.ts
@@ -0,0 +1,136 @@
+export const description = `
+Samples a texture using explicit gradients.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplegrad')
+ .desc(
+ `
+fn textureSampleGrad(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32>
+fn textureSampleGrad(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled texture.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * ddx The x direction derivative vector used to compute the sampling locations
+ * ddy The y direction derivative vector used to compute the sampling locations
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('sampled_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplegrad')
+ .desc(
+ `
+fn textureSampleGrad(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32>
+fn textureSampleGrad(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>, offset: vec3<i32>) -> vec4<f32>
+fn textureSampleGrad(t: texture_cube<f32>, s: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled texture.
+ * s The sampler type.
+ * ddx The x direction derivative vector used to compute the sampling locations
+ * ddy The y direction derivative vector used to compute the sampling locations
+ * coords The texture coordinates used for sampling.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('offset', generateOffsets(3))
+ )
+ .unimplemented();
+
+g.test('sampled_array_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplegrad')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSampleGrad(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32>
+fn textureSampleGrad(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, ddx: vec2<f32>, ddy: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled texture.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+ * ddx The x direction derivative vector used to compute the sampling locations
+ * ddy The y direction derivative vector used to compute the sampling locations
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('offset', generateOffsets(2))
+ )
+ .unimplemented();
+
+g.test('sampled_array_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplegrad')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSampleGrad(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: C, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled texture.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+ * ddx The x direction derivative vector used to compute the sampling locations
+ * ddy The y direction derivative vector used to compute the sampling locations
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('coords', generateCoordBoundaries(3))
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleLevel.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleLevel.spec.ts
new file mode 100644
index 0000000000..f8073c65d6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleLevel.spec.ts
@@ -0,0 +1,274 @@
+export const description = `
+Samples a texture.
+
+Must only be used in a fragment shader stage.
+Must only be invoked in uniform control flow.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel')
+ .desc(
+ `
+fn textureSampleLevel(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, level: f32) -> vec4<f32>
+fn textureSampleLevel(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, level: f32, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('offset', generateOffsets(2))
+ .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
+ )
+ .unimplemented();
+
+g.test('sampled_array_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSampleLevel(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, level: f32) -> vec4<f32>
+fn textureSampleLevel(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, level: f32, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('offset', generateOffsets(2))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
+ )
+ .unimplemented();
+
+g.test('sampled_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel')
+ .desc(
+ `
+fn textureSampleLevel(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, level: f32) -> vec4<f32>
+fn textureSampleLevel(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, level: f32, offset: vec3<i32>) -> vec4<f32>
+fn textureSampleLevel(t: texture_cube<f32>, s: sampler, coords: vec3<f32>, level: f32) -> vec4<f32>
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .params(u =>
+ u
+ .combine('texture_type', ['texture_3d', 'texture_cube'] as const)
+ .beginSubcases()
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('offset', generateOffsets(3))
+ .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
+ )
+ .unimplemented();
+
+g.test('sampled_array_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSampleLevel(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: C, level: f32) -> vec4<f32>
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('offset', generateOffsets(3))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
+ )
+ .unimplemented();
+
+g.test('depth_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSampleLevel(t: texture_depth_2d, s: sampler, coords: vec2<f32>, level: C) -> f32
+fn textureSampleLevel(t: texture_depth_2d, s: sampler, coords: vec2<f32>, level: C, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('offset', generateOffsets(2))
+ .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
+ )
+ .unimplemented();
+
+g.test('depth_array_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSampleLevel(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C, level: C) -> f32
+fn textureSampleLevel(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C, level: C, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * array_index The 0-based texture array index to sample.
+ * coords The texture coordinates used for sampling.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('offset', generateOffsets(2))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
+ )
+ .unimplemented();
+
+g.test('depth_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureSampleLevel(t: texture_depth_cube, s: sampler, coords: vec3<f32>, level: C) -> f32
+fn textureSampleLevel(t: texture_depth_cube_array, s: sampler, coords: vec3<f32>, array_index: C, level: C) -> f32
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+ )
+ .params(u =>
+ u
+ .combine('texture_type', ['texture_depth_cube', 'texture_depth_cube_array'] as const)
+ .beginSubcases()
+ .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'])
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ .combine('coords', generateCoordBoundaries(3))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureStore.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureStore.spec.ts
new file mode 100644
index 0000000000..efef971e24
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureStore.spec.ts
@@ -0,0 +1,122 @@
+export const description = `
+Writes a single texel to a texture.
+
+The channel format T depends on the storage texel format F.
+See the texel format table for the mapping of texel format to channel format.
+
+Note: An out-of-bounds access occurs if:
+ * any element of coords is outside the range [0, textureDimensions(t)) for the corresponding element, or
+ * array_index is outside the range of [0, textureNumLayers(t))
+
+If an out-of-bounds access occurs, the built-in function may do any of the following:
+ * not be executed
+ * store value to some in bounds texel
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TexelFormats } from '../../../../types.js';
+
+import { generateCoordBoundaries } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('store_1d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturestore')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureStore(t: texture_storage_1d<F,write>, coords: C, value: vec4<T>)
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * value The new texel value
+`
+ )
+ .params(u =>
+ u
+ .combineWithParams(TexelFormats)
+ .beginSubcases()
+ .combine('coords', generateCoordBoundaries(1))
+ .combine('C', ['i32', 'u32'] as const)
+ )
+ .unimplemented();
+
+g.test('store_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturestore')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureStore(t: texture_storage_2d<F,write>, coords: vec2<C>, value: vec4<T>)
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * value The new texel value
+`
+ )
+ .params(u =>
+ u
+ .combineWithParams(TexelFormats)
+ .beginSubcases()
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('C', ['i32', 'u32'] as const)
+ )
+ .unimplemented();
+
+g.test('store_array_2d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturestore')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureStore(t: texture_storage_2d_array<F,write>, coords: vec2<C>, array_index: C, value: vec4<T>)
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * array_index The 0-based texture array index
+ * coords The texture coordinates used for sampling.
+ * value The new texel value
+`
+ )
+ .params(
+ u =>
+ u
+ .combineWithParams(TexelFormats)
+ .beginSubcases()
+ .combine('coords', generateCoordBoundaries(2))
+ .combine('C', ['i32', 'u32'] as const)
+ .combine('C_value', [-1, 0, 1, 2, 3, 4] as const)
+ /* array_index not param'd as out-of-bounds is implementation specific */
+ )
+ .unimplemented();
+
+g.test('store_3d_coords')
+ .specURL('https://www.w3.org/TR/WGSL/#texturestore')
+ .desc(
+ `
+C is i32 or u32
+
+fn textureStore(t: texture_storage_3d<F,write>, coords: vec3<C>, value: vec4<T>)
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * value The new texel value
+`
+ )
+ .params(u =>
+ u
+ .combineWithParams(TexelFormats)
+ .beginSubcases()
+ .combine('coords', generateCoordBoundaries(3))
+ .combine('C', ['i32', 'u32'] as const)
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts
new file mode 100644
index 0000000000..6fd4887f35
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts
@@ -0,0 +1,158 @@
+export const description = `
+Execution tests for the 'transpose' builtin function
+
+T is AbstractFloat, f32, or f16
+@const transpose(e: matRxC<T> ) -> matCxR<T>
+Returns the transpose of e.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeF16, TypeF32, TypeMat } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ sparseMatrixF16Range,
+ sparseMatrixF32Range,
+ sparseMatrixF64Range,
+} from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_matCxR_[non_]const
+const f32_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixToMatrixCases(
+ sparseMatrixF32Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.transposeInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_matCxR_[non_]const
+const f16_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixToMatrixCases(
+ sparseMatrixF16Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.transposeInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: abstract_matCxR
+const abstract_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`abstract_mat${cols}x${rows}`]: () => {
+ return FP.abstract.generateMatrixToMatrixCases(
+ sparseMatrixF64Range(cols, rows),
+ 'finite',
+ FP.abstract.transposeInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('transpose', {
+ ...f32_cases,
+ ...f16_cases,
+ ...abstract_cases,
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('cols', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(`abstract_mat${cols}x${rows}`);
+ await run(
+ t,
+ abstractBuiltin('transpose'),
+ [TypeMat(cols, rows, TypeAbstractFloat)],
+ TypeMat(rows, cols, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('cols', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const'
+ ? `f32_mat${cols}x${rows}_const`
+ : `f32_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ builtin('transpose'),
+ [TypeMat(cols, rows, TypeF32)],
+ TypeMat(rows, cols, TypeF32),
+ t.params,
+ cases
+ );
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('cols', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const'
+ ? `f16_mat${cols}x${rows}_const`
+ : `f16_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ builtin('transpose'),
+ [TypeMat(cols, rows, TypeF16)],
+ TypeMat(rows, cols, TypeF16),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts
new file mode 100644
index 0000000000..63cd8470f5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts
@@ -0,0 +1,75 @@
+export const description = `
+Execution tests for the 'trunc' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn trunc(e: T ) -> T
+Returns the nearest whole number whose absolute value is less than or equal to e.
+Component-wise when T is a vector.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('trunc', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.truncInterval);
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f16.truncInterval);
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ fullF64Range(),
+ 'unfiltered',
+ FP.abstract.truncInterval
+ );
+ },
+});
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(t, abstractBuiltin('trunc'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ });
+
+g.test('f32')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f32 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('f32');
+ await run(t, builtin('trunc'), [TypeF32], TypeF32, t.params, cases);
+ });
+
+g.test('f16')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`f16 tests`)
+ .params(u =>
+ u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('f16');
+ await run(t, builtin('trunc'), [TypeF16], TypeF16, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts
new file mode 100644
index 0000000000..4a0bf075e9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts
@@ -0,0 +1,48 @@
+export const description = `
+Decomposes a 32-bit value into two 16-bit chunks, and reinterpets each chunk as
+a floating point value.
+Component i of the result is the f32 representation of v, where v is the
+interpretation of bits 16×i through 16×i+15 of e as an IEEE-754 binary16 value.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('unpack2x16float', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack2x16floatInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack2x16floatInterval
+ );
+ },
+});
+
+g.test('unpack')
+ .specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
+ .desc(
+ `
+@const fn unpack2x16float(e: u32) -> vec2<f32>
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
+ await run(t, builtin('unpack2x16float'), [TypeU32], TypeVec(2, TypeF32), t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts
new file mode 100644
index 0000000000..195cfd9a01
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts
@@ -0,0 +1,48 @@
+export const description = `
+Decomposes a 32-bit value into two 16-bit chunks, then reinterprets each chunk
+as a signed normalized floating point value.
+Component i of the result is max(v ÷ 32767, -1), where v is the interpretation
+of bits 16×i through 16×i+15 of e as a twos-complement signed integer.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('unpack2x16snorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack2x16snormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack2x16snormInterval
+ );
+ },
+});
+
+g.test('unpack')
+ .specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
+ .desc(
+ `
+@const fn unpack2x16snorm(e: u32) -> vec2<f32>
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
+ await run(t, builtin('unpack2x16snorm'), [TypeU32], TypeVec(2, TypeF32), t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts
new file mode 100644
index 0000000000..16b4e6397c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts
@@ -0,0 +1,48 @@
+export const description = `
+Decomposes a 32-bit value into two 16-bit chunks, then reinterprets each chunk
+as an unsigned normalized floating point value.
+Component i of the result is v ÷ 65535, where v is the interpretation of bits
+16×i through 16×i+15 of e as an unsigned integer.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('unpack2x16unorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack2x16unormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack2x16unormInterval
+ );
+ },
+});
+
+g.test('unpack')
+ .specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
+ .desc(
+ `
+@const fn unpack2x16unorm(e: u32) -> vec2<f32>
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
+ await run(t, builtin('unpack2x16unorm'), [TypeU32], TypeVec(2, TypeF32), t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts
new file mode 100644
index 0000000000..7ea8d51918
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts
@@ -0,0 +1,48 @@
+export const description = `
+Decomposes a 32-bit value into four 8-bit chunks, then reinterprets each chunk
+as a signed normalized floating point value.
+Component i of the result is max(v ÷ 127, -1), where v is the interpretation of
+bits 8×i through 8×i+7 of e as a twos-complement signed integer.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('unpack4x8snorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack4x8snormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack4x8snormInterval
+ );
+ },
+});
+
+g.test('unpack')
+ .specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
+ .desc(
+ `
+@const fn unpack4x8snorm(e: u32) -> vec4<f32>
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
+ await run(t, builtin('unpack4x8snorm'), [TypeU32], TypeVec(4, TypeF32), t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts
new file mode 100644
index 0000000000..bf54d23c12
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts
@@ -0,0 +1,48 @@
+export const description = `
+Decomposes a 32-bit value into four 8-bit chunks, then reinterprets each chunk
+as an unsigned normalized floating point value.
+Component i of the result is v ÷ 255, where v is the interpretation of bits 8×i
+through 8×i+7 of e as an unsigned integer.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('unpack4x8unorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack4x8unormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack4x8unormInterval
+ );
+ },
+});
+
+g.test('unpack')
+ .specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
+ .desc(
+ `
+@const fn unpack4x8unorm(e: u32) -> vec4<f32>
+`
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
+ await run(t, builtin('unpack4x8unorm'), [TypeU32], TypeVec(4, TypeF32), t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/utils.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/utils.ts
new file mode 100644
index 0000000000..9cbee00939
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/utils.ts
@@ -0,0 +1,45 @@
+/**
+ * Generates the boundary entries for the given number of dimensions
+ *
+ * @param numDimensions: The number of dimensions to generate for
+ * @returns an array of generated coord boundaries
+ */
+export function generateCoordBoundaries(numDimensions: number) {
+ const ret = ['in-bounds'];
+
+ if (numDimensions < 1 || numDimensions > 3) {
+ throw new Error(`invalid numDimensions: ${numDimensions}`);
+ }
+
+ const name = 'xyz';
+ for (let i = 0; i < numDimensions; ++i) {
+ for (const j of ['min', 'max']) {
+ for (const k of ['wrap', 'boundary']) {
+ ret.push(`${name[i]}-${j}-${k}`);
+ }
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * Generates a set of offset values to attempt in the range [-9, 8].
+ *
+ * @param numDimensions: The number of dimensions to generate for
+ * @return an array of generated offset values
+ */
+export function generateOffsets(numDimensions: number) {
+ if (numDimensions < 2 || numDimensions > 3) {
+ throw new Error(`generateOffsets: invalid numDimensions: ${numDimensions}`);
+ }
+ const ret: Array<undefined | Array<number>> = [undefined];
+ for (const val of [-9, -8, 0, 1, 7, 8]) {
+ const v = [];
+ for (let i = 0; i < numDimensions; ++i) {
+ v.push(val);
+ }
+ ret.push(v);
+ }
+ return ret;
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupBarrier.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupBarrier.spec.ts
new file mode 100644
index 0000000000..74e0f12325
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupBarrier.spec.ts
@@ -0,0 +1,38 @@
+export const description = `
+'workgroupBarrier' affects memory and atomic operations in the workgroup address space.
+
+All synchronization functions execute a control barrier with Acquire/Release memory ordering.
+That is, all synchronization functions, and affected memory and atomic operations are ordered
+in program order relative to the synchronization function. Additionally, the affected memory
+and atomic operations program-ordered before the synchronization function must be visible to
+all other threads in the workgroup before any affected memory or atomic operation program-ordered
+after the synchronization function is executed by a member of the workgroup. All synchronization
+functions use the Workgroup memory scope. All synchronization functions have a Workgroup
+execution scope.
+
+All synchronization functions must only be used in the compute shader stage.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stage')
+ .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions')
+ .desc(
+ `
+All synchronization functions must only be used in the compute shader stage.
+`
+ )
+ .params(u => u.combine('stage', ['vertex', 'fragment', 'compute'] as const))
+ .unimplemented();
+
+g.test('barrier')
+ .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions')
+ .desc(
+ `
+fn workgroupBarrier()
+`
+ )
+ .unimplemented();