gpuweb / gpuweb

Where the GPU for the Web work happens!

Home Page:http://webgpu.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Clarify whether type checking occurs for short-circuited RHS expressions

jrprice opened this issue · comments

WGSL's const-expr section contains the following note:

NOTE: The evaluation rule implies that short-circuiting operators && and || guard evaluation of their right-hand side subexpressions.

This means that invalid constant expressions on the RHS of a short-circuited operator do not cause shader creation errors, because they are never evaluated. For example:

const foo = sqrt(-1);          // error
const bar = false && (sqrt(-1) == 0); // no error

It's less clear whether rules around type validity that require sub-expression evaluation are included in this. For example, consider:

const foo = false && array<bool, (3 - 4)>()[0];
const bar = false && array<bool, i32(sqrt(-1))>()[0];

The array type requires a positive integer element count, but in order to validate that in the above cases we first have to evaluate the element count expressions. Are these evaluations guarded by the above note, or are they considered to be evaluated independently of the expression tree that the type appears in?

The requirements on element count in array type definitely seem wrong upon reflection:

Delaying all size checks to pipeline creation seems wrong given how few places can utilize a pipeline dependent value. I highly doubt any implementation would be so strict.

At least the final bullet should be changed to something along the lines of:

If N is not greater than zero, it is a shader-creation error if N is a const-expression and a pipeline-creation error otherwise.

That doesn't answer whether the question raised in this issue directly though. My opinion is that it would make sense to fully evaluate the type before evaluating the surrounding expressions.

Note: Array sizes that depend on override declarations have a special type checking rule that allows them to be checked at shader-creation time. The sizes must be:

They are both fixed-sized with element counts specified as identifiers resolving to the same declaration of a pipeline-overridable constant.

Ok, that aside, I think it's reasonably clear from 6.1 Type Checking:

Type checking a successfully parsed WGSL module is the process of mapping each expression to its static type, and verifying that type requirements of each statement are satisfied. If type checking fails, a special case of a shader-creation error, called a type error, results.

I'll emphasize "each expression" must be mapped to its static type. It doesn't matter if the expression is ever evaluated.

Concretely:

const foo = false && array<bool, (3 - 4)>()[0];
            ^bool                ^~~~~~~ AbstractInt and then i32.
                     ^~~~~~~~~~~~~~~~~~~~~~~~~ bool
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bool                                    
const bar = false && array<bool, i32(sqrt(-1))>()[0];
                                          ^~ AbstractInt then AbstractFloat
                                     ^~~~~~~~~ AbstractFloat
            ^bool                ^~~~~~~~~~~~~ i32.
                     ^~~~~~~~~~~~~~~~~~~~~~~~~ bool
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bool           

heh. It's been pointed out I missed the array type (!)
So yeah, you'd get array<bool,-1> which by current text and even after @alan-baker's #4552 would be a shader-creation error.

sqrt(-1) would be a domain error --> shader creation error.

But fair point, if we had a const-expr indeterminate value .... but I think that can't happen, or we would already have a shader-creation (or pipeline-creation) error.

For the override-expression case, you'd argue we don't have an actual array type until the override-expression is evaluated. But you can determine type matching. So yeah, we should tweak the "each expression is mapped to a static type" to also allow the case of using the symbolic sized array type when the size is an override-declared identifier.
And that also means we should disallow this case:

override n: i32;
var<workgroup> w: array<i32, n+ 1>;  // can't have a non-trivial override expression as the size of an array, or you'd never type check it properly. 

So, does this mean we have to run certain portions of const-eval, to calculate 3 - 4 in the array expression, before we do type checking and full const-eval? (full const-eval never sees it because the LHS is false and it bails early)

Do we have a list of the places where this "pre-const-eval-const-eval" has to happen? Is it just array expressions?

I don't think that type checking and expression evaluation are distinct stages of WGSL compilation. They are necessarily intertwined - you can't always do type checking without evaluating some expressions, and you can't always evaluate those same expressions until some amount of type checking has happened. You can see this by extending the original example with more nested array expressions:

const foo = false && array<bool, (3i - array<i32,1>(4)[0])>()[0];

Here, the inner array<i32,1>() constructor expression has to be evaluated and type checked before we can evaluate the array size expression on the outer array<bool, ...> type in order to type check its result.

Right, but in this case, due to the short circuit, we shouldn't be evaluating the entire array<bool, (3i - array<i32,1>(4)[0])>()[0]. There are just some subsections which need to be const-eval'd before we eventually bail out of the full const-eval.

Maybe it falls out naturally that we can eval just the parts needed for type checking and not the full expression.

Here are a few example cases driving discussion here at Google, that I think get to the core of the matter:

const c0 = false && (1/0)==0;  // guarded divide by zero
const c1 = false && 1;  // guarded RHS is incorrect type
const c2 = false && array<bool,-1>()[0]; // guarded invalid size for array type
const c3 = false && array<bool,1>()[false]; // invald index type
const c4 = false && array<bool,true>()[0]; // invalid type for array size.
WGSL 2024-04-16 Minutes
  • KG: Resolution is type checking always occurs.