Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[spv-in] Conditional loop backedge not lowered to Statement::Loop with break_if expression #1977

Closed
vili-1 opened this issue Jun 7, 2022 · 6 comments · Fixed by #2290
Closed
Labels
area: front-end Input formats for conversion kind: bug Something isn't working lang: SPIR-V Binary SPIR-V input and output

Comments

@vili-1
Copy link

vili-1 commented Jun 7, 2022

Naga (ab2806e) rejects this SPIR-V:

               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %7 "main"
               OpExecutionMode %7 LocalSize 1 1 1
               
               ; Below, we declare various types and variables for storage buffers.
               ; These decorations tell SPIR-V that the types and variables relate to storage buffers


               OpDecorate %size_1_struct_type BufferBlock
               OpMemberDecorate %size_1_struct_type 0 Offset 0
               OpDecorate %size_1_array_type ArrayStride 4

               OpDecorate %output_struct_type BufferBlock
               OpMemberDecorate %output_struct_type 0 Offset 0
               OpDecorate %output_array_type ArrayStride 4

               OpDecorate %directions_8_variable DescriptorSet 0
               OpDecorate %directions_8_variable Binding 0

               OpDecorate %directions_10_variable DescriptorSet 0
               OpDecorate %directions_10_variable Binding 1

               OpDecorate %directions_14_variable DescriptorSet 0
               OpDecorate %directions_14_variable Binding 2

               OpDecorate %directions_15_variable DescriptorSet 0
               OpDecorate %directions_15_variable Binding 3

               OpDecorate %output_variable DescriptorSet 0
               OpDecorate %output_variable Binding 4


          %1 = OpTypeVoid
          %2 = OpTypeFunction %1
          %3 = OpTypeBool
          %4 = OpTypeInt 32 0
          %5 = OpConstantTrue %3
          %6 = OpConstant %4 0
          

               %constant_0 = OpConstant %4 0
               %constant_1 = OpConstant %4 1
               %constant_7 = OpConstant %4 7
               %constant_8 = OpConstant %4 8
               %constant_9 = OpConstant %4 9
               %constant_10 = OpConstant %4 10
               %constant_11 = OpConstant %4 11
               %constant_12 = OpConstant %4 12
               %constant_13 = OpConstant %4 13
               %constant_14 = OpConstant %4 14
               %constant_15 = OpConstant %4 15


               ; Declaration of storage buffers for the 4 directions and the output
               

               %size_1_array_type = OpTypeArray %4 %constant_1
               %size_1_struct_type = OpTypeStruct %size_1_array_type
               %size_1_pointer_type = OpTypePointer Uniform %size_1_struct_type
               %directions_8_variable = OpVariable %size_1_pointer_type Uniform
               %directions_10_variable = OpVariable %size_1_pointer_type Uniform
               %directions_14_variable = OpVariable %size_1_pointer_type Uniform
               %directions_15_variable = OpVariable %size_1_pointer_type Uniform

               %output_array_type = OpTypeArray %4 %constant_7
               %output_struct_type = OpTypeStruct %output_array_type
               %output_pointer_type = OpTypePointer Uniform %output_struct_type
               %output_variable = OpVariable %output_pointer_type Uniform

               ; Pointer type for declaring local variables of int type
               %local_int_ptr = OpTypePointer Function %4

               ; Pointer type for integer data in a storage buffer
               %storage_buffer_int_ptr = OpTypePointer Uniform %4


          %7 = OpFunction %1 None %2

          %8 = OpLabel ; validCFG/SelectionHeader$2
               %output_index = OpVariable %local_int_ptr Function %constant_0
               %directions_8_index = OpVariable %local_int_ptr Function %constant_0
               %directions_10_index = OpVariable %local_int_ptr Function %constant_0
               %directions_14_index = OpVariable %local_int_ptr Function %constant_0
               %directions_15_index = OpVariable %local_int_ptr Function %constant_0


   %temp_8_0 = OpLoad %4 %output_index
   %temp_8_1 = OpAccessChain %storage_buffer_int_ptr %output_variable %constant_0 %temp_8_0
               OpStore %temp_8_1 %constant_8
   %temp_8_2 = OpIAdd %4 %temp_8_0 %constant_1
               OpStore %output_index %temp_8_2
   %temp_8_3 = OpLoad %4 %directions_8_index
   %temp_8_4 = OpAccessChain %storage_buffer_int_ptr %directions_8_variable %constant_0 %temp_8_3
   %temp_8_5 = OpLoad %4 %temp_8_4
   %temp_8_7 = OpIAdd %4 %temp_8_3 %constant_1
               OpStore %directions_8_index %temp_8_7
               OpSelectionMerge %9 None
               OpSwitch %temp_8_5 %10 1 %9


         %10 = OpLabel ; validCFG/SelectionHeader$1
  %temp_10_0 = OpLoad %4 %output_index
  %temp_10_1 = OpAccessChain %storage_buffer_int_ptr %output_variable %constant_0 %temp_10_0
               OpStore %temp_10_1 %constant_10
  %temp_10_2 = OpIAdd %4 %temp_10_0 %constant_1
               OpStore %output_index %temp_10_2
  %temp_10_3 = OpLoad %4 %directions_10_index
  %temp_10_4 = OpAccessChain %storage_buffer_int_ptr %directions_10_variable %constant_0 %temp_10_3
  %temp_10_5 = OpLoad %4 %temp_10_4
  %temp_10_7 = OpIAdd %4 %temp_10_3 %constant_1
               OpStore %directions_10_index %temp_10_7
               OpSelectionMerge %11 None
               OpSwitch %temp_10_5 %12 1 %11


         %12 = OpLabel ; validCFG/Block$0
               OpReturn


         %11 = OpLabel ; validCFG/LoopHeader$0
  %temp_11_0 = OpLoad %4 %output_index
  %temp_11_1 = OpAccessChain %storage_buffer_int_ptr %output_variable %constant_0 %temp_11_0
               OpStore %temp_11_1 %constant_11
  %temp_11_2 = OpIAdd %4 %temp_11_0 %constant_1
               OpStore %output_index %temp_11_2
               OpLoopMerge %13 %14 None
               OpBranch %14


         %14 = OpLabel ; validCFG/SelectionHeader$0
  %temp_14_0 = OpLoad %4 %output_index
  %temp_14_1 = OpAccessChain %storage_buffer_int_ptr %output_variable %constant_0 %temp_14_0
               OpStore %temp_14_1 %constant_14
  %temp_14_2 = OpIAdd %4 %temp_14_0 %constant_1
               OpStore %output_index %temp_14_2
  %temp_14_3 = OpLoad %4 %directions_14_index
  %temp_14_4 = OpAccessChain %storage_buffer_int_ptr %directions_14_variable %constant_0 %temp_14_3
  %temp_14_5 = OpLoad %4 %temp_14_4
  %temp_14_7 = OpIAdd %4 %temp_14_3 %constant_1
               OpStore %directions_14_index %temp_14_7
               OpSelectionMerge %15 None
               OpSwitch %temp_14_5 %15


         %15 = OpLabel ; validCFG/StructurallyReachableBlock$2
  %temp_15_0 = OpLoad %4 %output_index
  %temp_15_1 = OpAccessChain %storage_buffer_int_ptr %output_variable %constant_0 %temp_15_0
               OpStore %temp_15_1 %constant_15
  %temp_15_2 = OpIAdd %4 %temp_15_0 %constant_1
               OpStore %output_index %temp_15_2
  %temp_15_3 = OpLoad %4 %directions_15_index
  %temp_15_4 = OpAccessChain %storage_buffer_int_ptr %directions_15_variable %constant_0 %temp_15_3
  %temp_15_5 = OpLoad %4 %temp_15_4
  %temp_15_6 = OpIEqual %3 %temp_15_5 %constant_1
  %temp_15_7 = OpIAdd %4 %temp_15_3 %constant_1
               OpStore %directions_15_index %temp_15_7
               OpBranchConditional %temp_15_6 %13 %11


         %13 = OpLabel ; validCFG/StructurallyReachableBlock$1
  %temp_13_0 = OpLoad %4 %output_index
  %temp_13_1 = OpAccessChain %storage_buffer_int_ptr %output_variable %constant_0 %temp_13_0
               OpStore %temp_13_1 %constant_13
  %temp_13_2 = OpIAdd %4 %temp_13_0 %constant_1
               OpStore %output_index %temp_13_2
               OpBranch %9


          %9 = OpLabel ; validCFG/StructurallyReachableBlock$0
   %temp_9_0 = OpLoad %4 %output_index
   %temp_9_1 = OpAccessChain %storage_buffer_int_ptr %output_variable %constant_0 %temp_9_0
               OpStore %temp_9_1 %constant_9
   %temp_9_2 = OpIAdd %4 %temp_9_0 %constant_1
               OpStore %output_index %temp_9_2
               OpReturn

               OpFunctionEnd

