Skip to content

Commit

Permalink
shader/execution: Add 'preventValueOptimizations' case parameterization
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ben-clayton committed Mar 7, 2023
1 parent ec5ad7d commit 0e32299
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 21 deletions.
3 changes: 3 additions & 0 deletions src/webgpu/shader/execution/flow_control/call.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: `
Expand All @@ -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: `
Expand All @@ -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: `
Expand Down
12 changes: 9 additions & 3 deletions src/webgpu/shader/execution/flow_control/for.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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: `
Expand All @@ -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)};
}
Expand All @@ -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: `
Expand All @@ -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)};
}
Expand All @@ -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: `
Expand All @@ -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;
}
Expand Down
62 changes: 44 additions & 18 deletions src/webgpu/shader/execution/flow_control/harness.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<number>();
Expand All @@ -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({
Expand Down Expand Up @@ -117,7 +142,7 @@ struct Outputs {
count : u32,
data : array<u32>,
};
@group(0) @binding(0) var<storage, read> inputs : array<u32>;
@group(0) @binding(0) var<storage, read> inputs : array<i32>;
@group(0) @binding(1) var<storage, read_write> outputs : Outputs;
fn push_output(value : u32) {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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++) {
Expand Down
3 changes: 3 additions & 0 deletions src/webgpu/shader/execution/flow_control/if.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions src/webgpu/shader/execution/flow_control/loop.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions src/webgpu/shader/execution/flow_control/return.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions src/webgpu/shader/execution/flow_control/switch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions src/webgpu/shader/execution/flow_control/while.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down

0 comments on commit 0e32299

Please sign in to comment.