这是indexloc提供的服务,不要输入任何密码
Skip to content

[wgsl] continuing block #579

@litherum

Description

@litherum

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]
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    wgslWebGPU Shading Language Issues

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions