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

spirv-opt improper inlining OpKill into continue construct #2433

Closed
programmerjake opened this issue Mar 8, 2019 · 9 comments · Fixed by #2713 or #2842
Closed

spirv-opt improper inlining OpKill into continue construct #2433

programmerjake opened this issue Mar 8, 2019 · 9 comments · Fixed by #2713 or #2842
Assignees

Comments

@programmerjake
Copy link

@alan-baker asked me to open this issue here from KhronosGroup/SPIRV-Headers#86

Original issue:
Is a continue construct that consists of OpKill valid? If so, the rules on the header block post-dominating the continue construct need to be fixed. Otherwise, there's a bug in spirv-opt where kill() is improperly inlined.

http://shader-playground.timjones.io/1323167d9be3ec6d7a54996e098612de

spirv-opt output:

; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 7
; Bound: 13
; Schema: 0
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %main "main"
               OpExecutionMode %main OriginUpperLeft
               OpSource GLSL 330
               OpName %main "main"
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
       %main = OpFunction %void None %3
          %5 = OpLabel
               OpLoopMerge %11 %12 None
               OpBranch %12
         %12 = OpLabel
               OpKill
         %11 = OpLabel
               OpUnreachable
               OpFunctionEnd

spirv-opt input:

// Module Version 10000
// Generated by (magic number): 80007
// Id's are bound by 17

                              Capability Shader
               1:             ExtInstImport  "GLSL.std.450"
                              MemoryModel Logical GLSL450
                              EntryPoint Fragment 4  "main"
                              ExecutionMode 4 OriginUpperLeft
                              Source GLSL 330
                              Name 4  "main"
                              Name 6  "kill("
               2:             TypeVoid
               3:             TypeFunction 2
              14:             TypeBool
              15:    14(bool) ConstantTrue
         4(main):           2 Function None 3
               5:             Label
                              Branch 9
               9:             Label
                              LoopMerge 11 12 None
                              Branch 13
              13:             Label
                              BranchConditional 15 10 11
              10:               Label
                                Branch 12
              12:               Label
              16:           2   FunctionCall 6(kill()
                                Branch 9
              11:             Label
                              Return
                              FunctionEnd
        6(kill():           2 Function None 3
               7:             Label
                              Kill
                              FunctionEnd
@s-perron
Copy link
Collaborator

This is a complicated issue, and it is not clear how to handle this case. The problem is that before inlining it looks like the continue target branches back the to loop header, but it does not. The function never returns. Inlining keeps the same semantics, it just exposes that the function never returns.

No solutions seems obvious. We cannot create a new dummy continue target of the function call has no return because other parts of the code have to be able to jump to the continue target. Breaking from the loop and doing the OpKill out of the loop only works if it is executed unconditionally.

This will require some careful thought. I'll have to get back to it when I return from vacation in a couple weeks.

@programmerjake
Copy link
Author

My preferred solution (and the one I implemented in Kazan) is to treat the continue construct like a case construct in that it can fall through to the loop header, jump to merge targets, execute OpKill, and execute OpReturn*.

Continue construct parsing in implementation of structure parser (structures somewhat modified but map directly to official structures):
https://salsa.debian.org/Kazan-team/kazan/blob/5f073ca0d589f73ee72597c232529414dee7bcf7/shader-compiler/src/cfg/structure_tree.rs#L706-728

@s-perron
Copy link
Collaborator

@programmerjake What code that does come from? I'm guessing that this is glsl code where the function in the continue contains an OpKill somewhere in the call tree. Is this a common pattern? From what I can tell, the current version of the spec allows the OpKill in the continue construct, but I don't believe it was intended. I'm trying to get clarification on this. Having an idea of what is done in real code might be useful.

@programmerjake
Copy link
Author

@s-perron It does come from glsl that has a call inside a continue construct to a function that has discard in it. I don't have any real use-cases for such code, I was trying to determine the limits of what the SPIR-V to LLVM compiler I am writing would have to handle.

s-perron added a commit that referenced this issue Jul 4, 2019
It is illegal to inline an OpKill instruction into a continue construct because the continue header will no longer dominate the backedge.

This commit adds a check for this, and does not inline.

If we still want to be able to inline a function that contains an OpKill, we can add a new pass that will wrap OpKill instructions into its own function with just the single instruction.

I do not believe that this is a common case right now, so I will not do that yet.

Fixes #2433.
@s-perron
Copy link
Collaborator

There was a bug in the fix for this issue. I had to be reverted.

@s-perron s-perron reopened this Jul 17, 2019
@s-perron
Copy link
Collaborator

The solution I had relies on two analysies: dominator trees and inst-to-block. Neither of which are kept up-to-date during inlining. So if we have A calls B class C. First we inline B into A. Then, when trying to inline C into A, the inst-to-block mapping it out of date and we get a segmentation fault.

@programmerjake
Copy link
Author

The solution I had relies on two analysies: dominator trees and inst-to-block. Neither of which are kept up-to-date during inlining. So if we have A calls B class C. First we inline B into A. Then, when trying to inline C into A, the inst-to-block mapping it out of date and we get a segmentation fault.

maybe add a map with "function contains reachable OpKill" and track during inlining?

@s-perron
Copy link
Collaborator

My code had a map. The problem was determining if the function all is inside of a continue construct. To determine that, I need the basic block the function call belongs to (inst-to-block mapping) and know that it dominated by the continue target for the loop it is in (dominator-trees).

If I simply chose to not inline any function containing an OpKill, then many shader will be able to be correctly legalized. We would have to implement #2726 first.

I will see if there is another way to keep track of whether or not we are in a continue construct.

@programmerjake
Copy link
Author

programmerjake commented Jul 18, 2019

create a map tracking which call instructions are in continue constructs? that shouldn't be that hard to track and can be used in conjunction with the opkill-containing map to stop inlining.

another alternative is to create a separate function with a body that's just OpKill and replace all other opkills with a call to that function (edit: idea was already in #2726)

s-perron added a commit to s-perron/SPIRV-Tools that referenced this issue Aug 12, 2019
We are no able to inline OpKill instructions into a continue construct.
See KhronosGroup#2433.  However, we have to be able to inline to correctly do
legalization.  This commit creates a pass that will wrap OpKill
instructions into a function of its own.  That way we are able to inline
the rest of the code.

The follow up to this will be to not inline any function that contains
an OpKill.

Fixes KhronosGroup#2726
s-perron added a commit to s-perron/SPIRV-Tools that referenced this issue Aug 12, 2019
We are no able to inline OpKill instructions into a continue construct.
See KhronosGroup#2433.  However, we have to be able to inline to correctly do
legalization.  This commit creates a pass that will wrap OpKill
instructions into a function of its own.  That way we are able to inline
the rest of the code.

The follow up to this will be to not inline any function that contains
an OpKill.

Fixes KhronosGroup#2726
s-perron added a commit that referenced this issue Aug 14, 2019
We are no able to inline OpKill instructions into a continue construct.
See #2433.  However, we have to be able to inline to correctly do
legalization.  This commit creates a pass that will wrap OpKill
instructions into a function of its own.  That way we are able to inline
the rest of the code.

The follow up to this will be to not inline any function that contains
an OpKill.

Fixes #2726
s-perron added a commit to s-perron/SPIRV-Tools that referenced this issue Sep 9, 2019
If an OpKill instruction is inlined into a continue construct, then the
spir-v is no longer valid.  To avoid this issue, we do inline into an
OpKill at all.  This method was chosen because it is difficult to keep
track of whether or not you are in a continue construct while changing
the function that is being inlined into.  This will work well with wrap
OpKill because every will still be inlined except for the OpKill
instruction itself.

Fixes KhronosGroup#2554
Fixes KhronosGroup#2433

This reverts commit aa9e8f5.
s-perron added a commit that referenced this issue Sep 11, 2019
If an OpKill instruction is inlined into a continue construct, then the
spir-v is no longer valid.  To avoid this issue, we do inline into an
OpKill at all.  This method was chosen because it is difficult to keep
track of whether or not you are in a continue construct while changing
the function that is being inlined into.  This will work well with wrap
OpKill because every will still be inlined except for the OpKill
instruction itself.

Fixes #2554
Fixes #2433

This reverts commit aa9e8f5.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants