Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify whether type checking occurs for short-circuited RHS expressions #4551

Open
jrprice opened this issue Mar 28, 2024 · 8 comments
Open
Labels
wgsl resolved Resolved - waiting for a change to the WGSL specification wgsl WebGPU Shading Language Issues

Comments

@jrprice
Copy link
Contributor

jrprice commented Mar 28, 2024

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?

@jrprice jrprice added the wgsl WebGPU Shading Language Issues label Mar 28, 2024
@alan-baker
Copy link
Contributor

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.

alan-baker added a commit to alan-baker/gpuweb that referenced this issue Mar 28, 2024
Refs gpuweb#4551

* If the array element count expression is a const-expression, invalid
  values should be shader-creation errors
@dneto0
Copy link
Contributor

dneto0 commented Mar 28, 2024

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           

@dneto0
Copy link
Contributor

dneto0 commented Mar 28, 2024

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. 

@dj2
Copy link
Member

dj2 commented Mar 28, 2024

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?

@jrprice
Copy link
Contributor Author

jrprice commented Mar 28, 2024

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.

@dj2
Copy link
Member

dj2 commented Mar 28, 2024

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.

@dneto0
Copy link
Contributor

dneto0 commented Apr 16, 2024

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.

@kdashg
Copy link
Contributor

kdashg commented Apr 17, 2024

WGSL 2024-04-16 Minutes
  • KG: Resolution is type checking always occurs.

@kdashg kdashg added the wgsl resolved Resolved - waiting for a change to the WGSL specification label Apr 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wgsl resolved Resolved - waiting for a change to the WGSL specification wgsl WebGPU Shading Language Issues
Projects
None yet
Development

No branches or pull requests

5 participants