Control flow: a switch inside a loop

Guest branches translate. Forward branches skip blocks, backward branches loop, and both run on a real device. Forty-one execution tests.

The shape is a dispatch loop (D110): every guest basic block is an arm of one OpSwitch inside one loop, a program counter selects the arm, and a branch is an assignment to that counter. No control-flow analysis, no relooper, no reducibility requirement - the guest can branch backwards into the middle of anything from several places at once and the result is still valid SPIR-V.

It was mechanical because the architecture was already built for it. predicated.rs has said since it was first written that registers live in memory because "the dispatch loop this strategy is heading towards puts every guest block behind a different arm of a switch. Values cannot cross those arms." That note was written months of work before anything needed it, and it is the reason this was a day's work rather than a rewrite. I had described this in conversation as an unsolved design problem; it was a solved design problem with the solution written into a module doc comment.

The verification that mattered was the existing suite. Thirty-eight tests, every one of them a straight-line shader, all now running through a switch-in-a-loop. If the scaffolding were wrong they would fail, and they are the tests with the most coverage of anything else the translator does. spirv-val confirmed the structure separately.

A conditional branch adds no blocks. The arm evaluates the condition and OpSelects between two counter values, so every arm has exactly one predecessor and one successor. Nesting a selection per conditional would have reintroduced the structure the whole shape exists to avoid.

Surprises.

Not done. Branches on the scalar condition code are refused, because nothing sets it: the scalar compares (SOPC) do not translate, so it would read zero in every shader and every such branch would take the same path. That is the next obvious piece, and it is what the control fixture needs to translate end to end. Fidelity::Subgroup is still a stub. The single-block case still pays for the loop, by choice.