diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary')
30 files changed, 1209 insertions, 634 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/address_of_and_indirection.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/address_of_and_indirection.spec.ts new file mode 100644 index 0000000000..c4961558e0 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/address_of_and_indirection.spec.ts @@ -0,0 +1,171 @@ +export const description = ` +Execution Tests for unary address-of and indirection (dereference) +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { keysOf } from '../../../../../common/util/data_tables.js'; +import { GPUTest } from '../../../../gpu_test.js'; +import { ScalarKind, scalarType } from '../../../../util/conversion.js'; +import { sparseScalarF32Range } from '../../../../util/math.js'; +import { + allButConstInputSource, + basicExpressionWithPredeclarationBuilder, + run, +} from '../expression.js'; + +export const g = makeTestGroup(GPUTest); + +// All the ways to deref an expression +const kDerefCases = { + deref_address_of_identifier: { + wgsl: '(*(&a))', + requires_pointer_composite_access: false, + }, + deref_pointer: { + wgsl: '(*p)', + requires_pointer_composite_access: false, + }, + address_of_identifier: { + wgsl: '(&a)', + requires_pointer_composite_access: true, + }, + pointer: { + wgsl: 'p', + requires_pointer_composite_access: true, + }, +}; + +g.test('deref') + .specURL('https://www.w3.org/TR/WGSL/#indirection') + .desc( + ` +Expression: *e + +Pointer expression dereference. +` + ) + .params(u => + u + .combine('inputSource', allButConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('scalarType', ['bool', 'u32', 'i32', 'f32', 'f16'] as ScalarKind[]) + .combine('derefType', keysOf(kDerefCases)) + .filter(p => !kDerefCases[p.derefType].requires_pointer_composite_access) + ) + .beforeAllSubcases(t => { + if (t.params.scalarType === 'f16') { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + } + }) + .fn(async t => { + const ty = scalarType(t.params.scalarType); + const cases = sparseScalarF32Range().map(e => { + return { input: ty.create(e), expected: ty.create(e) }; + }); + const elemType = ty.kind; + const type = t.params.vectorize ? `vec${t.params.vectorize}<${elemType}>` : elemType; + const shaderBuilder = basicExpressionWithPredeclarationBuilder( + value => `get_dereferenced_value(${value})`, + `fn get_dereferenced_value(value: ${type}) -> ${type} { + var a = value; + let p = &a; + return ${kDerefCases[t.params.derefType].wgsl}; + }` + ); + await run(t, shaderBuilder, [ty], ty, t.params, cases); + }); + +g.test('deref_index') + .specURL('https://www.w3.org/TR/WGSL/#logical-expr') + .desc( + ` +Expression: (*e)[index] + +Pointer expression dereference as lhs of index accessor expression +` + ) + .params(u => + u + .combine('inputSource', allButConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('scalarType', ['bool', 'u32', 'i32', 'f32', 'f16'] as ScalarKind[]) + .combine('derefType', keysOf(kDerefCases)) + ) + .beforeAllSubcases(t => { + if (t.params.scalarType === 'f16') { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + } + }) + .fn(async t => { + if ( + kDerefCases[t.params.derefType].requires_pointer_composite_access && + !t.hasLanguageFeature('pointer_composite_access') + ) { + return; + } + + const ty = scalarType(t.params.scalarType); + const cases = sparseScalarF32Range().map(e => { + return { input: ty.create(e), expected: ty.create(e) }; + }); + const elemType = ty.kind; + const type = t.params.vectorize ? `vec${t.params.vectorize}<${elemType}>` : elemType; + const shaderBuilder = basicExpressionWithPredeclarationBuilder( + value => `get_dereferenced_value(${value})`, + `fn get_dereferenced_value(value: ${type}) -> ${type} { + var a = array<${type}, 1>(value); + let p = &a; + return ${kDerefCases[t.params.derefType].wgsl}[0]; + }` + ); + await run(t, shaderBuilder, [ty], ty, t.params, cases); + }); + +g.test('deref_member') + .specURL('https://www.w3.org/TR/WGSL/#logical-expr') + .desc( + ` +Expression: (*e).member + +Pointer expression dereference as lhs of member accessor expression +` + ) + .params(u => + u + .combine('inputSource', allButConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + .combine('scalarType', ['bool', 'u32', 'i32', 'f32', 'f16'] as ScalarKind[]) + .combine('derefType', keysOf(kDerefCases)) + ) + .beforeAllSubcases(t => { + if (t.params.scalarType === 'f16') { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + } + }) + .fn(async t => { + if ( + kDerefCases[t.params.derefType].requires_pointer_composite_access && + !t.hasLanguageFeature('pointer_composite_access') + ) { + return; + } + + const ty = scalarType(t.params.scalarType); + const cases = sparseScalarF32Range().map(e => { + return { input: ty.create(e), expected: ty.create(e) }; + }); + const elemType = ty.kind; + const type = t.params.vectorize ? `vec${t.params.vectorize}<${elemType}>` : elemType; + const shaderBuilder = basicExpressionWithPredeclarationBuilder( + value => `get_dereferenced_value(${value})`, + `struct S { + m : ${type} + } + fn get_dereferenced_value(value: ${type}) -> ${type} { + var a = S(value); + let p = &a; + return ${kDerefCases[t.params.derefType].wgsl}.m; + }` + ); + await run(t, shaderBuilder, [ty], ty, t.params, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.cache.ts new file mode 100644 index 0000000000..4f274e8922 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.cache.ts @@ -0,0 +1,13 @@ +import { FP } from '../../../../util/floating_point.js'; +import { scalarF64Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; + +export const d = makeCaseCache('unary/af_arithmetic', { + negation: () => { + return FP.abstract.generateScalarToIntervalCases( + scalarF64Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }), + 'unfiltered', + FP.abstract.negationInterval + ); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts index 182c0d76a9..686d4b7c4a 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts @@ -1,29 +1,17 @@ export const description = ` -Execution Tests for AbstractFloat arithmetic unary expression operations +Execution Tests for Type.abstractFloat arithmetic unary expression operations `; import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { TypeAbstractFloat } from '../../../../util/conversion.js'; -import { FP } from '../../../../util/floating_point.js'; -import { fullF64Range } from '../../../../util/math.js'; -import { makeCaseCache } from '../case_cache.js'; +import { Type } from '../../../../util/conversion.js'; import { onlyConstInputSource, run } from '../expression.js'; -import { abstractUnary } from './unary.js'; +import { d } from './af_arithmetic.cache.js'; +import { abstractFloatUnary } from './unary.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unary/af_arithmetic', { - negation: () => { - return FP.abstract.generateScalarToIntervalCases( - fullF64Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }), - 'unfiltered', - FP.abstract.negationInterval - ); - }, -}); - g.test('negation') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( @@ -39,5 +27,13 @@ Accuracy: Correctly rounded ) .fn(async t => { const cases = await d.get('negation'); - await run(t, abstractUnary('-'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); + await run( + t, + abstractFloatUnary('-'), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases, + 1 + ); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.cache.ts new file mode 100644 index 0000000000..7c607927fe --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.cache.ts @@ -0,0 +1,51 @@ +import { kValue } from '../../../../util/constants.js'; +import { abstractFloat } from '../../../../util/conversion.js'; +import { FP } from '../../../../util/floating_point.js'; +import { + isSubnormalNumberF64, + limitedScalarF64Range, + scalarF64Range, +} from '../../../../util/math.js'; +import { reinterpretU64AsF64 } from '../../../../util/reinterpret.js'; +import { makeCaseCache } from '../case_cache.js'; + +export const d = makeCaseCache('unary/af_assignment', { + abstract: () => { + const inputs = [ + // Values that are useful for debugging the underlying framework/shader code, since it cannot be directly unit tested. + 0, + 0.5, + 0.5, + 1, + -1, + reinterpretU64AsF64(0x7000_0000_0000_0001n), // smallest magnitude negative subnormal with non-zero mantissa + reinterpretU64AsF64(0x0000_0000_0000_0001n), // smallest magnitude positive subnormal with non-zero mantissa + reinterpretU64AsF64(0x600a_aaaa_5555_5555n), // negative subnormal with obvious pattern + reinterpretU64AsF64(0x000a_aaaa_5555_5555n), // positive subnormal with obvious pattern + reinterpretU64AsF64(0x0010_0000_0000_0001n), // smallest magnitude negative normal with non-zero mantissa + reinterpretU64AsF64(0x0010_0000_0000_0001n), // smallest magnitude positive normal with non-zero mantissa + reinterpretU64AsF64(0xf555_5555_aaaa_aaaan), // negative normal with obvious pattern + reinterpretU64AsF64(0x5555_5555_aaaa_aaaan), // positive normal with obvious pattern + reinterpretU64AsF64(0xffef_ffff_ffff_ffffn), // largest magnitude negative normal + reinterpretU64AsF64(0x7fef_ffff_ffff_ffffn), // largest magnitude positive normal + // WebGPU implementation stressing values + ...scalarF64Range(), + ]; + return inputs.map(f => { + return { + input: abstractFloat(f), + expected: isSubnormalNumberF64(f) ? abstractFloat(0) : abstractFloat(f), + }; + }); + }, + f32: () => { + return limitedScalarF64Range(kValue.f32.negative.min, kValue.f32.positive.max).map(f => { + return { input: abstractFloat(f), expected: FP.f32.correctlyRoundedInterval(f) }; + }); + }, + f16: () => { + return limitedScalarF64Range(kValue.f16.negative.min, kValue.f16.positive.max).map(f => { + return { input: abstractFloat(f), expected: FP.f16.correctlyRoundedInterval(f) }; + }); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts index 141d87d0f2..001c47e117 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts @@ -4,20 +4,17 @@ Execution Tests for assignment of AbstractFloats import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { kValue } from '../../../../util/constants.js'; -import { abstractFloat, TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../util/conversion.js'; -import { FP } from '../../../../util/floating_point.js'; -import { filteredF64Range, fullF64Range, isSubnormalNumberF64 } from '../../../../util/math.js'; -import { reinterpretU64AsF64 } from '../../../../util/reinterpret.js'; -import { makeCaseCache } from '../case_cache.js'; +import { Type } from '../../../../util/conversion.js'; import { + ShaderBuilder, abstractFloatShaderBuilder, basicExpressionBuilder, onlyConstInputSource, run, - ShaderBuilder, } from '../expression.js'; +import { d } from './af_assignment.cache.js'; + function concrete_assignment(): ShaderBuilder { return basicExpressionBuilder(value => `${value}`); } @@ -28,47 +25,6 @@ function abstract_assignment(): ShaderBuilder { export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unary/af_assignment', { - abstract: () => { - const inputs = [ - // Values that are useful for debugging the underlying framework/shader code, since it cannot be directly unit tested. - 0, - 0.5, - 0.5, - 1, - -1, - reinterpretU64AsF64(0x7000_0000_0000_0001n), // smallest magnitude negative subnormal with non-zero mantissa - reinterpretU64AsF64(0x0000_0000_0000_0001n), // smallest magnitude positive subnormal with non-zero mantissa - reinterpretU64AsF64(0x600a_aaaa_5555_5555n), // negative subnormal with obvious pattern - reinterpretU64AsF64(0x000a_aaaa_5555_5555n), // positive subnormal with obvious pattern - reinterpretU64AsF64(0x0010_0000_0000_0001n), // smallest magnitude negative normal with non-zero mantissa - reinterpretU64AsF64(0x0010_0000_0000_0001n), // smallest magnitude positive normal with non-zero mantissa - reinterpretU64AsF64(0xf555_5555_aaaa_aaaan), // negative normal with obvious pattern - reinterpretU64AsF64(0x5555_5555_aaaa_aaaan), // positive normal with obvious pattern - reinterpretU64AsF64(0xffef_ffff_ffff_ffffn), // largest magnitude negative normal - reinterpretU64AsF64(0x7fef_ffff_ffff_ffffn), // largest magnitude positive normal - // WebGPU implementation stressing values - ...fullF64Range(), - ]; - return inputs.map(f => { - return { - input: abstractFloat(f), - expected: isSubnormalNumberF64(f) ? abstractFloat(0) : abstractFloat(f), - }; - }); - }, - f32: () => { - return filteredF64Range(kValue.f32.negative.min, kValue.f32.positive.max).map(f => { - return { input: abstractFloat(f), expected: FP.f32.correctlyRoundedInterval(f) }; - }); - }, - f16: () => { - return filteredF64Range(kValue.f16.negative.min, kValue.f16.positive.max).map(f => { - return { input: abstractFloat(f), expected: FP.f16.correctlyRoundedInterval(f) }; - }); - }, -}); - g.test('abstract') .specURL('https://www.w3.org/TR/WGSL/#floating-point-conversion') .desc( @@ -79,7 +35,15 @@ testing that extracting abstract floats works .params(u => u.combine('inputSource', onlyConstInputSource)) .fn(async t => { const cases = await d.get('abstract'); - await run(t, abstract_assignment(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1); + await run( + t, + abstract_assignment(), + [Type.abstractFloat], + Type.abstractFloat, + t.params, + cases, + 1 + ); }); g.test('f32') @@ -92,7 +56,7 @@ concretizing to f32 .params(u => u.combine('inputSource', onlyConstInputSource)) .fn(async t => { const cases = await d.get('f32'); - await run(t, concrete_assignment(), [TypeAbstractFloat], TypeF32, t.params, cases); + await run(t, concrete_assignment(), [Type.abstractFloat], Type.f32, t.params, cases); }); g.test('f16') @@ -108,5 +72,5 @@ concretizing to f16 .params(u => u.combine('inputSource', onlyConstInputSource)) .fn(async t => { const cases = await d.get('f16'); - await run(t, concrete_assignment(), [TypeAbstractFloat], TypeF16, t.params, cases); + await run(t, concrete_assignment(), [Type.abstractFloat], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.cache.ts new file mode 100644 index 0000000000..904c0bc700 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.cache.ts @@ -0,0 +1,11 @@ +import { abstractInt } from '../../../../util/conversion.js'; +import { fullI64Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; + +export const d = makeCaseCache('unary/ai_arithmetic', { + negation: () => { + return fullI64Range().map(e => { + return { input: abstractInt(e), expected: abstractInt(-e) }; + }); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.spec.ts new file mode 100644 index 0000000000..625a38b2d5 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.spec.ts @@ -0,0 +1,30 @@ +export const description = ` +Execution Tests for the abstract integer arithmetic unary expression operations +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../gpu_test.js'; +import { Type } from '../../../../util/conversion.js'; +import { onlyConstInputSource, run } from '../expression.js'; + +import { d } from './ai_arithmetic.cache.js'; +import { abstractIntUnary } from './unary.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('negation') + .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr') + .desc( + ` +Expression: -x +` + ) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('negation'); + await run(t, abstractIntUnary('-'), [Type.abstractInt], Type.abstractInt, t.params, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.cache.ts new file mode 100644 index 0000000000..0fa4f2efc2 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.cache.ts @@ -0,0 +1,21 @@ +import { abstractInt, i32, u32 } from '../../../../util/conversion.js'; +import { fullI32Range, fullI64Range, fullU32Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; + +export const d = makeCaseCache('unary/ai_assignment', { + abstract: () => { + return fullI64Range().map(n => { + return { input: abstractInt(n), expected: abstractInt(n) }; + }); + }, + i32: () => { + return fullI32Range().map(n => { + return { input: abstractInt(BigInt(n)), expected: i32(n) }; + }); + }, + u32: () => { + return fullU32Range().map(n => { + return { input: abstractInt(BigInt(n)), expected: u32(n) }; + }); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.spec.ts new file mode 100644 index 0000000000..fe1ba2d9fc --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.spec.ts @@ -0,0 +1,65 @@ +export const description = ` +Execution Tests for assignment of AbstractInts +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../gpu_test.js'; +import { Type } from '../../../../util/conversion.js'; +import { + ShaderBuilder, + abstractIntShaderBuilder, + basicExpressionBuilder, + onlyConstInputSource, + run, +} from '../expression.js'; + +import { d } from './ai_assignment.cache.js'; + +function concrete_assignment(): ShaderBuilder { + return basicExpressionBuilder(value => `${value}`); +} + +function abstract_assignment(): ShaderBuilder { + return abstractIntShaderBuilder(value => `${value}`); +} + +export const g = makeTestGroup(GPUTest); + +g.test('abstract') + .specURL('https://www.w3.org/TR/WGSL/#abstract-types') + .desc( + ` +testing that extracting abstract ints works +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('abstract'); + await run(t, abstract_assignment(), [Type.abstractInt], Type.abstractInt, t.params, cases, 1); + }); + +g.test('i32') + .specURL('https://www.w3.org/TR/WGSL/#i32-builtin') + .desc( + ` +concretizing to i32 +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('i32'); + await run(t, concrete_assignment(), [Type.abstractInt], Type.i32, t.params, cases); + }); + +g.test('u32') + .specURL('https://www.w3.org/TR/WGSL/#u32-builtin') + .desc( + ` +concretizing to u32 +` + ) + .params(u => u.combine('inputSource', onlyConstInputSource)) + .fn(async t => { + const cases = await d.get('u32'); + await run(t, concrete_assignment(), [Type.abstractInt], Type.u32, t.params, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_complement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_complement.spec.ts new file mode 100644 index 0000000000..507ae219cb --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_complement.spec.ts @@ -0,0 +1,32 @@ +export const description = ` +Execution Tests for the Type.abstractInt bitwise complement operation +`; + +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { GPUTest } from '../../../../gpu_test.js'; +import { abstractInt, Type } from '../../../../util/conversion.js'; +import { fullI64Range } from '../../../../util/math.js'; +import { onlyConstInputSource, run } from '../expression.js'; + +import { abstractIntUnary } from './unary.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('complement') + .specURL('https://www.w3.org/TR/WGSL/#bit-expr') + .desc( + ` +Expression: ~x +` + ) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = fullI64Range().map(e => { + return { input: abstractInt(e), expected: abstractInt(~e) }; + }); + await run(t, abstractIntUnary('~'), [Type.abstractInt], Type.abstractInt, t.params, cases); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.cache.ts new file mode 100644 index 0000000000..5b16c49c42 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.cache.ts @@ -0,0 +1,54 @@ +import { anyOf } from '../../../../util/compare.js'; +import { ScalarValue, bool, f16, f32, i32, u32 } from '../../../../util/conversion.js'; +import { + fullI32Range, + fullU32Range, + isSubnormalNumberF16, + isSubnormalNumberF32, + scalarF16Range, + scalarF32Range, +} from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; + +export const d = makeCaseCache('unary/bool_conversion', { + bool: () => { + return [ + { input: bool(true), expected: bool(true) }, + { input: bool(false), expected: bool(false) }, + ]; + }, + u32: () => { + return fullU32Range().map(u => { + return { input: u32(u), expected: u === 0 ? bool(false) : bool(true) }; + }); + }, + i32: () => { + return fullI32Range().map(i => { + return { input: i32(i), expected: i === 0 ? bool(false) : bool(true) }; + }); + }, + f32: () => { + return scalarF32Range().map(f => { + const expected: ScalarValue[] = []; + if (f !== 0) { + expected.push(bool(true)); + } + if (isSubnormalNumberF32(f)) { + expected.push(bool(false)); + } + return { input: f32(f), expected: anyOf(...expected) }; + }); + }, + f16: () => { + return scalarF16Range().map(f => { + const expected: ScalarValue[] = []; + if (f !== 0) { + expected.push(bool(true)); + } + if (isSubnormalNumberF16(f)) { + expected.push(bool(false)); + } + return { input: f16(f), expected: anyOf(...expected) }; + }); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts index 8fcfed339f..55d01d3eda 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts @@ -4,78 +4,14 @@ Execution Tests for the boolean conversion operations import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { anyOf } from '../../../../util/compare.js'; -import { - bool, - f32, - f16, - i32, - Scalar, - TypeBool, - TypeF32, - TypeF16, - TypeI32, - TypeU32, - u32, -} from '../../../../util/conversion.js'; -import { - fullF32Range, - fullF16Range, - fullI32Range, - fullU32Range, - isSubnormalNumberF32, - isSubnormalNumberF16, -} from '../../../../util/math.js'; -import { makeCaseCache } from '../case_cache.js'; -import { allInputSources, run, ShaderBuilder } from '../expression.js'; +import { Type } from '../../../../util/conversion.js'; +import { ShaderBuilder, allInputSources, run } from '../expression.js'; +import { d } from './bool_conversion.cache.js'; import { unary } from './unary.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unary/bool_conversion', { - bool: () => { - return [ - { input: bool(true), expected: bool(true) }, - { input: bool(false), expected: bool(false) }, - ]; - }, - u32: () => { - return fullU32Range().map(u => { - return { input: u32(u), expected: u === 0 ? bool(false) : bool(true) }; - }); - }, - i32: () => { - return fullI32Range().map(i => { - return { input: i32(i), expected: i === 0 ? bool(false) : bool(true) }; - }); - }, - f32: () => { - return fullF32Range().map(f => { - const expected: Scalar[] = []; - if (f !== 0) { - expected.push(bool(true)); - } - if (isSubnormalNumberF32(f)) { - expected.push(bool(false)); - } - return { input: f32(f), expected: anyOf(...expected) }; - }); - }, - f16: () => { - return fullF16Range().map(f => { - const expected: Scalar[] = []; - if (f !== 0) { - expected.push(bool(true)); - } - if (isSubnormalNumberF16(f)) { - expected.push(bool(false)); - } - return { input: f16(f), expected: anyOf(...expected) }; - }); - }, -}); - /** Generate expression builder based on how the test case is to be vectorized */ function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder { return vectorize === undefined ? unary('bool') : unary(`vec${vectorize}<bool>`); @@ -95,7 +31,14 @@ Identity operation ) .fn(async t => { const cases = await d.get('bool'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeBool, t.params, cases); + await run( + t, + vectorizeToExpression(t.params.vectorize), + [Type.bool], + Type.bool, + t.params, + cases + ); }); g.test('u32') @@ -113,7 +56,7 @@ The result is false if e is 0, and true otherwise. ) .fn(async t => { const cases = await d.get('u32'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeBool, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.bool, t.params, cases); }); g.test('i32') @@ -131,7 +74,7 @@ The result is false if e is 0, and true otherwise. ) .fn(async t => { const cases = await d.get('i32'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeBool, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.bool, t.params, cases); }); g.test('f32') @@ -149,7 +92,7 @@ The result is false if e is 0.0 or -0.0, and true otherwise. ) .fn(async t => { const cases = await d.get('f32'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeBool, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.bool, t.params, cases); }); g.test('f16') @@ -170,5 +113,5 @@ The result is false if e is 0.0 or -0.0, and true otherwise. }) .fn(async t => { const cases = await d.get('f16'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeBool, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.bool, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts index 01eaaab43a..58d4b9fc0f 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts @@ -4,7 +4,7 @@ Execution Tests for the boolean unary logical expression operations import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { bool, TypeBool } from '../../../../util/conversion.js'; +import { bool, Type } from '../../../../util/conversion.js'; import { allInputSources, run } from '../expression.js'; import { unary } from './unary.js'; @@ -29,5 +29,5 @@ Logical negation. The result is true when e is false and false when e is true. C { input: bool(false), expected: bool(true) }, ]; - await run(t, unary('!'), [TypeBool], TypeBool, t.params, cases); + await run(t, unary('!'), [Type.bool], Type.bool, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.cache.ts new file mode 100644 index 0000000000..7d9ee35eec --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.cache.ts @@ -0,0 +1,13 @@ +import { FP } from '../../../../util/floating_point.js'; +import { scalarF16Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; + +export const d = makeCaseCache('unary/f16_arithmetic', { + negation: () => { + return FP.f16.generateScalarToIntervalCases( + scalarF16Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }), + 'unfiltered', + FP.f16.negationInterval + ); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts index 83d7579c07..813bbf7a64 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts @@ -4,26 +4,14 @@ Execution Tests for the f16 arithmetic unary expression operations import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { TypeF16 } from '../../../../util/conversion.js'; -import { FP } from '../../../../util/floating_point.js'; -import { fullF16Range } from '../../../../util/math.js'; -import { makeCaseCache } from '../case_cache.js'; +import { Type } from '../../../../util/conversion.js'; import { allInputSources, run } from '../expression.js'; +import { d } from './f16_arithmetic.cache.js'; import { unary } from './unary.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unary/f16_arithmetic', { - negation: () => { - return FP.f16.generateScalarToIntervalCases( - fullF16Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }), - 'unfiltered', - FP.f16.negationInterval - ); - }, -}); - g.test('negation') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( @@ -40,5 +28,5 @@ Accuracy: Correctly rounded }) .fn(async t => { const cases = await d.get('negation'); - await run(t, unary('-'), [TypeF16], TypeF16, t.params, cases); + await run(t, unary('-'), [Type.f16], Type.f16, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.cache.ts new file mode 100644 index 0000000000..bb0eb091dd --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.cache.ts @@ -0,0 +1,135 @@ +import { abstractInt, bool, f16, i32, u32 } from '../../../../util/conversion.js'; +import { FP, FPInterval } from '../../../../util/floating_point.js'; +import { fullI32Range, fullI64Range, fullU32Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; + +const f16FiniteRangeInterval = new FPInterval( + 'f16', + FP.f16.constants().negative.min, + FP.f16.constants().positive.max +); + +// Cases: f32_matCxR_[non_]const +// Note that f32 values may be not exactly representable in f16 and/or out of range. +const f32_mat_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( + FP.f32.sparseMatrixRange(cols, rows), + nonConst ? 'unfiltered' : 'finite', + FP.f16.correctlyRoundedMatrix + ); + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +// Cases: f16_matCxR_[non_]const +const f16_mat_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'}`]: () => { + // Input matrix is of f16 types, use f16.generateMatrixToMatrixCases. + return FP.f16.generateMatrixToMatrixCases( + FP.f16.sparseMatrixRange(cols, rows), + nonConst ? 'unfiltered' : 'finite', + FP.f16.correctlyRoundedMatrix + ); + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +// Cases: abstract_float_matCxR +// Note that abstract float values may be not exactly representable in f16 +// and/or out of range. +const abstract_float_mat_cases = ([2, 3, 4] as const) + .flatMap(cols => + ([2, 3, 4] as const).map(rows => ({ + [`abstract_float_mat${cols}x${rows}`]: () => { + return FP.abstract.generateMatrixToMatrixCases( + FP.abstract.sparseMatrixRange(cols, rows), + 'finite', + FP.f16.correctlyRoundedMatrix + ); + }, + })) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('unary/f16_conversion', { + bool: () => { + return [ + { input: bool(true), expected: f16(1.0) }, + { input: bool(false), expected: f16(0.0) }, + ]; + }, + u32_non_const: () => { + return [...fullU32Range(), 65504].map(u => { + return { input: u32(u), expected: FP.f16.correctlyRoundedInterval(u) }; + }); + }, + u32_const: () => { + return [...fullU32Range(), 65504] + .filter(v => f16FiniteRangeInterval.contains(v)) + .map(u => { + return { input: u32(u), expected: FP.f16.correctlyRoundedInterval(u) }; + }); + }, + i32_non_const: () => { + return [...fullI32Range(), 65504, -65504].map(i => { + return { input: i32(i), expected: FP.f16.correctlyRoundedInterval(i) }; + }); + }, + i32_const: () => { + return [...fullI32Range(), 65504, -65504] + .filter(v => f16FiniteRangeInterval.contains(v)) + .map(i => { + return { input: i32(i), expected: FP.f16.correctlyRoundedInterval(i) }; + }); + }, + abstract_int: () => { + return [...fullI64Range(), 65504n, -65504n] + .filter(v => f16FiniteRangeInterval.contains(Number(v))) + .map(i => { + return { input: abstractInt(i), expected: FP.f16.correctlyRoundedInterval(Number(i)) }; + }); + }, + // Note that f32 values may be not exactly representable in f16 and/or out of range. + f32_non_const: () => { + return FP.f32.generateScalarToIntervalCases( + [...FP.f32.scalarRange(), 65535.996, -65535.996], + 'unfiltered', + FP.f16.correctlyRoundedInterval + ); + }, + f32_const: () => { + return FP.f32.generateScalarToIntervalCases( + [...FP.f32.scalarRange(), 65535.996, -65535.996], + 'finite', + FP.f16.correctlyRoundedInterval + ); + }, + // Note that abstract float values may be not exactly representable in f16. + abstract_float: () => { + return FP.abstract.generateScalarToIntervalCases( + [...FP.abstract.scalarRange(), 65535.996, -65535.996], + 'finite', + FP.f16.correctlyRoundedInterval + ); + }, + // All f16 values are exactly representable in f16. + f16: () => { + return FP.f16.scalarRange().map(f => { + return { input: f16(f), expected: FP.f16.correctlyRoundedInterval(f) }; + }); + }, + ...f32_mat_cases, + ...f16_mat_cases, + ...abstract_float_mat_cases, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts index 9eb84f0270..92bd9c6a07 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts @@ -4,132 +4,14 @@ Execution Tests for the f32 conversion operations import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { - bool, - f16, - i32, - TypeBool, - TypeF32, - TypeF16, - TypeI32, - TypeMat, - TypeU32, - u32, -} from '../../../../util/conversion.js'; -import { FP, FPInterval } from '../../../../util/floating_point.js'; -import { - fullF32Range, - fullF16Range, - fullI32Range, - fullU32Range, - sparseMatrixF32Range, - sparseMatrixF16Range, -} from '../../../../util/math.js'; -import { makeCaseCache } from '../case_cache.js'; -import { allInputSources, run, ShaderBuilder } from '../expression.js'; +import { Type } from '../../../../util/conversion.js'; +import { ShaderBuilder, allInputSources, run, onlyConstInputSource } from '../expression.js'; +import { d } from './f16_conversion.cache.js'; import { unary } from './unary.js'; export const g = makeTestGroup(GPUTest); -const f16FiniteRangeInterval = new FPInterval( - 'f32', - FP.f16.constants().negative.min, - FP.f16.constants().positive.max -); - -// Cases: f32_matCxR_[non_]const -// Note that f32 values may be not exactly representable in f16 and/or out of range. -const f32_mat_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.f16.correctlyRoundedMatrix - ); - }, - })) - ) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: f16_matCxR_[non_]const -const f16_mat_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'}`]: () => { - // Input matrix is of f16 types, use f16.generateMatrixToMatrixCases. - return FP.f16.generateMatrixToMatrixCases( - sparseMatrixF16Range(cols, rows), - nonConst ? 'unfiltered' : 'finite', - FP.f16.correctlyRoundedMatrix - ); - }, - })) - ) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -export const d = makeCaseCache('unary/f16_conversion', { - bool: () => { - return [ - { input: bool(true), expected: f16(1.0) }, - { input: bool(false), expected: f16(0.0) }, - ]; - }, - u32_non_const: () => { - return [...fullU32Range(), 65504].map(u => { - return { input: u32(u), expected: FP.f16.correctlyRoundedInterval(u) }; - }); - }, - u32_const: () => { - return [...fullU32Range(), 65504] - .filter(v => f16FiniteRangeInterval.contains(v)) - .map(u => { - return { input: u32(u), expected: FP.f16.correctlyRoundedInterval(u) }; - }); - }, - i32_non_const: () => { - return [...fullI32Range(), 65504, -65504].map(i => { - return { input: i32(i), expected: FP.f16.correctlyRoundedInterval(i) }; - }); - }, - i32_const: () => { - return [...fullI32Range(), 65504, -65504] - .filter(v => f16FiniteRangeInterval.contains(v)) - .map(i => { - return { input: i32(i), expected: FP.f16.correctlyRoundedInterval(i) }; - }); - }, - // Note that f32 values may be not exactly representable in f16 and/or out of range. - f32_non_const: () => { - return FP.f32.generateScalarToIntervalCases( - [...fullF32Range(), 65535.996, -65535.996], - 'unfiltered', - FP.f16.correctlyRoundedInterval - ); - }, - f32_const: () => { - return FP.f32.generateScalarToIntervalCases( - [...fullF32Range(), 65535.996, -65535.996], - 'finite', - FP.f16.correctlyRoundedInterval - ); - }, - // All f16 values are exactly representable in f16. - f16: () => { - return fullF16Range().map(f => { - return { input: f16(f), expected: FP.f16.correctlyRoundedInterval(f) }; - }); - }, - ...f32_mat_cases, - ...f16_mat_cases, -}); - /** Generate a ShaderBuilder based on how the test case is to be vectorized */ function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder { return vectorize === undefined ? unary('f16') : unary(`vec${vectorize}<f16>`); @@ -157,7 +39,7 @@ The result is 1.0 if e is true and 0.0 otherwise }) .fn(async t => { const cases = await d.get('bool'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeF16, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.bool], Type.f16, t.params, cases); }); g.test('u32') @@ -177,7 +59,7 @@ Converted to f16, +/-Inf if out of range }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeF16, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.f16, t.params, cases); }); g.test('i32') @@ -197,7 +79,36 @@ Converted to f16, +/-Inf if out of range }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'i32_const' : 'i32_non_const'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeF16, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.f16, t.params, cases); + }); + +g.test('abstract_int') + .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function') + .desc( + ` +f16(e), where e is an AbstractInt + +Converted to f16, +/-Inf if out of range +` + ) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + }) + .fn(async t => { + const cases = await d.get('abstract_int'); + await run( + t, + vectorizeToExpression(t.params.vectorize), + [Type.abstractInt], + Type.f16, + t.params, + cases + ); }); g.test('f32') @@ -217,7 +128,7 @@ Correctly rounded to f16 }) .fn(async t => { const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeF16, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.f16, t.params, cases); }); g.test('f32_mat') @@ -243,8 +154,8 @@ g.test('f32_mat') await run( t, matrixExperession(cols, rows), - [TypeMat(cols, rows, TypeF32)], - TypeMat(cols, rows, TypeF16), + [Type.mat(cols, rows, Type.f32)], + Type.mat(cols, rows, Type.f16), t.params, cases ); @@ -267,7 +178,7 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeF16, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.f16, t.params, cases); }); g.test('f16_mat') @@ -293,8 +204,63 @@ g.test('f16_mat') await run( t, matrixExperession(cols, rows), - [TypeMat(cols, rows, TypeF16)], - TypeMat(cols, rows, TypeF16), + [Type.mat(cols, rows, Type.f16)], + Type.mat(cols, rows, Type.f16), + t.params, + cases + ); + }); + +g.test('abstract_float') + .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function') + .desc( + ` +f16(e), where e is an AbstractFloat + +Correctly rounded to f16 +` + ) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + }) + .fn(async t => { + const cases = await d.get('abstract_float'); + await run( + t, + vectorizeToExpression(t.params.vectorize), + [Type.abstractFloat], + Type.f16, + t.params, + cases + ); + }); + +g.test('abstract_float_mat') + .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions') + .desc(`AbstractFloat matrix to f16 matrix tests`) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('cols', [2, 3, 4] as const) + .combine('rows', [2, 3, 4] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] }); + }) + .fn(async t => { + const cols = t.params.cols; + const rows = t.params.rows; + const cases = await d.get(`abstract_float_mat${cols}x${rows}`); + await run( + t, + matrixExperession(cols, rows), + [Type.mat(cols, rows, Type.abstractFloat)], + Type.mat(cols, rows, Type.f16), t.params, cases ); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.cache.ts new file mode 100644 index 0000000000..b23ed3216b --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.cache.ts @@ -0,0 +1,13 @@ +import { FP } from '../../../../util/floating_point.js'; +import { scalarF32Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; + +export const d = makeCaseCache('unary/f32_arithmetic', { + negation: () => { + return FP.f32.generateScalarToIntervalCases( + scalarF32Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }), + 'unfiltered', + FP.f32.negationInterval + ); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts index f53cff46d8..3bb48705e8 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts @@ -4,26 +4,14 @@ Execution Tests for the f32 arithmetic unary expression operations import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { TypeF32 } from '../../../../util/conversion.js'; -import { FP } from '../../../../util/floating_point.js'; -import { fullF32Range } from '../../../../util/math.js'; -import { makeCaseCache } from '../case_cache.js'; +import { Type } from '../../../../util/conversion.js'; import { allInputSources, run } from '../expression.js'; +import { d } from './f32_arithmetic.cache.js'; import { unary } from './unary.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unary/f32_arithmetic', { - negation: () => { - return FP.f32.generateScalarToIntervalCases( - fullF32Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }), - 'unfiltered', - FP.f32.negationInterval - ); - }, -}); - g.test('negation') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( @@ -37,5 +25,5 @@ Accuracy: Correctly rounded ) .fn(async t => { const cases = await d.get('negation'); - await run(t, unary('-'), [TypeF32], TypeF32, t.params, cases); + await run(t, unary('-'), [Type.f32], Type.f32, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.cache.ts new file mode 100644 index 0000000000..f61435f07c --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.cache.ts @@ -0,0 +1,79 @@ +import { bool, f16, f32, i32, u32 } from '../../../../util/conversion.js'; +import { FP } from '../../../../util/floating_point.js'; +import { + fullI32Range, + fullU32Range, + scalarF16Range, + scalarF32Range, + sparseMatrixF16Range, + sparseMatrixF32Range, +} from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; + +// Cases: f32_matCxR_[non_]const +const f32_mat_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.correctlyRoundedMatrix + ); + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +// Cases: f16_matCxR_[non_]const +// Note that all f16 values are exactly representable in f32. +const f16_mat_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'}`]: () => { + // Input matrix is of f16 types, use f16.generateMatrixToMatrixCases. + return FP.f16.generateMatrixToMatrixCases( + sparseMatrixF16Range(cols, rows), + nonConst ? 'unfiltered' : 'finite', + FP.f32.correctlyRoundedMatrix + ); + }, + })) + ) + ) + .reduce((a, b) => ({ ...a, ...b }), {}); + +export const d = makeCaseCache('unary/f32_conversion', { + bool: () => { + return [ + { input: bool(true), expected: f32(1.0) }, + { input: bool(false), expected: f32(0.0) }, + ]; + }, + u32: () => { + return fullU32Range().map(u => { + return { input: u32(u), expected: FP.f32.correctlyRoundedInterval(u) }; + }); + }, + i32: () => { + return fullI32Range().map(i => { + return { input: i32(i), expected: FP.f32.correctlyRoundedInterval(i) }; + }); + }, + f32: () => { + return scalarF32Range().map(f => { + return { input: f32(f), expected: FP.f32.correctlyRoundedInterval(f) }; + }); + }, + // All f16 values are exactly representable in f32. + f16: () => { + return scalarF16Range().map(f => { + return { input: f16(f), expected: FP.f32.correctlyRoundedInterval(f) }; + }); + }, + ...f32_mat_cases, + ...f16_mat_cases, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts index 223b13c2d5..464fdee44e 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts @@ -4,103 +4,14 @@ Execution Tests for the f32 conversion operations import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { - bool, - f32, - f16, - i32, - TypeBool, - TypeF32, - TypeF16, - TypeI32, - TypeMat, - TypeU32, - u32, -} from '../../../../util/conversion.js'; -import { FP } from '../../../../util/floating_point.js'; -import { - fullF32Range, - fullF16Range, - fullI32Range, - fullU32Range, - sparseMatrixF32Range, - sparseMatrixF16Range, -} from '../../../../util/math.js'; -import { makeCaseCache } from '../case_cache.js'; -import { allInputSources, run, ShaderBuilder } from '../expression.js'; +import { Type } from '../../../../util/conversion.js'; +import { ShaderBuilder, allInputSources, run } from '../expression.js'; +import { d } from './f32_conversion.cache.js'; import { unary } from './unary.js'; export const g = makeTestGroup(GPUTest); -// Cases: f32_matCxR_[non_]const -const f32_mat_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.correctlyRoundedMatrix - ); - }, - })) - ) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -// Cases: f16_matCxR_[non_]const -// Note that all f16 values are exactly representable in f32. -const f16_mat_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'}`]: () => { - // Input matrix is of f16 types, use f16.generateMatrixToMatrixCases. - return FP.f16.generateMatrixToMatrixCases( - sparseMatrixF16Range(cols, rows), - nonConst ? 'unfiltered' : 'finite', - FP.f32.correctlyRoundedMatrix - ); - }, - })) - ) - ) - .reduce((a, b) => ({ ...a, ...b }), {}); - -export const d = makeCaseCache('unary/f32_conversion', { - bool: () => { - return [ - { input: bool(true), expected: f32(1.0) }, - { input: bool(false), expected: f32(0.0) }, - ]; - }, - u32: () => { - return fullU32Range().map(u => { - return { input: u32(u), expected: FP.f32.correctlyRoundedInterval(u) }; - }); - }, - i32: () => { - return fullI32Range().map(i => { - return { input: i32(i), expected: FP.f32.correctlyRoundedInterval(i) }; - }); - }, - f32: () => { - return fullF32Range().map(f => { - return { input: f32(f), expected: FP.f32.correctlyRoundedInterval(f) }; - }); - }, - // All f16 values are exactly representable in f32. - f16: () => { - return fullF16Range().map(f => { - return { input: f16(f), expected: FP.f32.correctlyRoundedInterval(f) }; - }); - }, - ...f32_mat_cases, - ...f16_mat_cases, -}); - /** Generate a ShaderBuilder based on how the test case is to be vectorized */ function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder { return vectorize === undefined ? unary('f32') : unary(`vec${vectorize}<f32>`); @@ -125,7 +36,7 @@ The result is 1.0 if e is true and 0.0 otherwise ) .fn(async t => { const cases = await d.get('bool'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeF32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.bool], Type.f32, t.params, cases); }); g.test('u32') @@ -142,7 +53,7 @@ Converted to f32 ) .fn(async t => { const cases = await d.get('u32'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeF32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.f32, t.params, cases); }); g.test('i32') @@ -159,7 +70,7 @@ Converted to f32 ) .fn(async t => { const cases = await d.get('i32'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeF32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.f32, t.params, cases); }); g.test('f32') @@ -176,7 +87,7 @@ Identity operation ) .fn(async t => { const cases = await d.get('f32'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeF32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.f32, t.params, cases); }); g.test('f32_mat') @@ -199,8 +110,8 @@ g.test('f32_mat') await run( t, matrixExperession(cols, rows), - [TypeMat(cols, rows, TypeF32)], - TypeMat(cols, rows, TypeF32), + [Type.mat(cols, rows, Type.f32)], + Type.mat(cols, rows, Type.f32), t.params, cases ); @@ -223,7 +134,7 @@ g.test('f16') }) .fn(async t => { const cases = await d.get('f16'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeF32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.f32, t.params, cases); }); g.test('f16_mat') @@ -249,8 +160,8 @@ g.test('f16_mat') await run( t, matrixExperession(cols, rows), - [TypeMat(cols, rows, TypeF16)], - TypeMat(cols, rows, TypeF32), + [Type.mat(cols, rows, Type.f16)], + Type.mat(cols, rows, Type.f32), t.params, cases ); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.cache.ts new file mode 100644 index 0000000000..b7206bcf45 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.cache.ts @@ -0,0 +1,11 @@ +import { i32 } from '../../../../util/conversion.js'; +import { fullI32Range } from '../../../../util/math.js'; +import { makeCaseCache } from '../case_cache.js'; + +export const d = makeCaseCache('unary/i32_arithmetic', { + negation: () => { + return fullI32Range().map(e => { + return { input: i32(e), expected: i32(-e) }; + }); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts index 14519b8967..a7d16a96cd 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts @@ -4,23 +4,14 @@ Execution Tests for the i32 arithmetic unary expression operations import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { i32, TypeI32 } from '../../../../util/conversion.js'; -import { fullI32Range } from '../../../../util/math.js'; -import { makeCaseCache } from '../case_cache.js'; +import { Type } from '../../../../util/conversion.js'; import { allInputSources, run } from '../expression.js'; +import { d } from './i32_arithmetic.cache.js'; import { unary } from './unary.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unary/i32_arithmetic', { - negation: () => { - return fullI32Range().map(e => { - return { input: i32(e), expected: i32(-e) }; - }); - }, -}); - g.test('negation') .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation') .desc( @@ -33,5 +24,5 @@ Expression: -x ) .fn(async t => { const cases = await d.get('negation'); - await run(t, unary('-'), [TypeI32], TypeI32, t.params, cases); + await run(t, unary('-'), [Type.i32], Type.i32, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts index e8bda51b51..01e5728d70 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts @@ -4,23 +4,14 @@ Execution Tests for the i32 bitwise complement operation import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { i32, TypeI32 } from '../../../../util/conversion.js'; +import { i32, Type } from '../../../../util/conversion.js'; import { fullI32Range } from '../../../../util/math.js'; -import { makeCaseCache } from '../case_cache.js'; import { allInputSources, run } from '../expression.js'; import { unary } from './unary.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unary/i32_complement', { - complement: () => { - return fullI32Range().map(e => { - return { input: i32(e), expected: i32(~e) }; - }); - }, -}); - g.test('i32_complement') .specURL('https://www.w3.org/TR/WGSL/#bit-expr') .desc( @@ -32,6 +23,8 @@ Expression: ~x u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - const cases = await d.get('complement'); - await run(t, unary('~'), [TypeI32], TypeI32, t.params, cases); + const cases = fullI32Range().map(e => { + return { input: i32(e), expected: i32(~e) }; + }); + await run(t, unary('~'), [Type.i32], Type.i32, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.cache.ts new file mode 100644 index 0000000000..1c2d01548d --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.cache.ts @@ -0,0 +1,116 @@ +import { kValue } from '../../../../util/constants.js'; +import { + abstractFloat, + abstractInt, + bool, + f16, + f32, + i32, + u32, +} from '../../../../util/conversion.js'; +import { + fullI32Range, + fullU32Range, + quantizeToF16, + quantizeToF32, + scalarF16Range, + scalarF32Range, + scalarF64Range, +} from '../../../../util/math.js'; +import { reinterpretU32AsI32 } from '../../../../util/reinterpret.js'; +import { makeCaseCache } from '../case_cache.js'; + +export const d = makeCaseCache('unary/i32_conversion', { + bool: () => { + return [ + { input: bool(true), expected: i32(1) }, + { input: bool(false), expected: i32(0) }, + ]; + }, + abstractInt: () => { + return fullI32Range().map(i => { + return { input: abstractInt(BigInt(i)), expected: i32(i) }; + }); + }, + u32: () => { + return fullU32Range().map(u => { + return { input: u32(u), expected: i32(reinterpretU32AsI32(u)) }; + }); + }, + i32: () => { + return fullI32Range().map(i => { + return { input: i32(i), expected: i32(i) }; + }); + }, + abstractFloat: () => { + return scalarF64Range().map(f => { + // Handles zeros and subnormals + if (Math.abs(f) < 1.0) { + return { input: abstractFloat(f), expected: i32(0) }; + } + + if (f <= kValue.i32.negative.min) { + return { input: abstractFloat(f), expected: i32(kValue.i32.negative.min) }; + } + + if (f >= kValue.i32.positive.max) { + return { input: abstractFloat(f), expected: i32(kValue.i32.positive.max) }; + } + + // All i32s are representable as f64, and both AbstractFloat and number + // are f64 internally, so there is no need for special casing like f32 and + // f16 below. + return { input: abstractFloat(f), expected: i32(Math.trunc(f)) }; + }); + }, + f32: () => { + return scalarF32Range().map(f => { + // Handles zeros and subnormals + if (Math.abs(f) < 1.0) { + return { input: f32(f), expected: i32(0) }; + } + + if (f <= kValue.i32.negative.min) { + return { input: f32(f), expected: i32(kValue.i32.negative.min) }; + } + + if (f >= kValue.i32.positive.max) { + return { input: f32(f), expected: i32(kValue.i32.positive.max) }; + } + + // All f32 no larger than 2^24 has a precise interger part and a fractional part, just need + // to trunc towards 0 for the result integer. + if (Math.abs(f) <= 2 ** 24) { + return { input: f32(f), expected: i32(Math.trunc(f)) }; + } + + // All f32s between 2 ** 24 and kValue.i32.negative.min/.positive.max are + // integers, so in theory one could use them directly, expect that number + // is actually f64 internally, so they need to be quantized to f32 first. + // Cannot just use trunc here, since that might produce a i32 value that + // is precise in f64, but not in f32. + return { input: f32(f), expected: i32(quantizeToF32(f)) }; + }); + }, + f16: () => { + // Note that finite f16 values are always in range of i32. + return scalarF16Range().map(f => { + // Handles zeros and subnormals + if (Math.abs(f) < 1.0) { + return { input: f16(f), expected: i32(0) }; + } + + // All f16 no larger than <= 2^12 has a precise interger part and a fractional part, just need + // to trunc towards 0 for the result integer. + if (Math.abs(f) <= 2 ** 12) { + return { input: f16(f), expected: i32(Math.trunc(f)) }; + } + + // All f16s larger than 2 ** 12 are integers, so in theory one could use them directly, expect + // that number is actually f64 internally, so they need to be quantized to f16 first. + // Cannot just use trunc here, since that might produce a i32 value that is precise in f64, + // but not in f16. + return { input: f16(f), expected: i32(quantizeToF16(f)) }; + }); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts index a77aa0e4d3..b47ffe0d07 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts @@ -4,104 +4,14 @@ Execution Tests for the i32 conversion operations import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { kValue } from '../../../../util/constants.js'; -import { - bool, - f32, - f16, - i32, - TypeBool, - TypeF32, - TypeF16, - TypeI32, - TypeU32, - u32, -} from '../../../../util/conversion.js'; -import { - fullF32Range, - fullF16Range, - fullI32Range, - fullU32Range, - quantizeToF32, - quantizeToF16, -} from '../../../../util/math.js'; -import { reinterpretU32AsI32 } from '../../../../util/reinterpret.js'; -import { makeCaseCache } from '../case_cache.js'; -import { allInputSources, run, ShaderBuilder } from '../expression.js'; +import { Type } from '../../../../util/conversion.js'; +import { ShaderBuilder, allInputSources, run, onlyConstInputSource } from '../expression.js'; +import { d } from './i32_conversion.cache.js'; import { unary } from './unary.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unary/i32_conversion', { - bool: () => { - return [ - { input: bool(true), expected: i32(1) }, - { input: bool(false), expected: i32(0) }, - ]; - }, - u32: () => { - return fullU32Range().map(u => { - return { input: u32(u), expected: i32(reinterpretU32AsI32(u)) }; - }); - }, - i32: () => { - return fullI32Range().map(i => { - return { input: i32(i), expected: i32(i) }; - }); - }, - f32: () => { - return fullF32Range().map(f => { - // Handles zeros and subnormals - if (Math.abs(f) < 1.0) { - return { input: f32(f), expected: i32(0) }; - } - - if (f <= kValue.i32.negative.min) { - return { input: f32(f), expected: i32(kValue.i32.negative.min) }; - } - - if (f >= kValue.i32.positive.max) { - return { input: f32(f), expected: i32(kValue.i32.positive.max) }; - } - - // All f32 no larger than 2^24 has a precise interger part and a fractional part, just need - // to trunc towards 0 for the result integer. - if (Math.abs(f) <= 2 ** 24) { - return { input: f32(f), expected: i32(Math.trunc(f)) }; - } - - // All f32s between 2 ** 24 and kValue.i32.negative.min/.positive.max are - // integers, so in theory one could use them directly, expect that number - // is actually f64 internally, so they need to be quantized to f32 first. - // Cannot just use trunc here, since that might produce a i32 value that - // is precise in f64, but not in f32. - return { input: f32(f), expected: i32(quantizeToF32(f)) }; - }); - }, - f16: () => { - // Note that finite f16 values are always in range of i32. - return fullF16Range().map(f => { - // Handles zeros and subnormals - if (Math.abs(f) < 1.0) { - return { input: f16(f), expected: i32(0) }; - } - - // All f16 no larger than <= 2^12 has a precise interger part and a fractional part, just need - // to trunc towards 0 for the result integer. - if (Math.abs(f) <= 2 ** 12) { - return { input: f16(f), expected: i32(Math.trunc(f)) }; - } - - // All f16s larger than 2 ** 12 are integers, so in theory one could use them directly, expect - // that number is actually f64 internally, so they need to be quantized to f16 first. - // Cannot just use trunc here, since that might produce a i32 value that is precise in f64, - // but not in f16. - return { input: f16(f), expected: i32(quantizeToF16(f)) }; - }); - }, -}); - /** Generate a ShaderBuilder based on how the test case is to be vectorized */ function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder { return vectorize === undefined ? unary('i32') : unary(`vec${vectorize}<i32>`); @@ -121,7 +31,7 @@ The result is 1u if e is true and 0u otherwise ) .fn(async t => { const cases = await d.get('bool'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeI32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.bool], Type.i32, t.params, cases); }); g.test('u32') @@ -138,7 +48,7 @@ Reinterpretation of bits ) .fn(async t => { const cases = await d.get('u32'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeI32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.i32, t.params, cases); }); g.test('i32') @@ -155,7 +65,7 @@ Identity operation ) .fn(async t => { const cases = await d.get('i32'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeI32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.i32, t.params, cases); }); g.test('f32') @@ -172,7 +82,7 @@ e is converted to i32, rounding towards zero ) .fn(async t => { const cases = await d.get('f32'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeI32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.i32, t.params, cases); }); g.test('f16') @@ -192,5 +102,57 @@ e is converted to u32, rounding towards zero }) .fn(async t => { const cases = await d.get('f16'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeI32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.i32, t.params, cases); + }); + +g.test('abstract_int') + .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function') + .desc( + ` +i32(e), where e is an AbstractInt + +Identity operation if e is in bounds for i32, otherwise shader creation error +` + ) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstractInt'); + await run( + t, + vectorizeToExpression(t.params.vectorize), + [Type.abstractInt], + Type.i32, + t.params, + cases + ); + }); + +g.test('abstract_float') + .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function') + .desc( + ` +i32(e), where e is an AbstractFloat + +e is converted to i32, rounding towards zero +` + ) + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstractFloat'); + await run( + t, + vectorizeToExpression(t.params.vectorize), + [Type.abstractFloat], + Type.i32, + t.params, + cases + ); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts index 446e0918bd..74251a32c6 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts @@ -4,23 +4,14 @@ Execution Tests for the u32 bitwise complement operation import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { u32, TypeU32 } from '../../../../util/conversion.js'; +import { Type, u32 } from '../../../../util/conversion.js'; import { fullU32Range } from '../../../../util/math.js'; -import { makeCaseCache } from '../case_cache.js'; import { allInputSources, run } from '../expression.js'; import { unary } from './unary.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unary/u32_complement', { - complement: () => { - return fullU32Range().map(e => { - return { input: u32(e), expected: u32(~e) }; - }); - }, -}); - g.test('u32_complement') .specURL('https://www.w3.org/TR/WGSL/#bit-expr') .desc( @@ -32,6 +23,8 @@ Expression: ~x u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) ) .fn(async t => { - const cases = await d.get('complement'); - await run(t, unary('~'), [TypeU32], TypeU32, t.params, cases); + const cases = fullU32Range().map(e => { + return { input: u32(e), expected: u32(~e) }; + }); + await run(t, unary('~'), [Type.u32], Type.u32, t.params, cases); }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.cache.ts new file mode 100644 index 0000000000..1ef307810f --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.cache.ts @@ -0,0 +1,107 @@ +import { kValue } from '../../../../util/constants.js'; +import { + abstractFloat, + abstractInt, + bool, + f16, + f32, + i32, + u32, +} from '../../../../util/conversion.js'; +import { + fullI32Range, + fullU32Range, + quantizeToF16, + quantizeToF32, + scalarF16Range, + scalarF32Range, + scalarF64Range, +} from '../../../../util/math.js'; +import { reinterpretI32AsU32 } from '../../../../util/reinterpret.js'; +import { makeCaseCache } from '../case_cache.js'; + +export const d = makeCaseCache('unary/u32_conversion', { + bool: () => { + return [ + { input: bool(true), expected: u32(1) }, + { input: bool(false), expected: u32(0) }, + ]; + }, + abstractInt: () => { + return fullU32Range().map(u => { + return { input: abstractInt(BigInt(u)), expected: u32(u) }; + }); + }, + u32: () => { + return fullU32Range().map(u => { + return { input: u32(u), expected: u32(u) }; + }); + }, + i32: () => { + return fullI32Range().map(i => { + return { input: i32(i), expected: u32(reinterpretI32AsU32(i)) }; + }); + }, + abstractFloat: () => { + return [...scalarF64Range(), -1].map(f => { + // Handles zeros, subnormals, and negatives + if (f < 1.0) { + return { input: abstractFloat(f), expected: u32(0) }; + } + + if (f >= kValue.u32.max) { + return { input: abstractFloat(f), expected: u32(kValue.u32.max) }; + } + + // All u32s are representable as f64s and number is a f64 internally, so + // no need for special handling like is done for f32 and f16 below. + return { input: abstractFloat(f), expected: u32(Math.floor(f)) }; + }); + }, + f32: () => { + return scalarF32Range().map(f => { + // Handles zeros, subnormals, and negatives + if (f < 1.0) { + return { input: f32(f), expected: u32(0) }; + } + + if (f >= kValue.u32.max) { + return { input: f32(f), expected: u32(kValue.u32.max) }; + } + + // All f32 no larger than 2^24 has a precise integer part and a fractional + // part, just need to trunc towards 0 for the result integer. + if (f <= 2 ** 24) { + return { input: f32(f), expected: u32(Math.floor(f)) }; + } + + // All f32s between 2 ** 24 and kValue.u32.max are integers, so in theory + // one could use them directly, expect that number is actually f64 + // internally, so they need to be quantized to f32 first. + // Cannot just use floor here, since that might produce a u32 value that + // is precise in f64, but not in f32. + return { input: f32(f), expected: u32(quantizeToF32(f)) }; + }); + }, + f16: () => { + // Note that all positive finite f16 values are in range of u32. + return scalarF16Range().map(f => { + // Handles zeros, subnormals, and negatives + if (f < 1.0) { + return { input: f16(f), expected: u32(0) }; + } + + // All f16 no larger than <= 2^12 has a precise integer part and a + // fractional part, just need to trunc towards 0 for the result integer. + if (f <= 2 ** 12) { + return { input: f16(f), expected: u32(Math.trunc(f)) }; + } + + // All f16s larger than 2 ** 12 are integers, so in theory one could use + // them directly, expect that number is actually f64 internally, so they + // need to be quantized to f16 first.Cannot just use trunc here, since + // that might produce a u32 value that is precise in f64, but not in f16. + return { input: f16(f), expected: u32(quantizeToF16(f)) }; + }); + }, +}); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts index 87dc6e7a5d..6d342afffb 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts @@ -4,100 +4,14 @@ Execution Tests for the u32 conversion operations import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -import { kValue } from '../../../../util/constants.js'; -import { - bool, - f32, - f16, - i32, - TypeBool, - TypeF32, - TypeF16, - TypeI32, - TypeU32, - u32, -} from '../../../../util/conversion.js'; -import { - fullF32Range, - fullF16Range, - fullI32Range, - fullU32Range, - quantizeToF32, - quantizeToF16, -} from '../../../../util/math.js'; -import { reinterpretI32AsU32 } from '../../../../util/reinterpret.js'; -import { makeCaseCache } from '../case_cache.js'; -import { allInputSources, run, ShaderBuilder } from '../expression.js'; +import { Type } from '../../../../util/conversion.js'; +import { ShaderBuilder, allInputSources, run, onlyConstInputSource } from '../expression.js'; +import { d } from './u32_conversion.cache.js'; import { unary } from './unary.js'; export const g = makeTestGroup(GPUTest); -export const d = makeCaseCache('unary/u32_conversion', { - bool: () => { - return [ - { input: bool(true), expected: u32(1) }, - { input: bool(false), expected: u32(0) }, - ]; - }, - u32: () => { - return fullU32Range().map(u => { - return { input: u32(u), expected: u32(u) }; - }); - }, - i32: () => { - return fullI32Range().map(i => { - return { input: i32(i), expected: u32(reinterpretI32AsU32(i)) }; - }); - }, - f32: () => { - return fullF32Range().map(f => { - // Handles zeros, subnormals, and negatives - if (f < 1.0) { - return { input: f32(f), expected: u32(0) }; - } - - if (f >= kValue.u32.max) { - return { input: f32(f), expected: u32(kValue.u32.max) }; - } - - // All f32 no larger than 2^24 has a precise interger part and a fractional part, just need - // to trunc towards 0 for the result integer. - if (f <= 2 ** 24) { - return { input: f32(f), expected: u32(Math.floor(f)) }; - } - - // All f32s between 2 ** 24 and kValue.u32.max are integers, so in theory - // one could use them directly, expect that number is actually f64 - // internally, so they need to be quantized to f32 first. - // Cannot just use floor here, since that might produce a u32 value that - // is precise in f64, but not in f32. - return { input: f32(f), expected: u32(quantizeToF32(f)) }; - }); - }, - f16: () => { - // Note that all positive finite f16 values are in range of u32. - return fullF16Range().map(f => { - // Handles zeros, subnormals, and negatives - if (f < 1.0) { - return { input: f16(f), expected: u32(0) }; - } - - // All f16 no larger than <= 2^12 has a precise interger part and a fractional part, just need - // to trunc towards 0 for the result integer. - if (f <= 2 ** 12) { - return { input: f16(f), expected: u32(Math.trunc(f)) }; - } - - // All f16s larger than 2 ** 12 are integers, so in theory one could use them directly, expect - // that number is actually f64 internally, so they need to be quantized to f16 first. - // Cannot just use trunc here, since that might produce a u32 value that is precise in f64, - // but not in f16. - return { input: f16(f), expected: u32(quantizeToF16(f)) }; - }); - }, -}); - /** Generate a ShaderBuilder based on how the test case is to be vectorized */ function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder { return vectorize === undefined ? unary('u32') : unary(`vec${vectorize}<u32>`); @@ -117,7 +31,7 @@ The result is 1u if e is true and 0u otherwise ) .fn(async t => { const cases = await d.get('bool'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeU32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.bool], Type.u32, t.params, cases); }); g.test('u32') @@ -134,7 +48,7 @@ Identity operation ) .fn(async t => { const cases = await d.get('u32'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeU32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.u32, t.params, cases); }); g.test('i32') @@ -151,7 +65,7 @@ Reinterpretation of bits ) .fn(async t => { const cases = await d.get('i32'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeU32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.u32, t.params, cases); }); g.test('f32') @@ -168,7 +82,7 @@ e is converted to u32, rounding towards zero ) .fn(async t => { const cases = await d.get('f32'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeU32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.u32, t.params, cases); }); g.test('f16') @@ -188,7 +102,7 @@ e is converted to u32, rounding towards zero }) .fn(async t => { const cases = await d.get('f16'); - await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeU32, t.params, cases); + await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.u32, t.params, cases); }); g.test('abstract_int') @@ -201,6 +115,44 @@ Identity operation if the e can be represented in u32, otherwise it produces a s ` ) .params(u => - u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const) + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstractInt'); + await run( + t, + vectorizeToExpression(t.params.vectorize), + [Type.abstractInt], + Type.u32, + t.params, + cases + ); + }); + +g.test('abstract_float') + .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function') + .desc( + ` +u32(e), where e is an AbstractFloat + +e is converted to u32, rounding towards zero +` ) - .unimplemented(); + .params(u => + u + .combine('inputSource', onlyConstInputSource) + .combine('vectorize', [undefined, 2, 3, 4] as const) + ) + .fn(async t => { + const cases = await d.get('abstractFloat'); + await run( + t, + vectorizeToExpression(t.params.vectorize), + [Type.abstractFloat], + Type.u32, + t.params, + cases + ); + }); diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts index 160e465178..109e6c1421 100644 --- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts @@ -1,5 +1,6 @@ import { abstractFloatShaderBuilder, + abstractIntShaderBuilder, basicExpressionBuilder, ShaderBuilder, } from '../expression.js'; @@ -10,6 +11,11 @@ export function unary(op: string): ShaderBuilder { } /* @returns a ShaderBuilder that evaluates a prefix unary operation that returns AbstractFloats */ -export function abstractUnary(op: string): ShaderBuilder { +export function abstractFloatUnary(op: string): ShaderBuilder { return abstractFloatShaderBuilder(value => `${op}(${value})`); } + +/* @returns a ShaderBuilder that evaluates a prefix unary operation that returns AbstractInts */ +export function abstractIntUnary(op: string): ShaderBuilder { + return abstractIntShaderBuilder(value => `${op}(${value})`); +} |