<html>
<head>
<meta http-equiv="Content-Type" content="text/html"/>
</head>
<body>
<div style="color: black;">
<p style="margin: 0 0 1em 0; color: black; font-family: sans-serif;">On
December 7, 2017 11:23:09 Jason Ekstrand <jason@jlekstrand.net>
wrote:</p>
<p style="margin: 0 0 1em 0; color: black; font-family: sans-serif;">>
Ugh, I just remembered we still haven't landed anything for this...<br>
><br>
> On Fri, Oct 27, 2017 at 5:05 PM, Francisco Jerez
<currojerez@riseup.net><br>
> wrote:<br>
><br>
>> This addresses a long-standing back-end compiler bug that could
lead<br>
>> to cross-channel data corruption in loops executed
non-uniformly.  In<br>
>> some cases live variables extending through a loop divergence
point<br>
>> (e.g. a non-uniform break) into a convergence point (e.g. the end
of<br>
>> the loop) wouldn't be considered live along all physical control
flow<br>
>> paths the SIMD thread could possibly have taken in between due to
some<br>
>> channels remaining in the loop for additional iterations.<br>
>><br>
>> This patch fixes the problem by extending the CFG with physical
edges<br>
>> that don't exist in the idealized non-vectorized program, but<br>
>> represent valid control flow paths the SIMD EU may take due to the<br>
>> divergence of logical threads.  This makes sense because the
i965 IR<br>
>> is explicitly SIMD, and it's not uncommon for instructions to have
an<br>
>> influence on neighboring channels (e.g. a force_writemask_all
header<br>
>> setup), so the behavior of the SIMD thread as a whole needs to be<br>
>> considered.<br>
>><br>
><br>
> I've read all three patches and are convinced that the first two are<br>
> correct and that this one does solve the problem.  That said, I'm
still not<br>
> sure what I think of this approach.  It does solve the problem
but it's<br>
> correctness relies on even more subtle interactions between the way we<br>
> build the CFG and liveness analysis than we had before.  I don't
like<br>
> subtle interactions.  That said, I'm a bit on the fence as to how
much<br>
> longer I want to go around in circles trying to fix this...<br>
</p>
<p style="margin: 0 0 1em 0; color: black;"><br>
After some time on a whiteboard, I've come to the conclusion that this is a
reasonable mental model.  In particular it has two nice properties:</p>
<p style="margin: 0 0 1em 0; color: black;"> 1) Every instruction in the
loop is reachable from every other instruction in the loop.</p>
<p style="margin: 0 0 1em 0; color: black;"> 2) Once you enter the loop,
you cannot leave without going through a BREAK.</p>
<p style="margin: 0 0 1em 0; color: black;">Reviewed-by: Jason Ekstrand
<jason@jlekstrand.net><br>
</p>
<p style="margin: 0 0 1em 0; color: black; font-family: sans-serif;"><br>
>> No changes in shader-db.<br>
>> ---<br>
>>  src/intel/compiler/brw_cfg.cpp | 75
++++++++++++++++++++++++++++++<br>
>> ++++++++----<br>
>>  1 file changed, 69 insertions(+), 6 deletions(-)<br>
>><br>
>> diff --git a/src/intel/compiler/brw_cfg.cpp
b/src/intel/compiler/brw_cfg.c<br>
>> pp<br>
>> index fad12eec588..600b428a492 100644<br>
>> --- a/src/intel/compiler/brw_cfg.cpp<br>
>> +++ b/src/intel/compiler/brw_cfg.cpp<br>
>> @@ -98,6 +98,7 @@ ends_block(const backend_instruction *inst)<br>
>>           
op == BRW_OPCODE_ELSE ||<br>
>>           
op == BRW_OPCODE_CONTINUE ||<br>
>>           
op == BRW_OPCODE_BREAK ||<br>
>> +          op ==
BRW_OPCODE_DO ||<br>
>>           
op == BRW_OPCODE_WHILE;<br>
>>  }<br>
>><br>
>> @@ -268,13 +269,57 @@ cfg_t::cfg_t(exec_list *instructions)<br>
>>           }<br>
>><br>
>>          
cur->instructions.push_tail(inst);<br>
>> +<br>
>> +         /* Represent
divergent execution of the loop as a pair of<br>
>> alternative<br>
>> +          * edges
coming out of the DO instruction: For any physical<br>
>> iteration<br>
>> +          * of the
loop a given logical thread can either start off<br>
>> enabled<br>
>> +          * (which
is represented as the "next" successor), or disabled<br>
>> (if it<br>
>> +          * has
reached a non-uniform exit of the loop during a previous<br>
>> +          *
iteration, which is represented as the "cur_while" successor).<br>
>> +          *<br>
>> +          * The
disabled edge will be taken by the logical thread anytime<br>
>> we<br>
>> +          * arrive
at the DO instruction through a back-edge coming from a<br>
>> +          *
conditional exit of the loop where divergent control flow<br>
>> started.<br>
>> +          *<br>
>> +          * This
guarantees that there is a control-flow path from any<br>
>> +          *
divergence point of the loop into the convergence point<br>
>> +          *
(immediately past the WHILE instruction) such that it<br>
>> overlaps the<br>
>> +          * whole IP
region of divergent control flow (potentially the<br>
>> whole<br>
>> +          * loop)
*and* doesn't imply the execution of any instructions<br>
>> part<br>
>> +          * of the
loop (since the corresponding execution mask bit will<br>
>> be<br>
>> +          * disabled
for a diverging thread).<br>
>> +          *<br>
>> +          * This way
we make sure that any variables that are live<br>
>> throughout<br>
>> +          * the
region of divergence for an inactive logical thread are<br>
>> also<br>
>> +          *
considered to interfere with any other variables assigned by<br>
>> +          * active
logical threads within the same physical region of the<br>
>> +          * program,
since otherwise we would risk cross-channel data<br>
>> +          *
corruption.<br>
>> +          */<br>
>> +         next =
new_block();<br>
>> +        
cur->add_successor(mem_ctx, next);<br>
>> +        
cur->add_successor(mem_ctx, cur_while);<br>
>> +        
set_next_block(&cur, next, ip);<br>
>>          break;<br>
>><br>
>>        case
BRW_OPCODE_CONTINUE:<br>
>>          
cur->instructions.push_tail(inst);<br>
>><br>
>> +         /* A conditional
CONTINUE may start a region of divergent control<br>
>> +          * flow
until the start of the next loop iteration (*not* until<br>
>> the<br>
>> +          * end of
the loop which is why the successor is not the<br>
>> top-level<br>
>> +          *
divergence point at cur_do).  The live interval of any<br>
>> variable<br>
>> +          *
extending through a CONTINUE edge is guaranteed to overlap the<br>
>> +          * whole
region of divergent execution, because any variable<br>
>> live-out<br>
>> +          * at the
CONTINUE instruction will also be live-in at the top<br>
>> of the<br>
>> +          * loop,
and therefore also live-out at the bottom-most point of<br>
>> the<br>
>> +          * loop
which is reachable from the top (since a control flow<br>
>> path<br>
>> +          * exists
from a definition of the variable through this CONTINUE<br>
>> +          *
instruction, the top of the loop, the (reachable) bottom of<br>
>> the<br>
>> +          * loop,
the top of the loop again, into a use of the variable).<br>
>> +          */<br>
>>          
assert(cur_do != NULL);<br>
>> -       
cur->add_successor(mem_ctx, cur_do);<br>
>> +        
cur->add_successor(mem_ctx, cur_do->next());<br>
>><br>
>>          next =
new_block();<br>
>>          if
(inst->predicate)<br>
>> @@ -286,8 +331,18 @@ cfg_t::cfg_t(exec_list *instructions)<br>
>>        case BRW_OPCODE_BREAK:<br>
>>          
cur->instructions.push_tail(inst);<br>
>><br>
>> -         assert(cur_while
!= NULL);<br>
>> -       
cur->add_successor(mem_ctx, cur_while);<br>
>> +         /* A conditional
BREAK instruction may start a region of<br>
>> divergent<br>
>> +          * control
flow until the end of the loop if the condition is<br>
>> +          *
non-uniform, in which case the loop will execute additional<br>
>> +          *
iterations with the present channel disabled.  We model this<br>
>> as a<br>
>> +          * control
flow path from the divergence point to the convergence<br>
>> +          * point
that overlaps the whole IP range of the loop and skips<br>
>> over<br>
>> +          * the
execution of any other instructions part of the loop.<br>
>> +          *<br>
>> +          * See the
DO case for additional explanation.<br>
>> +          */<br>
>> +         assert(cur_do !=
NULL);<br>
>> +        
cur->add_successor(mem_ctx, cur_do);<br>
>><br>
>>          next =
new_block();<br>
>>          if
(inst->predicate)<br>
>> @@ -300,10 +355,18 @@ cfg_t::cfg_t(exec_list *instructions)<br>
>>          
cur->instructions.push_tail(inst);<br>
>><br>
>>          
assert(cur_do != NULL && cur_while != NULL);<br>
>> -       
cur->add_successor(mem_ctx, cur_do);<br>
>><br>
>> -         if
(inst->predicate)<br>
>>
-           
cur->add_successor(mem_ctx, cur_while);<br>
>> +         /* A conditional
WHILE instruction may start a region of<br>
>> divergent<br>
>> +          * control
flow until the end of the loop, just like the BREAK<br>
>> +          *
instruction.  See the BREAK case for more details.  OTOH an<br>
>> +          *
unconditional WHILE instruction is non-divergent (just like an<br>
>> +          *
unconditional CONTINUE), and will necessarily lead to the<br>
>> +          *
execution of an additional iteration of the loop for all<br>
>> enabled<br>
>> +          *
channels, so we may skip over the divergence point at the top<br>
>> of<br>
>> +          * the loop
to keep the CFG as unambiguous as possible.<br>
>> +          */<br>
>> +        
cur->add_successor(mem_ctx, inst->predicate ? cur_do :<br>
>>
+                                    
cur_do->next());<br>
>><br>
>>         
set_next_block(&cur, cur_while, ip);<br>
>><br>
>> --<br>
>> 2.14.2<br>
>><br>
>><br>
</p>
</div>
</body>
</html>