We get:

The 'break' is used outside of a 'loop' or 'switch' context

The SPIR-V is valid, and spirv-val accepts it. There is no "break" outside any switch.

test_0_3842908924422139125

This is exactly the same as this issue.

@teoxoy teoxoy added kind: bug Something isn't working lang: SPIR-V Binary SPIR-V input and output area: front-end Input formats for conversion labels Jun 13, 2022
@teoxoy
Copy link
Member

teoxoy commented Jun 13, 2022

@JCapucho any idea? (#1028 seems to have been fixed; maybe a regression somewhere?)

@JCapucho
Copy link
Collaborator

I finally had some time to analyze this issue and it seems to be a combination of smaller issues. The part that's is causing all of this is

%15 = OpLabel ; validCFG/StructurallyReachableBlock$2
      // ...
      OpBranchConditional %temp_15_6 %13 %11

To note here is the conditional branch that goes to either %13 the loop merge target or to %11 this is the loop header, essentially causing it to continue.

The second target is tricky and the problem is further discussed in this issue gfx-rs/wgpu#4388

The first target is a break and should be handled as such, but naga's validator seems to disallow breaks in the continuing block which the wgsl spec seems to allow (https://gpuweb.github.io/gpuweb/wgsl/#behaviors), but I'm not sure, maybe @jimblandy can confirm it.

@jimblandy
Copy link
Member

The grammar isn't especially clear, but break statements in continuing blocks are restricted to be the very last statement in the block, like this:

loop {
    ...
    continuing {
        ...
        break if (condition);
    }
}

Further statements after the break if or after the continuing block are forbidden.

This is supposed to exactly match SPIR-V's requirements for continuing blocks:

  • the Continue Target must dominate the back-edge block
  • the back-edge block must post dominate the Continue Target

Those rules forbid breaks in continue constructs, except that the final backward branch may be conditional.

@jimblandy
Copy link
Member

It doesn't look like Naga has implemented that final break if yet. I think the Naga IR for loops should end up like this:

    Loop { body: Block, continuing: Block, bottom_break_if: Option<Handle<Expression>> },

@eddyb
Copy link
Contributor

eddyb commented Nov 17, 2022

By using the Naga CLI, I was able to reduce one of my own errors to:

(click to open - it's not that relevant, see later below)
fn f(cond: bool) {
    loop {
        // ... loop body ...
        continue;
        continuing {
            if cond {
            } else {
                break;
            }
        }
    }
}
$ naga minimal.wgsl
error: 
   ┌─ minimal.wgsl:1:1

 1 │ ╭ fn f(cond: bool) {
 2 │ │     loop {
 3 │ │         // ... loop body ...
 4 │ │         continue;
   · │
 8 │ │                 break;
   │ │                 ^^^^^ invalid break
 9 │ │             }
10 │ │         }
11 │ │     }
   │ ╰─────^ naga::Function [1]

Function [1] 'f' is invalid: 
        The `break` is used outside of a `loop` or `switch` context

Is this the same issue, @jimblandy? I wonder if the title of this issue should be changed to remove "switch", in that case.


Though, reducing the WGSL is a bit misleading, since what I'd really want to do is reduce the spirv-val-passing input that it fails on. And I can't trivially get that from WGSL, either:

$ naga --validate 0 minimal.wgsl minimal.spv
$ spirv-val minimal.spv
error: line 24: The continue construct with the continue target '12[%12]' is not structurally post dominated by the back-edge block '13[%13]'
  %13 = OpLabel

But by starting from spirv-dis minimal.spv > minimal.spvasm, I was able to to come up with this:

OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450

%void = OpTypeVoid
%bool = OpTypeBool

%typeof_f = OpTypeFunction %void %bool
%f = OpFunction %void None %typeof_f
  %cond = OpFunctionParameter %bool

  %entry = OpLabel
    OpBranch %loop

  %loop = OpLabel
    OpLoopMerge %loop_exit_merge %loop_continue None
    OpBranch %loop_body

    %loop_body = OpLabel
      OpBranch %loop_continue

    %loop_continue = OpLabel
      ; OpSelectionMerge %loop_back_edge None
      ; OpBranchConditional %cond %loop_break %loop_back_edge
      OpBranchConditional %cond %loop_exit_merge %loop

    ;  %loop_break = OpLabel
    ;    OpBranch %loop_exit_merge

    ; %loop_back_edge = OpLabel
    ;  OpBranch %loop

  %loop_exit_merge = OpLabel
    OpReturn

OpFunctionEnd

(the commented out instructions are the original ones, that I had to look at the original larger shader to know what to replace them with)
(in retrospect it makes so much sense that break if is the same feature where the backedge can be a OpBranchConditional instead of just an OpBranch, without a OpSelectionMerge since it's the backedge, but "conditional break" is not how I think of such backedges)

$ spirv-as minimal.spvasm -o minimal.spv
$ spirv-val minimal.spv
$ naga minimal.spv
Function [1] '' is invalid: 
        The `break` is used outside of a `loop` or `switch` context

@jimblandy so it feels like "SPIR-V frontend: conditional backedge not lowered to break if" or similar should be the title (not super familiar with Naga terminology, don't mind me if this isn't helpful).

@eddyb
Copy link
Contributor

eddyb commented Nov 17, 2022

As suspected, this breaks idiomatic (i.e. using conditional backedges) SPIR-V do-while loops:

$ cat do_while.vert
#version 450

void f(bool cond) {
    do {} while(cond);
}

void main() {
    f(true);
}
$ glslangValidator -V do_while.vert -o do_while.spv
do_while.vert
$ spirv-val do_while.spv
$ naga do_while.spv
Function [1] 'f(b1;' is invalid: 
        The `break` is used outside of a `loop` or `switch` context

By comparison, Naga's own GLSL frontend produces effectively this (w/o any continuing/break if):

fn f(cond: bool) {
    loop {
        if !cond {
            break;
        }
    }
    return;
}

eddyb added a commit to EmbarkStudios/spirt that referenced this issue Dec 9, 2022
…rget. (#10)

As we have no `break` edges out of SPIR-T loop bodies, any point in the
loop body (not nested in another selection/loop) is a valid "continue"
target, and we can spare a block by making *the whole body* that.

In [WGSL loops](https://gpuweb.github.io/gpuweb/wgsl/#loop-statement),
this corresponds to moving the rest of the loop body (*not* using
`break`/`continue`) into the `continuing {...}` block. <sub>(but
examples are hard because `break if` isn't implemented yet in Naga, and
our `do`-`while`-style loops use SPIR-V "conditional back-edges" which
in WGSL *require* `break if` - see
gfx-rs/naga#1977 (comment) for
more details/context)</sub>

For `tests/data/for-loop.wgsl.spvasm` (after `spirv-as` ->
`spv-lower-link-lift` -> `spirv-dis`), the change is:
```diff
@@ -25,5 +25,5 @@
          %22 = OpLabel
-         %13 = OpPhi %int %int_1 %21 %16 %27
-         %14 = OpPhi %int %int_1 %21 %17 %27
-               OpLoopMerge %28 %27 None
+         %13 = OpPhi %int %int_1 %21 %16 %26
+         %14 = OpPhi %int %int_1 %21 %17 %26
+               OpLoopMerge %28 %23 None
                OpBranch %23
@@ -44,4 +44,2 @@
          %17 = OpPhi %int %20 %24 %11 %25
-               OpBranch %27
-         %27 = OpLabel
                OpBranchConditional %15 %22 %28
```
<sub>(on the "after" side I had to use `sed 's/%27/%28/g'` on to make
the diff more readable wrt renumbering)</sub>
@jimblandy jimblandy changed the title Naga rejects example with break from switch [spv-in] Conditional loop backedge not lowered to Statement::Loop with break_if expression Mar 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: front-end Input formats for conversion kind: bug Something isn't working lang: SPIR-V Binary SPIR-V input and output
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants