| export const description = ` |
| Execution Tests for shadowing |
| `; |
| |
| import { makeTestGroup } from '../../../common/framework/test_group.js'; |
| import { iterRange } from '../../../common/util/util.js'; |
| import { AllFeaturesMaxLimitsGPUTest, GPUTest } from '../../gpu_test.js'; |
| |
| export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest); |
| |
| /** |
| * Run a shader and check that the buffer output matches expectations. |
| * |
| * @param t The test object |
| * @param wgsl The shader source |
| * @param expected The array of expected values after running the shader |
| */ |
| function runShaderTest(t: GPUTest, wgsl: string, expected: Uint32Array): void { |
| const pipeline = t.device.createComputePipeline({ |
| layout: 'auto', |
| compute: { |
| module: t.device.createShaderModule({ code: wgsl }), |
| entryPoint: 'main', |
| }, |
| }); |
| |
| // Allocate a buffer and fill it with 0xdeadbeef words. |
| const outputBuffer = t.makeBufferWithContents( |
| new Uint32Array([...iterRange(expected.length, _i => 0xdeadbeef)]), |
| GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC |
| ); |
| const bindGroup = t.device.createBindGroup({ |
| layout: pipeline.getBindGroupLayout(0), |
| entries: [{ binding: 0, resource: { buffer: outputBuffer } }], |
| }); |
| |
| // Run the shader. |
| const encoder = t.device.createCommandEncoder(); |
| const pass = encoder.beginComputePass(); |
| pass.setPipeline(pipeline); |
| pass.setBindGroup(0, bindGroup); |
| pass.dispatchWorkgroups(1); |
| pass.end(); |
| t.queue.submit([encoder.finish()]); |
| |
| // Check that only the non-padding bytes were modified. |
| t.expectGPUBufferValuesEqual(outputBuffer, expected); |
| } |
| |
| g.test('declaration') |
| .desc(`Test that shadowing is handled correctly`) |
| .fn(t => { |
| const wgsl = ` |
| struct S { |
| my_var_start: u32, |
| my_var_block_shadow: u32, |
| my_var_unshadow: u32, |
| my_var_param_shadow: u32, |
| my_var_param_reshadow: u32, |
| my_var_after_func: u32, |
| |
| my_const_start: u32, |
| my_const_block_shadow: u32, |
| my_const_unshadow: u32, |
| my_const_param_shadow: u32, |
| my_const_param_reshadow: u32, |
| my_const_after_func: u32, |
| |
| my_let_block_shadow: u32, |
| my_let_param_reshadow: u32, |
| my_let_after_func: u32, |
| |
| my_func_param_shadow: u32, |
| my_func_shadow: u32, |
| } |
| @group(0) @binding(0) var<storage, read_write> buffer : S; |
| |
| var<private> my_var: u32 = 1; |
| const my_const: u32 = 100; |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| let my_let = 200u; |
| |
| buffer.my_var_start = my_var; // 1 |
| buffer.my_const_start = my_const; // 100 |
| |
| { |
| var my_var: u32 = 10; |
| const my_const: u32 = 110; |
| |
| buffer.my_var_block_shadow = my_var; // 10 |
| buffer.my_const_block_shadow = my_const; // 110 |
| |
| let my_let = 210u; |
| buffer.my_let_block_shadow = my_let; // 210 |
| } |
| |
| buffer.my_var_unshadow = my_var; // 1 |
| buffer.my_const_unshadow = my_const; // 100 |
| |
| my_func(20, 120, my_let, 300); |
| |
| buffer.my_var_after_func = my_var; // 1 |
| buffer.my_const_after_func = my_const; // 100 |
| buffer.my_let_after_func = my_let; // 200; |
| }; |
| |
| // Note, defined after |main| |
| fn my_func(my_var: u32, my_const: u32, my_let: u32, my_func: u32) { |
| buffer.my_var_param_shadow = my_var; // 20 |
| buffer.my_const_param_shadow = my_const; // 120 |
| |
| buffer.my_func_param_shadow = my_func; // 300 |
| |
| // Need block here because of scoping rules for parameters |
| { |
| var my_var = 30u; |
| const my_const = 130u; |
| |
| buffer.my_var_param_reshadow = my_var; // 30 |
| buffer.my_const_param_reshadow = my_const; // 130 |
| |
| let my_let = 220u; |
| buffer.my_let_param_reshadow = my_let; // 220 |
| |
| let my_func: u32 = 310; |
| buffer.my_func_shadow = my_func; // 310 |
| } |
| } |
| `; |
| runShaderTest( |
| t, |
| wgsl, |
| new Uint32Array([ |
| // my_var |
| 1, // my_var_start |
| 10, // my_var_block_shadow |
| 1, // my_var_unshadow |
| 20, // my_var_param_shadow |
| 30, // my_var_param_reshadow |
| 1, // my_var_after_func |
| // my_const |
| 100, // my_const_start |
| 110, // my_const_block_shadow |
| 100, // my_const_unshadow |
| 120, // my_const_param_shadow |
| 130, // my_const_param_reshadow |
| 100, // my_const_after_func |
| // my_let |
| 210, // my_let_block_shadow |
| 220, // my_let_param_reshadow |
| 200, // my_let_after_func |
| // my_func |
| 300, // my_func_param_shadow |
| 310, // my_func_shadow |
| ]) |
| ); |
| }); |
| |
| g.test('builtin') |
| .desc(`Test that shadowing a builtin name is handled correctly`) |
| .fn(t => { |
| const wgsl = ` |
| struct S { |
| my_max_shadow: u32, |
| max_call: u32, |
| } |
| @group(0) @binding(0) var<storage, read_write> buffer : S; |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| let max = 400u; |
| buffer.my_max_shadow = max; |
| |
| my_func(); |
| }; |
| |
| fn my_func() { |
| buffer.max_call = max(310u, 410u); |
| } |
| `; |
| runShaderTest( |
| t, |
| wgsl, |
| new Uint32Array([ |
| // my_max |
| 400, // my_max_shadow |
| 410, // max_call |
| ]) |
| ); |
| }); |
| |
| g.test('for_loop') |
| .desc(`Test that shadowing is handled correctly with for loops`) |
| .fn(t => { |
| const wgsl = ` |
| struct S { |
| my_idx_before: u32, |
| my_idx_loop: array<u32, 2>, |
| my_idx_after: u32, |
| } |
| @group(0) @binding(0) var<storage, read_write> buffer : S; |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| var my_idx = 500u; |
| buffer.my_idx_before = my_idx; // 500; |
| for (var my_idx = 0u; my_idx < 2u; my_idx++) { |
| let pos = my_idx; |
| var my_idx = 501u + my_idx; |
| buffer.my_idx_loop[pos] = my_idx; // 501, 502 |
| } |
| buffer.my_idx_after = my_idx; // 500; |
| }; |
| `; |
| runShaderTest( |
| t, |
| wgsl, |
| new Uint32Array([ |
| 500, // my_idx_before |
| 501, // my_idx_loop[0] |
| 502, // my_idx_loop[1] |
| 500, // my_idx_after |
| ]) |
| ); |
| }); |
| |
| g.test('while') |
| .desc(`Test that shadowing is handled correctly with while loops`) |
| .fn(t => { |
| const wgsl = ` |
| struct S { |
| my_idx_before: u32, |
| my_idx_loop: array<u32, 2>, |
| my_idx_after: u32, |
| } |
| @group(0) @binding(0) var<storage, read_write> buffer : S; |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| var my_idx = 0u; |
| buffer.my_idx_before = my_idx; // 0; |
| |
| var counter = 0u; |
| while (counter < 2) { |
| var my_idx = 500u + counter; |
| buffer.my_idx_loop[counter] = my_idx; // 500, 501 |
| |
| counter += 1; |
| } |
| |
| buffer.my_idx_after = my_idx; // 1; |
| }; |
| `; |
| runShaderTest( |
| t, |
| wgsl, |
| new Uint32Array([ |
| 0, // my_idx_before |
| 500, // my_idx_loop[0] |
| 501, // my_idx_loop[1] |
| 0, // my_idx_after |
| ]) |
| ); |
| }); |
| |
| g.test('loop') |
| .desc(`Test that shadowing is handled correctly with loops`) |
| .fn(t => { |
| const wgsl = ` |
| struct S { |
| my_idx_before: u32, |
| my_idx_loop: array<u32, 2>, |
| my_idx_continuing: array<u32, 2>, |
| my_idx_after: u32, |
| } |
| @group(0) @binding(0) var<storage, read_write> buffer : S; |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| var my_idx = 0u; |
| buffer.my_idx_before = my_idx; // 0; |
| |
| var counter = 0u; |
| loop { |
| var my_idx = 500u + counter; |
| buffer.my_idx_loop[counter] = my_idx; // 500, 501 |
| |
| |
| continuing { |
| var my_idx = 600u + counter; |
| buffer.my_idx_continuing[counter] = my_idx; // 600, 601 |
| |
| counter += 1; |
| break if counter == 2; |
| } |
| } |
| buffer.my_idx_after = my_idx; // 1; |
| }; |
| `; |
| runShaderTest( |
| t, |
| wgsl, |
| new Uint32Array([ |
| 0, // my_idx_before |
| 500, // my_idx_loop[0] |
| 501, // my_idx_loop[1] |
| 600, // my_idx_continuing[0] |
| 601, // my_idx_continuing[1] |
| 0, // my_idx_after |
| ]) |
| ); |
| }); |
| |
| g.test('switch') |
| .desc(`Test that shadowing is handled correctly with a switch`) |
| .fn(t => { |
| const wgsl = ` |
| struct S { |
| my_idx_before: u32, |
| my_idx_case: u32, |
| my_idx_default: u32, |
| my_idx_after: u32, |
| } |
| @group(0) @binding(0) var<storage, read_write> buffer : S; |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| var my_idx = 0u; |
| buffer.my_idx_before = my_idx; // 0; |
| |
| for (var i = 0; i < 2; i++) { |
| switch (i) { |
| case 0: { |
| var my_idx = 10u; |
| buffer.my_idx_case = my_idx; // 10 |
| } |
| default: { |
| var my_idx = 20u; |
| buffer.my_idx_default = my_idx; // 20 |
| } |
| } |
| } |
| |
| buffer.my_idx_after = my_idx; // 1; |
| }; |
| `; |
| runShaderTest( |
| t, |
| wgsl, |
| new Uint32Array([ |
| 0, // my_idx_before |
| 10, // my_idx_case |
| 20, // my_idx_default |
| 0, // my_idx_after |
| ]) |
| ); |
| }); |
| |
| g.test('if') |
| .desc(`Test that shadowing is handled correctly with a switch`) |
| .fn(t => { |
| const wgsl = ` |
| struct S { |
| my_idx_before: u32, |
| my_idx_if: u32, |
| my_idx_elseif: u32, |
| my_idx_else: u32, |
| my_idx_after: u32, |
| } |
| @group(0) @binding(0) var<storage, read_write> buffer : S; |
| |
| @compute @workgroup_size(1) |
| fn main() { |
| var my_idx = 0u; |
| buffer.my_idx_before = my_idx; // 0; |
| |
| for (var i = 0; i < 3; i++) { |
| if i == 0 { |
| var my_idx = 10u; |
| buffer.my_idx_if = my_idx; // 10 |
| } else if i == 1 { |
| var my_idx = 20u; |
| buffer.my_idx_elseif = my_idx; // 20 |
| } else { |
| var my_idx = 30u; |
| buffer.my_idx_else = my_idx; // 30 |
| } |
| } |
| |
| buffer.my_idx_after = my_idx; // 1; |
| }; |
| `; |
| runShaderTest( |
| t, |
| wgsl, |
| new Uint32Array([ |
| 0, // my_idx_before |
| 10, // my_idx_if |
| 20, // my_idx_elseif |
| 30, // my_idx_else |
| 0, // my_idx_after |
| ]) |
| ); |
| }); |