-
Notifications
You must be signed in to change notification settings - Fork 344
Description
The Problem
All loops in SPIR-V have this structure:
OpLoopMerge %51 %52 None ; structured loop
/* loop body here, possibly including continue and break */
%52 = OpLabel ; continue target
/* loop increment code */
OpBranch %49 ; loop back
(continue will jump you to %52, and break will jump you just after the loop back.)
Note that the loop increment code can contain arbitrary SPIR-V. In fact, it can even contain another whole loop! The only requirement is that the continue label dominate the backedge.
However, in human-writable languages, the contents of a for loop increment can't contain arbitrary code. They can only contain a single statement. Therefore, this is one of the numerous places where SPIR-V is more expressive than human writable languages.
WGSL strives to be a faithful vessel for SPIR-V source (among other things it strives for). Therefore, there's a desire for it to be able to represent this kind of control flow.
Option 1: continuing
This is what WGSL has today. You represent arbitrary code in the loop increment by giving the loop increment a whole block, with {}s.
loop {
if (!(i < 10))
break;
/* loop body */
continuing {
i = i + 1;
j = j + 1;
}
}This is the most faithful to the original SPIR-V. On the other hand, it's confusing to authors.
If additional loop types are added, authors need never write continuing. It can be relegated to "things that are only present to support SPIR-V compilers."
Option 2: Implicit functions
Force compilers producing WGSL to put the loop increment in a new function, which wasn't present in the original SPIR-V source.
fn invisibleFunction(i : ptr<function, i32>, j : ptr<function, i32>) {
i = i + 1;
j = j + 1;
}
...
for (i = 0; i < 10; invisibleFunction(i, j)) {
/* loop body */
}This is the most familiar to authors. On the other hand, scanning for variable references to build the argument list for invisibleFunction is a nonzero amount of work.
Option 3: Comma operator
If we allow all statements to be separated by commas, then we can represent arbitrary code by just a concatenation of the statements.
for (i = 0; i < 10; i = i + 1, j = j + 1) {
/* loop body */
}This means something like the following would be valid:
var i : i32 = 0, loop { ... }, i = j + 7;
The comma operator is already somewhat familiar to programmers of the C family of languages. Just like in those languages, its use would be fairly rare, and could perhaps be relegated to "things that are only present to support SPIR-V compilers." Authors writing normal code need not use it.
Option 4: Restrict the set of acceptable programs
Even though SPIR-V is more expressive than human-writable languages, it is being used as a vessel to represent source code written in those human-writable languages. Therefore, most SPIR-V programs wouldn't actually put multiple statements in the loop increment, because the original source code doesn't put multiple statements in the loop increment. Therefore, we could put restrictions on what we are willing to accept in the loop increment, such that it would only contain a single WGSL statement, and most, but not all, SPIR-V code would be compatible.
SPIR-V optimizers might make this analysis difficult, because who knows what code they will move into the loop increment.
There is already precedent for not representing all SPIR-V programs. There are certain operations which simply can't be representable in WGSL because they aren't representable in either HLSL or Metal Shading Language (for example, OpImageTexelPointer or putting volatile on loads/stores instead of variables). Therefore, any SPIR-V tool will already necessarily have to have a mode switch for WGSL.
The downside is we lose compatibility with more SPIR-V programs. The upside is tools can be simpler.
Option 5: Introduce invisible variables
This would move the loop increment inside the loop body, and add additional control flow around it.
If the original source looked like
for (i = 0; i < 10; [complicated loop increment code]) {
if (...)
continue;
a();
}
This could be represented as
for (i = 0; i < 10; ) {
bool shouldContinue = false;
if (...)
shouldContinue = true;
if (!shouldContinue) {
a();
}
[complicated loop increment code]
}