From 0e3229906a77f3f174be4d1b58e99cb92a3afec3 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Tue, 7 Mar 2023 11:41:25 +0000 Subject: [PATCH] shader/execution: Add 'preventValueOptimizations' case parameterization This controls the behaviour of `f.value()`. When enabled, the value is placed into a storage buffer and loaded, preventing the compiler from seeing the constant value. When disabled, the value is simply placed into the shader as a literal. This was previously enabled by default. --- .../execution/flow_control/call.spec.ts | 3 + .../shader/execution/flow_control/for.spec.ts | 12 +++- .../shader/execution/flow_control/harness.ts | 62 +++++++++++++------ .../shader/execution/flow_control/if.spec.ts | 3 + .../execution/flow_control/loop.spec.ts | 3 + .../execution/flow_control/return.spec.ts | 3 + .../execution/flow_control/switch.spec.ts | 5 ++ .../execution/flow_control/while.spec.ts | 3 + 8 files changed, 73 insertions(+), 21 deletions(-) diff --git a/src/webgpu/shader/execution/flow_control/call.spec.ts b/src/webgpu/shader/execution/flow_control/call.spec.ts index 633e97474b37..b86134a58c78 100644 --- a/src/webgpu/shader/execution/flow_control/call.spec.ts +++ b/src/webgpu/shader/execution/flow_control/call.spec.ts @@ -11,6 +11,7 @@ export const g = makeTestGroup(GPUTest); g.test('call_basic') .desc('Test that flow control enters a called function') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest(t, f => ({ entrypoint: ` @@ -27,6 +28,7 @@ fn f() { g.test('call_nested') .desc('Test that flow control enters a nested function calls') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest(t, f => ({ entrypoint: ` @@ -53,6 +55,7 @@ fn c() { g.test('call_repeated') .desc('Test that flow control enters a nested function calls') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest(t, f => ({ entrypoint: ` diff --git a/src/webgpu/shader/execution/flow_control/for.spec.ts b/src/webgpu/shader/execution/flow_control/for.spec.ts index ae8fb7646f16..5e3a92bba893 100644 --- a/src/webgpu/shader/execution/flow_control/for.spec.ts +++ b/src/webgpu/shader/execution/flow_control/for.spec.ts @@ -11,6 +11,7 @@ export const g = makeTestGroup(GPUTest); g.test('for_basic') .desc('Test that flow control executes a for-loop body the correct number of times') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -27,6 +28,7 @@ g.test('for_basic') g.test('for_break') .desc('Test that flow control exits a for-loop when reaching a break statement') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -48,6 +50,7 @@ g.test('for_break') g.test('for_continue') .desc('Test flow control for a for-loop continue statement') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -69,6 +72,7 @@ g.test('for_continue') g.test('for_initalizer') .desc('Test flow control for a for-loop initializer') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest(t, f => ({ entrypoint: ` @@ -79,7 +83,7 @@ g.test('for_initalizer') ${f.expect_order(5)} `, extra: ` -fn initializer() -> u32 { +fn initializer() -> i32 { ${f.expect_order(1)} return ${f.value(0)}; } @@ -89,6 +93,7 @@ fn initializer() -> u32 { g.test('for_condition') .desc('Test flow control for a for-loop condition') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest(t, f => ({ entrypoint: ` @@ -99,7 +104,7 @@ g.test('for_condition') ${f.expect_order(8)} `, extra: ` -fn condition(i : u32) -> bool { +fn condition(i : i32) -> bool { ${f.expect_order(1, 3, 5, 7)} return i < ${f.value(3)}; } @@ -109,6 +114,7 @@ fn condition(i : u32) -> bool { g.test('for_continuing') .desc('Test flow control for a for-loop continuing statement') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest(t, f => ({ entrypoint: ` @@ -119,7 +125,7 @@ g.test('for_continuing') ${f.expect_order(7)} `, extra: ` -fn cont(i : u32) -> u32 { +fn cont(i : i32) -> i32 { ${f.expect_order(2, 4, 6)} return i + 1; } diff --git a/src/webgpu/shader/execution/flow_control/harness.ts b/src/webgpu/shader/execution/flow_control/harness.ts index 4d03851e66b0..56f3b3ddc7a8 100644 --- a/src/webgpu/shader/execution/flow_control/harness.ts +++ b/src/webgpu/shader/execution/flow_control/harness.ts @@ -1,21 +1,41 @@ import { Colors } from '../../../../common/util/colors.js'; import { GPUTest } from '../../../gpu_test.js'; +/** + * Options for runFlowControlTest() + */ +interface FlowControlTest extends GPUTest { + params: { + /** + * If true, then constant values will be placed into a storage buffer, + * preventing the shader compiler from knowing the value at compile time. + * This can prevent constant folding, loop unrolling, dead-code + * optimizations etc, which would could all affect the tests. + */ + preventValueOptimizations?: boolean; + }; +} + /** * The builder interface for the runFlowControlTest() callback. - * This interface is indented to be used to inject WGSL logic into the test shader. + * This interface is indented to be used to inject WGSL logic into the test + * shader. * @see runFlowControlTest */ interface FlowControlTestBuilder { /** - * Emits an expression to load the given value from a storage buffer, preventing the shader - * compiler from knowing the value at compile time. This can prevent constant folding, loop - * unrolling, dead-code optimizations etc, which would could all affect the tests. + * Emits a value into the shader. + * If the FlowControlTest.params.preventValueOptimizations flag is enabled, + * then value() emits an expression to load the given value from a storage + * buffer, preventing the shader compiler from knowing the value at compile + * time. This can prevent constant folding, loop unrolling, dead-code + * optimizations etc, which would could all affect the tests. */ value(v: number | boolean): string; /** - * Emits an expectation that the statement will be executed at the given chronological events. + * Emits an expectation that the statement will be executed at the given + * chronological events. * @param event one or more chronological events, the first being 0. */ expect_order(...event: number[]): string; @@ -55,12 +75,13 @@ interface FlowControlTestBuilder { * ``` * * @param t The test object - * @param builder The shader builder function that takes a FlowControlTestBuilder as the single - * argument, and returns either a WGSL string which is embedded into the WGSL entrypoint function, - * or a structure with entrypoint-scoped WGSL code and extra module-scope WGSL code. + * @param builder The shader builder function that takes a + * FlowControlTestBuilder as the single argument, and returns either a WGSL + * string which is embedded into the WGSL entrypoint function, or a structure + * with entrypoint-scoped WGSL code and extra module-scope WGSL code. */ export function runFlowControlTest( - t: GPUTest, + t: FlowControlTest, build_wgsl: (builder: FlowControlTestBuilder) => string | { entrypoint: string; extra: string } ) { const inputData = new Array(); @@ -80,12 +101,16 @@ export function runFlowControlTest( const build_wgsl_result = build_wgsl({ value: v => { - if (typeof v === 'boolean') { - inputData.push(v ? 1 : 0); - return `inputs[${inputData.length - 1}] != 0`; + if (t.params.preventValueOptimizations) { + if (typeof v === 'boolean') { + inputData.push(v ? 1 : 0); + return `inputs[${inputData.length - 1}] != 0`; + } + inputData.push(v); + return `inputs[${inputData.length - 1}]`; + } else { + return `${v}`; } - inputData.push(v); - return `inputs[${inputData.length - 1}]`; }, expect_order: (...expected) => { expectations.push({ @@ -117,7 +142,7 @@ struct Outputs { count : u32, data : array, }; -@group(0) @binding(0) var inputs : array; +@group(0) @binding(0) var inputs : array; @group(0) @binding(1) var outputs : Outputs; fn push_output(value : u32) { @@ -142,7 +167,8 @@ ${main_wgsl.extra} }, }); - // If there are no inputs, just put a single value in the buffer to keep makeBufferWithContents() happy. + // If there are no inputs, just put a single value in the buffer to keep + // makeBufferWithContents() happy. if (inputData.length === 0) { inputData.push(0); } @@ -191,8 +217,8 @@ ${main_wgsl.extra} // returns an Error with the given message and WGSL source const fail = (err: string) => Error(`${err}\nWGSL:\n${Colors.dim(Colors.blue(wgsl))}`); - // returns a colorized string of the expect_order() call, highlighting the event number that - // caused an error. + // returns a colorized string of the expect_order() call, highlighting + // the event number that caused an error. const expect_order_err = (expectation: ExpectedEvents, err_idx: number) => { let out = 'expect_order('; for (let i = 0; i < expectation.values.length; i++) { diff --git a/src/webgpu/shader/execution/flow_control/if.spec.ts b/src/webgpu/shader/execution/flow_control/if.spec.ts index cb66f99d2b7e..bbcd981da181 100644 --- a/src/webgpu/shader/execution/flow_control/if.spec.ts +++ b/src/webgpu/shader/execution/flow_control/if.spec.ts @@ -13,6 +13,7 @@ g.test('if_true') .desc( "Test that flow control executes the 'true' block of an if statement and not the 'false' block" ) + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -32,6 +33,7 @@ g.test('if_false') .desc( "Test that flow control executes the 'false' block of an if statement and not the 'true' block" ) + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -49,6 +51,7 @@ g.test('if_false') g.test('else_if') .desc("Test that flow control executes the correct 'else if' block of an if statement") + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, diff --git a/src/webgpu/shader/execution/flow_control/loop.spec.ts b/src/webgpu/shader/execution/flow_control/loop.spec.ts index 8021893d6ffe..68d24eba11e9 100644 --- a/src/webgpu/shader/execution/flow_control/loop.spec.ts +++ b/src/webgpu/shader/execution/flow_control/loop.spec.ts @@ -11,6 +11,7 @@ export const g = makeTestGroup(GPUTest); g.test('loop_break') .desc('Test that flow control exits a loop when reaching a break statement') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -33,6 +34,7 @@ g.test('loop_break') g.test('loop_continue') .desc('Test flow control for a loop continue statement') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -60,6 +62,7 @@ g.test('loop_continue') g.test('loop_continuing_basic') .desc('Test basic flow control for a loop continuing block') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, diff --git a/src/webgpu/shader/execution/flow_control/return.spec.ts b/src/webgpu/shader/execution/flow_control/return.spec.ts index be2c73c22704..ddfb20044cf9 100644 --- a/src/webgpu/shader/execution/flow_control/return.spec.ts +++ b/src/webgpu/shader/execution/flow_control/return.spec.ts @@ -11,6 +11,7 @@ export const g = makeTestGroup(GPUTest); g.test('return') .desc("Test that flow control does not execute after a 'return' statement") + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -24,6 +25,7 @@ g.test('return') g.test('return_conditional_true') .desc("Test that flow control does not execute after a 'return' statement in a if (true) block") + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -39,6 +41,7 @@ g.test('return_conditional_true') g.test('return_conditional_false') .desc("Test that flow control does not execute after a 'return' statement in a if (false) block") + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, diff --git a/src/webgpu/shader/execution/flow_control/switch.spec.ts b/src/webgpu/shader/execution/flow_control/switch.spec.ts index b0d3ee6fcaa4..2e3909e16cf8 100644 --- a/src/webgpu/shader/execution/flow_control/switch.spec.ts +++ b/src/webgpu/shader/execution/flow_control/switch.spec.ts @@ -11,6 +11,7 @@ export const g = makeTestGroup(GPUTest); g.test('switch') .desc('Test that flow control executes the correct switch case block') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -43,6 +44,7 @@ g.test('switch_multiple_case') .desc( 'Test that flow control executes the correct switch case block with multiple cases per block' ) + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -71,6 +73,7 @@ g.test('switch_multiple_case_default') .desc( 'Test that flow control executes the correct switch case block with multiple cases per block (combined with default)' ) + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -92,6 +95,7 @@ g.test('switch_multiple_case_default') }); g.test('switch_default') .desc('Test that flow control executes the switch default block') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -122,6 +126,7 @@ ${f.expect_order(2)} g.test('switch_default_only') .desc('Test that flow control executes the switch default block, which is the only case') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, diff --git a/src/webgpu/shader/execution/flow_control/while.spec.ts b/src/webgpu/shader/execution/flow_control/while.spec.ts index 025de79b0171..a7a1bb5bac98 100644 --- a/src/webgpu/shader/execution/flow_control/while.spec.ts +++ b/src/webgpu/shader/execution/flow_control/while.spec.ts @@ -11,6 +11,7 @@ export const g = makeTestGroup(GPUTest); g.test('while_basic') .desc('Test that flow control executes a while-loop body the correct number of times') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -29,6 +30,7 @@ g.test('while_basic') g.test('while_break') .desc('Test that flow control exits a while-loop when reaching a break statement') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t, @@ -52,6 +54,7 @@ g.test('while_break') g.test('while_continue') .desc('Test flow control for a while-loop continue statement') + .params(u => u.combine('preventValueOptimizations', [true, false])) .fn(t => { runFlowControlTest( t,