Skip to content

Loop unrolling, part 2 (conditional jumps).

The idea is to substitute break and continue with stores to broken and continued variables, and to move all instructions after these jumps to if (!broken) and if (!continued) checks. continued gets reset after each iteration, and we check broken after each iteration to stop early if it's been set to true.

Some notes:

Copy prop parent state chaining is used to "emulate" something like this:

if (!broken)
{
    // ...loop parts go here.
    if (!broken)
    {
        // another loop iteration goes here.
    }
}

while keeping if nesting to a minimum:

if (!broken)
{
    // ...one iteration.
}
if (!broken)
{
    // another iteration.
    // since its copy prop state parent is the previous iteration
    // to copy prop, this is as if it was nested inside the previous iteration
}

Unrolling with conditionals needs each loop part to be split, mostly because for loops with continues are very annoying:

// from for.shader_test
[unroll] for (i = 0; i < 10; i++)
{
    x += i;
    if (tex > 0.5 && i == 5)
        break;
    if (tex > -0.5 && i >= 7)
        continue;
    x -= 1;
}

Since neither d3dbc nor tpf have jumps, our way of compiling continues is to copy the loop iter block (in this example, i++) to before each continue; in pseudo-IR:

loop
{
    if (!(i < 10))
    {
        break;
    }

    x += 1;

    if (tex > 0.5 && i == 5)
    {
        break;
    }
    if (tex > -0.5 && i >= 7)
    {
        i++;
        continue;
    }
    x -= 1;
    i++;
}

This doesn't work for unrolling, because i gets invalidated in the branch, and therefore its value doesn't get substituted in future iterations of the loop.

What unrolling does instead is to push the iter block to the beginning of the next iteration. We can't do this without splitting each loop block off, which I've decided to do with a separate node type which later gets resolved to our current loop nodes if the loop isn't unrolled.


3/8 is required as otherwise copy prop would invalidate all variables that are modified in the loop. !748 (closed) worked around this by copying all instructions from the head of the loop's parent block to the loop itself and applying copy prop to these instructions, which is incorrect for loops nested inside other blocks with loop induction variables set outside their parent blocks (see the example below)


2/8 and 3/8 are required because of shaders like:

float a : register(c0);

float4 main() : sv_target
{
    int i = 0;
    if (a > 0)
    {
        [unroll]
        for (; i < 10; ++i);

        return float4(i, a, 2.0, 3.0);
    }

    return float4(0.0, a, 3.0, 4.0);
}

Without 2/8, once copy prop is stopped at the loop node we would lose all the copy prop state as the stack unwinds.


Prior discussions at !748 (closed) and !786 (merged).

Edited by Victor Chiletto

Merge request reports

Loading