gpuweb / gpuweb

Where the GPU for the Web work happens!
https://webgpu.io
Other
4.79k stars 317 forks source link

[wgsl] Support compile-time-constants (constexpr) #1272

Closed ben-clayton closed 2 years ago

ben-clayton commented 3 years ago

Discussed at the 2020-11-30 meeting.

https://github.com/gpuweb/gpuweb/pull/1238 has highlighted the desire compile-time-constant variables.

The textureSample() intrinsics that take an offset parameter require the offset value to be known at compile time. A const variable might be compile-time-known, but is not guaranteed. const at module scope might suffice for offset's needs, but permitting const based on lexical scope is confusing, and specializations may break this anyway.

We may wish to consider adding constexpr as a new variable specifier, which can only be assigned a const_expr expression.

variable_statement would then look something like:

variable_statement
  : variable_decl
  | variable_decl EQUAL short_circuit_or_expression
  | CONST variable_ident_decl EQUAL short_circuit_or_expression
  | CONSTEXPR variable_ident_decl EQUAL const_expr

Exampes:

constexpr a : f32 = 1.0;
constexpr b : i32 = 2;
constexpr c : vec2<i32> = vec2<i32>(1, 2);

The current grammar of const_expr does not allow for identifiers, so constexpr d : vec2<i32> = c; would not currently be valid. const_expr could be altered to:

const_expr
  : type_decl PAREN_LEFT (const_expr COMMA)* const_expr PAREN_RIGHT
  | IDENT
  | const_literal

Where the IDENT needs to resolve to a constexpr variable, allowing for:

constexpr e : vec2<i32> = vec2<i32>(b, b);
constexpr f : vec3<i32> = vec3<i32>(e, 5);
kainino0x commented 3 years ago

Open question I don't think we figured out in the meeting:


I think @jdashg and I are trying to advocate for the following, which has been fleshed out with a bunch of more informed research:

  1. Currently (I think), const outside functions (global_constant_decl) exactly corresponds to (the result of) OpConstant or OpConstantComposite. Function-scope const (variable_statement) is completely different; it corresponds to the result of a value-producing op (load, add, etc.)

    We should change the outside keyword to constexpr and keep the (non-spec-constant*) functionality exactly the same. And I think we need Ben's addition above to allow constexpr names to be used inside other constexprs (it's actually necessary, to be able to directly represent a lot of SPIR-V, I'm pretty sure, so this is somewhat necessary and entirely orthogonal).

    • * I think specialization constants need to become more distinct, with another separate keyword. Not all specialization constants have IDs (so we can't identify them by whether they have [[constant_id]]): spec constants with IDs must be scalar, while others are just constants that happen to be built up from other specialization constants (and may be scalar, OpSpecConstantComposite, or OpSpecConstantOp).
  2. Then, we should consider allowing constexpr inside functions as well, for convenience (unless spir-v doesn't allow that, maybe; but we could do it even if it doesn't).

kvark commented 3 years ago

Thank you for writing this! As I stated on the call, I think we need to solve this local vs global constants difference before considering the const expression semantics.

unless spir-v doesn't allow that, maybe; but we could do it even if it doesn't

In SPIR-V the constants are declared at the beginning of the module. They aren't local to functions.

I think specialization constants need to become more distinct, with another separate keyword

I don't see how the story is leading there. If we want the module-scope constants to become OpConstant in SPIR-V, it seems only reasonable to keep the approach we have now where some of these are annotated as specialization constants. What's the problem you are trying to solve by separating the two?

The "constexpr" name is a bit confusing if it applies to spec constants. It seems to me that we should just keep the "const" of the module scope, and rename the function-scope "const".

Not all specialization constants have IDs

True, but that's on SPIR-V side. It may very well be an implementation detail of the shader translation, this doesn't necessarily affect how we define WGSL.

dneto0 commented 3 years ago

I can confirm this:

Currently (I think), const outside functions (global_constant_decl) exactly corresponds to (the result of) OpConstant or OpConstantComposite. Function-scope const (variable_statement) is completely different; it corresponds to the result of a value-producing op (load, add, etc.)

As to this question:

Can texture sample offset be a specialization constant?

Yes it can (in SPIR-V Vulkan). The offset parameter in the texture builtins corresponds to the ConstOffset image operand. It says:

A following operand is added to (u, v, w) before texel lookup. It must be an <id> of an integer-based constant instruction of scalar or vector type. It is invalid for these to be outside a target-dependent allowed range

That defined term "constant instruction" is:

Constant Instruction: Either a specialization-constant instruction or a non-specialization constant instruction: Instructions that start "OpConstant" or "OpSpec".

So: it's specializable either directly (in the scalar case) or indirectly computed via OpSpecConstantComposite or OpSpecConstantOp.

NOTE: This is the baseline functionality in Vulkan. There is extra optional functionality:

dneto0 commented 3 years ago

I'll restate here something I said in the meeting: the various kinds of constant-ness maps to the value being finalized/fixed at different phases in the shader lifecycle:

I've pointed out the missing bits of expressiveness. That's where a constexpr-kind of feature can fit.

In terms of validation, it follows that when adding the ability to form expressions, the terms in an expression at a particular phase can only come from the same phase or earlier phases.

dj2 commented 3 years ago

In Tint for:

* when a name comes into scope
'const' in function scope. For example, a 'const' declared inside a loop can have a different value in each iteration. But while that instance is still in scope, it retains the same value. (Like @kainino0x said, it corresponds to a singly-assigned value, an SSA ID)

If the initialize is a Scalar or Type constructor then we'll generate an OpConstant. For any other type of initializer we use OpVariable as we have to set it at runtime. So, in the case where the initializer is const_expr we treat the const inside a function as the same as outside a function.

dneto0 commented 3 years ago

About naming:

We had mentioned the "go with the JS way of doing things" as good default. That works for 'const'. But JS doesn't have those 4 phases of const-ness. So it's inevitable that we'll have to go beyond JS.

dneto0 commented 3 years ago

So, in the case where the initializer is const_expr we treat the const inside a function as the same as outside a function.

Right, when the translator sees that values are more fixed than they could be, it has the option of bubbling up the definition of that value up to a category in an earlier phase.

ben-clayton commented 3 years ago

Can texture sample offset be a specialization constant?

Because this is being asked again, I want to reiterate - even if it is supported, I don't think we should support it for offset. Being able to do compile-time validation on the offsets allows us to avoid expensive runtime validation. Performing that validation at pipeline creation time is going to be really messy.

Whatever the solution (constexpr or otherwise), I feel it should not support spec-constants. Otherwise we'd have to propagate some flag to indicate whether the variable could originate from a spec-constant, and this is entirely hidden from the grammar. Having a unique keyword for compile-time-constant-and-not-spec-constant is the ideal for offset. I don't really mind what that thing is called.

kvark commented 3 years ago

@ben-clayton this is a great problem to consider!

even if it is supported, I don't think we should support it for offset. Being able to do compile-time validation on the offsets allows us to avoid expensive runtime validation.

I have 2 points that counter this argument:

  1. It may very well be that we can not even generate a SPIR-V module at createShaderModule. There are reasons, one of them is, for example, that some WGSL entry points may use capabilities that aren't supported by the logical device. In WebGPU we considered this to be a valid use case, but SPIR-V would complain and fail to load in vkCreateShaderModule. There are other reasons, too - a topic for another discussion, but the point is that this may be required, and this would be very much in line with what we do for MSL and DXIL anyway.
  2. If we detect that a specialization constant is used for the offset, we could insert a OpSpecConstantOp sequence to clamp it to 8, using a combination of OpUGreaterThan and OpSelect. This wouldn't require any run-time checks.

TL;DR: specializing the texture offsets is a valid case that we can and should support

Edit: the reason for (1) is not valid. We could filter the entry points right away. There are other reasons though, like https://github.com/gpuweb/gpuweb/issues/1064#issuecomment-733067687

ben-clayton commented 3 years ago

If we detect that a specialization constant is used for the offset, we could insert a OpSpecConstantOp sequence to clamp it to 8, using a combination of OpUGreaterThan and OpSelect. This wouldn't require any run-time checks

I still believe that erroring is preferable to clamping, as going OOB is almost certainly not what the developer intended, and would likely be masking a bug. The difference in behavior between compile-time-error when non-spec-constant vs clamping when spec-constant a bit jarring, but I could live with it, I guess.

dj2 commented 3 years ago

What are peoples thoughts on doing:

  | CONST variable_ident_decl EQUAL const_expr 
  | FIXED variable_ident_decl EQUAL short_circuit_or_expression

Where a FIXED is set to a specific value at creation and cannot be changed.

kdashg commented 3 years ago

It would be great to have different names for "inside-const" and "outside-const", and it seems like "const" and "constexpr" respectively would fit the bill here. There are a bunch of real concerns about constexpr ballooning into something hard to implement, but I don't think we need sophisticated arbitrary-expressions-and-function-calls constexpr yet. constexpr INDENTIFIER would be initialized a (grammar) const_expr, and then that ident could be used anywhere where we require const_expr today.

ben-clayton commented 3 years ago

It would be great to have different names for "inside-const" and "outside-const", and it seems like "const" and "constexpr" respectively would fit the bill here.

Thanks to the discussion around offset, we're now aware of the various definitions of const based on lexical scope and what's on the RHS of the assignment. However, until the limitations on offset became apparent, this was mostly an implementation detail and IIUC had no significant semantic significance to the developer aside from "this variable is immutable" (with possible exception to spec-ops).

Renaming const based on lexical scope may help differentiate what's going on in the SPIR-V backend, but does it really help the developer writing in the language? I have concerns around renaming const based on lexical scope, as this pushes the complexity burden on to our users, perhaps needlessly. Maybe I'm wrong, and the backend differences are significant enough to warrant different keywords for different lexical scope declarations, but I don't think we've proved that yet.

There are a bunch of real concerns about constexpr ballooning into something hard to implement, but I don't think we need sophisticated arbitrary-expressions-and-function-calls constexpr yet.

I agree this isn't necessary for MVP, but I start to have doubts about my own constexpr proposal if we're not intending to support arithmetic (at the very least) at some point. As mentioned in the call this week, a compile-time-const that can, and will only ever contain a trivial literal-like RHS is as good as a scoped AST substitution. A preprocessor #define is a kludgey scopeless way of giving a literal-like value a name. Even putting a comment on the texture-call argument isn't far from the limited benefits of a named literal-like value. If we really want a way to declare a variable with literal-like value and never wish to treat it as a true expression, then I agree it shouldn't be called constexpr. Maybe alias foo = 42; or something is better.

With that said, I'm very much for having a constexpr variable that supports arithmetic, one day. I've lost count the number of times in my career that I've written generators to inject precomputed math into shader code (Poisson Disc offsets being a good example), and I'd have loved to have the ability to calculate this stuff without writing a bunch of bespoke and bug-prone generator tooling.

kdashg commented 3 years ago

In a way, I don't care about the implementation details of constexpr (outside of "is it feasibly implementable"), and my goal is not to surface here what's going on in some conceptual spir-v equivalent. Rather, I think it's valuable to give two different concepts, that have different constraints and different valid usages, different names. Given that a very limited constexpr (or consteval), like we apparently already have for const_expr for composite (vec) type declarations, really matches well what we want here, is feasible and not onerous to implement, and is forward-compatible with expanding what we expect implementations to calculate at compile time in the future, we should move that direction.

This is in contrast to function-level const, which is just an immutable variable declaration, and is not constant for the purposes of operations that require constants, like ConstOffset for image loads, which must be a for-real compile-time constant (or spec-const), not an immutable-marked local variable.

Effectively, we already have some form of constexpr for module-const, so it's too late to avoid adding a constexpr, because we already have one, or something very much like it. Rather, it'd be great to have a more clearly distinct name for these, such as constexpr, consteval, or static const. I'm not proposing any changes here to what module-const can do, just a renaming in line with the original post here, and I think we're largely getting sidetracked with discussing implications of what constexpr some day might be, rather than making our situation a little bit more clear today.

dj2 commented 3 years ago

@jdashg thoughts on the const and fixed I suggested above? @kvark I don't know if eyes is a good emoji response or a bad one, heh.

kainino0x commented 3 years ago

Effectively, we already have some form of constexpr for module-const, so it's too late to avoid adding a constexpr, because we already have one, or something very much like it. Rather, it'd be great to have a more clearly distinct name for these, such as constexpr, consteval, or static const.

+1.

On the topic of static, I would like to use it, because it's a very apt word for what we mean, except that its meaning has been so heavily overloaded by C, C++, Java, and so on.

I'm not proposing any changes here to what module-const can do

I wasn't originally intending to propose this either, but in investigating specialization constants I became convinced that the model we have today is flawed (see above); maybe I'm wrong.

ben-clayton commented 3 years ago

Effectively, we already have some form of constexpr for module-const, so it's too late to avoid adding a constexpr, because we already have one, or something very much like it. Rather, it'd be great to have a more clearly distinct name for these, such as constexpr, consteval, or static const.

Okay, I agree with this. You're right that module-scope const isn't just-an-immutable var as I had suggested (it cannot be assigned a non const_expr), and if the module-scope const looks like a compile-time-const-or-spec-op, swims like a compile-time-const-or-spec-op and quacks like a compile-time-const-or-spec-op, it should probably be called a compile-time-const-or-spec-op (constexpr, consteval, static const, fixed, or something else).

That said, allowing only a compile-time-const-or-spec-op at module scope, and const only at function scope is still less than ideal, and I'd prefer to be consistent and allow both regardless of scope. This can be discussed as a followup.

I'm not proposing any changes here to what module-const can do, just a renaming in line with the original post here, and I think we're largely getting sidetracked with discussing implications of what constexpr some day might be, rather than making our situation a little bit more clear today.

The reason I brought this up is that @dj2 has expressed a strong desire to never support expressions that go much beyond what const_expr is today, and so is not keen on the term constexpr as this is suggestive of something more flexible than what we'll ever have. I assume this is why you're keen on fixed, @dj2 ? To reiterate my position, I'm very keen on supporting at least arithmetic for these at some point in the future, but if that's never going to happen, I agree constexpr / consteval are bad names.

@kvark - I've given https://github.com/gpuweb/gpuweb/issues/1272#issuecomment-741866589 more thought, and I think I'm okay with offset coming from a spec-op with the clamping as you suggested. If we're to do this, I'd recommend keeping the clamping behaviour consistent with non-spec-op, and removing the proposed compile time error from the spec, but have the compiler produce a warning (e.g. warning: offset.x evaluates to 10, which is outside the allowed limits of [-8 to 7]. Clamping offset.x to 7). This keeps behaviour consistent regardless of value origin, and still alerts the developer that they're likely doing something stupid for the more common case of a literal-like in the shader source.

dj2 commented 3 years ago

Partly, I think fixed is a good name for the concept and I've never really liked constexpr. Even if it was calculable (which as I mentioned I'd prefer not to do) the value will still be fixed at compile time.

kvark commented 3 years ago

@dj2 I don't have strong feelings about "fixed", hence the eyes emoji (aka - acknowledged). I do agree that it's an interesting choice of a word that is clear, yet not confusing with regards to prior art in let,val,var,const, etc. It's definitely better than what we have today :)

dneto0 commented 3 years ago

Caught up on this.

shaoboyan commented 3 years ago

I have an use case to support this constexpr when I'm writing switch-case statement in below:

constexpr ALPHA_MODE_DONT_CHANGE : u32 = 0u;
constexpr ALPHA_MODE_PREMULTIPLY : u32 = 1u;
constexpr ALPHA_MODE_UNPREMULTIPLY: u32 = 2u;
switch (uniform.alphaOp) {
    case ALPHA_MODE_DONT_CHANGE: {
      break;
    }
    ....
}

This will improve the code readability a lot if we support constexpr and allow it to be in 'case' selector.

kdashg commented 3 years ago
WGSL meeting minutes 2021-08-17 * MM: * What should be able to be constexpr * Should it be an attrib? New keyword * Can you take a pointer to them? * AB: + Must the compiler evaluate them? * MM: Let’s start with, what can flow into a constexpr? * JG: Just literals and constexpr? * MM: What about math? (we want math) * JG: A couple different tiers: * literals and constexpr * Operators * Builtins * Constexpr functions * MM: Can’t have sampling from a constexpr, but could do sqrt. Like mathy vs not-mathy * JG: I want to avoid defining what’s required for a function/builtin to be a constexpr, but maybe we can just mark a bunch of builtins as constexpr * MM: Eventually we want users to be able to do this, even if it’s illegal today * DM: If we have constexpr, is there still use for non-overridable constants * MM: I would view them as the same feature * DM: Same syntax then? If not overridable, then it’s constexpr. * AB: Though if you want to forbid sampling, it would look different * MM: At the global scope, just `let` would be constexpr. * AB: Yes, but not outside global scope * MM: We probably want a way for an author to commit that something is constexpr (and have it validated that it is) * JG: OK I think we know what we want to be constexpr-able * […] * MM: Imagine a program * Let x = myTexture.sample(...); * Let y = 5; * myTexture.sample(..., y); * MM: From an author’s perspective, we want authors to know that y can flow into other constexpr, but x cannot. * AB: We could have it either be implicit or explicit * MM: There are places where constexpr are required, so ideally authors can assert (early) what’s constexpr or not. * AB: Technically optional * MM: It’d be spooky action at a distance, where somewhere up the dependency tree breaks something downstream. * JG: What about `constexpr y = 5;`, like instead of `let` * DM: Do we need these at local scope? * MM: I now think we do, since it’s nice to have declarations near their use site. * [...] * MM: Could have a rule that, for everything you would use `let` for at the global scope today, instead you have to use `constexpr`, and make it illegal to use `let` at the global scope. * MM: So potentially, at the global scope, `constexpr x = 5` is legal, but `let x = 5` isn’t. * AB: Well, this gets into “when does the compiler need to evaluate these at compile time” * MM: Specs are as-if, so I think we don’t need to make strong commitments here * AB: In floating point, should everyone produce the same bits? E.g. for a program that (implicitly?) relies on rounding errors? * MM: We don’t commit to ieee754 so I think it’s fine to leave unspecified * AB: Does constexpr in control-flow change static use rules? * MM: I don’t think so, and I think we’ve previously said no. In particular dead code elimination is best-effort. (not required) * MM: Sort of a separate thing, we should have a reflection API where we would not require identical reflection data to be returned on different APIs or OSes. This reflection data would return information about the shader _after_ the OS-level optimizers have run. It would be part of the contract this _this_ specific API would be intentionally non-portable. The reason is from some advice that Microsoft gave maybe 2 years ago about how authors often specialize huge shaders, and rely on post-optimization reflection data to determine which inputs they actually need to hook up to the shader
litherum commented 3 years ago

Here's a proposal:

Part 1: Expressions

All expressions are either constexpr or non-constexpr.

An expression is constexpr iff all the leaves in its AST are constexpr, and all the function calls in its AST are to constexpr functions.

Example: sqrt(myConstexpr[4][myOtherConstexpr].foo * 5) is constexpr.

Part 2: Variable annotation

Indicate a constexpr variable by a new variable keyword: constexpr:

var aVariable: i32 = 0;
let aConstant: i32 = 1; // Not a constexpr
constexpr aConstexpr: i32 = 1;

(Rationale: This is a natural extension from the var vs let divide we have today.)

For future expansion, mark constexpr as a reserved word, to make sure users can't for example make a struct named "constexpr".

Variables which do not use this keyword in their declaration are not constexpr.

A constexpr variable must be initialized to a constexpr expression. Once an author has a constexpr variable, the rules for using it follow the rules for let variables, with some exceptions (more on this below).

Part 3: Constexpr functions

(For now,) all user-defined functions are non-constexpr.

A function is constexpr iff it is in one of these categories of built-in functions:

Part 3: Acceptable types

A constexpr expression (including all its sub-expressions) must be a "constructible" type:

Part 4: Use

Exceptions to the rules for let variables are:

constexpr expressions do not cause observable dead code elimination.

Note: The fact that the rules for using constexpr variables follow the rules for let variables means constexpr variables are lexically scoped, and their names can shadow each other.

Part 5: Module scope

All current non-overridable let variables at module scope already follow these rules above, and therefore could be promoted to constexpr variables. Let's make a rule that all let variables at global scope must be overridable - if an author wants a non-overridable let, they can always use the strictly-more-powerful constexpr instead.

Kangz commented 3 years ago

Thank you for putting this up! I'm a bit lacking background on this discussion so excuse if the following seems misguided.

First WGSL currently doesn't use const so maybe that could be used as the keyword instead of constexpr. We also might want to have user-defined constant evaluated functions at some point and that could use const fn instead of just fn.

The other thing is that we have a lot of different volumes of space-time at which the value of variables is fixed:

For the most part the list above seems to be in subtyping order: "uniform" variables are "workgroup uniform". In defining constexpr variables it would be great to also find a consistent way to talk about the various types above so that developers can make type assertions but also so that the specification can use the types to define the uniformity analysis. The developer could also add uniformity annotations to the arguments of function to help guide the uniformity analysis.

(thinking about it, const fn will be great as the result will be just as good as the worst of their arguments)

RobinMorisset commented 3 years ago

We already agreed to have some form of optional annotations for uniform variables: https://github.com/gpuweb/gpuweb/issues/1791 (although I still have to propose a spec change for it).

Making it part of the same hierarchy as const/let/var is an interesting idea, although I am not sure I am in favor. One difference between these concepts is that (currently at least), we are making const/let/var a non-optional thing (we won't try to infer that some var is actually constexpr and thus can be used that way), whereas we are trying our best to infer uniformity and only need annotations to make it more precise at the global level.

litherum commented 3 years ago

I made one quick edit to the proposal above: instead of saying things like array sizes can be constexpr variables, it now says array sizes can be constexpr expressions. If we support constexpr, we ought to be able to support array<i32, 1 + 1>.

Kangz commented 3 years ago

@RobinMorisset agreed that the time (constexpr, pipelineexpr, ) and space (uniform vs. not etc) dimensions should be mostly separable. I shouldn't have bundled them together like that. +1 on not inferring the constexpr-ness for variables.

dneto0 commented 3 years ago

See https://github.com/gpuweb/gpuweb/issues/732#issuecomment-623923613 for interesting issues/questions related to when expressions may be or are required to be folded. (from @johnkslang)

kdashg commented 3 years ago
WGSL meeting minutes 2021-08-24 * MM: [highlighting details from his proposal] * DM: Implementation burden. Eg. all the constant folding. * MM: Depends if it’s observable. E.g. constexpr that sizes an array. Compiler is required to compute the value at compile time. * JG: Stops short of user functions as constexpr. * MM: Right. Would be a big can of worms to tackle for now. * MM: This proposal doesn’t mark a function as constexpr. It’s just a list in the spec. * DM: Nonportability is expected. It’s not intuitive. * MM: Logic is you already get different results for different machines anyway. * MM: If you limit to integral, then it would be portable. But it would be unwise for shaders. * BC: CTS tests have to accommodate both constexpr and not. * DM: It’s counterintuitive. When I define constexpr, I expect it to be “just a number” outside the program. E.g. if pi is different. People don’t expect that. * MM: So you’d want a rule that the compiler _must_ evaluate at compile time, and that it _must _be the same. * DM: Just trying to see tradeoffs here. * JG: Do worry about requiring too much exactness. E.g. would limit the implementation for something like matrix inversion. * DM: Inversion is an extreme, but other cases exist. * MM: stuck to “math”-like things. * JB: Can always add to the list later. * BC: Texture offset parameter was a motivator. * MM: Cases where it’s used: array sizing; case statements; offset to textures; index into array-value (let-declared array). * DN: I’m concerned that we don’t have a lot of CTS tests for what we already have, yet we’re proposing a bunch of new tests for this * BC: I do think that the complexity add for CTS tests would be minimal, since we should just be able to toggle between constexpr and not. * MM: So argument is that set of testing beyond the toggleable bit isn’t huge? (yes) * BC: It’s definitely significantly more work for us to do for MVP * MM: Reason this came up was for dynamically sized workgroup thing, yes? * DN: Heard from internal discussion that because *this* doesn’t include overrideable constants, then that makes it less attractive to me. * MM: What did you want to solve? * DN: Want to size the workgroup data array sizes by the pipeline-overrideable constant for the workgroup size. * MM: You would like this proposal more if pipeline-overrideable were constexpr (yes) * BC: I think constexpr also makes things much nicer for texture fetch offsets too. * DM: Didn’t we discuss before that constant offsets could not be pipeline-overrideable? * DN: Yes, so there’s sort of two levels here. * JG: `pipelineexpr`? * MM: Could take every pipeline-overrideable constexpr and make its dependencies also pipeline-overrideable in our implementation backends * DN: Yeah, maybe? * MM: Not sure if that’s implementable, though. * JG: Are we ready for writing spec text? * MM: Based on this convo, not yet. * JG: Should we come back to this next week? * DN: I think we should see what this looks like in implementation, as well as discussing more internally. I don’t know if we’ll have time to do enough implementation here to have feedback soon. Further, as DM said, maybe this isn’t M enough for MVP. * GR: If this is primarily about texture offset, well tex offset isn’t super important, so it’s worth weighing how useful it would be vs how much it would contort the language. * MM: There are four usecases, so it’s not just tex-offsets. * DN: All four are actually ints, for what it’s worth * MM: It would make me some-frowny-face about not having floats because of the proposed performance benefits of compile-time * DN: I’m not that convinced by the perf usecase * JG: Sometimes people do float math and then pull an int out of that, so floats may be useful even if only ints are needed for constexpr usages * GR: Tex offsets can’t be pipeline-overrideable right? (yes) If that’s what we’re looking at, that’s a problem. Tex offsets needing to be [very primitive? Literal?] is a really old restriction * BC: I do think tex-offsets are useful, and I have used them in the past * DN: Clspv translates OpenCL C to Vulkan SPIR-V. CL C has pointer-to-local argument that is sized more dynamically (actually at enqueue time but in Vulkan would be pipeline-time.). Clspv remaps those to module-scope workgroup variables with a specializable size, then informs the application (sideband data) to say “set this size to the array size you wanted; rather than what you would have set a enqueue time”. So that’s proof that the mapping works one way; I suspect strongly that a useful feature can be mapped the other way (so Metal style to Vulkan style), with the restrictions we already know we have to have. I want to think more about MM’s idea of elaborating out the overridable constants and doing some of the constexpr math in the pipeline creation step. (So put part of the “compiler” in the pipeline creation…) Want to think over the next week. * JG: Let’s come back to this next week *
kdashg commented 3 years ago
WGSL meeting minutes 2021-08-31 * DN: Some new discussion internally. I think it was notable that the primary use cases were all integers, which makes thing easier because we don’t need to worry about float issues. * DN: Some users wanted to be able to get the length of a fixed array, rather than repeating the length for the array and for the iteration over it. * DN: We do have a dearth of CTS tests, and if I were to choose either CTS or working on this feature, I would like to work on CTS first before working on this new feature. * MM: +1 to array length builtin * DM: Where this would fail would be if you wanted multiple arrays of the same type. * MM: I think it’s still useful and we should do that part. * DN: I think I can propose that. * MM: For this I think we want to support let or var, where implementations don’t do an actual function call. * DN: We can make a carveout for this, to make it like sizeof, but I don’t know if we can forbid execution of the expression called to it. * MM: E.g. ArrayLength(foo(5)), would call foo(5) * DM: Also for static use, if I use ArrayLength(foo), I expect foo to be statically used * DN: “static use” is one thing, and actually causing the accesses to occur (by requiring the evaluation of the argument) * BC: ArrayLength has to be very special (even as a builtin) for dynamic sized arrays * Resolved: Revisit post-mvp * DN: I will also say that I want CTS first, but then constexpr rather than a workgroup size proposal that I don’t like. But picking two of those three, I would pick CTS+constexpr. * BC: Constexpr can be done in stages. The builtins will cause most of the work here. We could start with integer arithmetic and binary and unary operators on those. Agree with David in that not necessarily needed right now. * JG: Do we need constexpr OR workgroup-size for mvp? If so, we should keep working on this. * DN: Internal users have said that our lack of support for good workgroup size (or constexpr) is really annoying, though tolerable for now. Would really want this for v1, but probably not needed for mvp. * DM: What would you like to go back to for solving this? Overrideable constants? * DN: Yeah, but keeping in mind what Apple wants wrt pipeline-overrideables * MM: Could we talk again next week? * DN: Probably not. * MM: Ok, will ask the same question next week. * DN: For next week, would rather present something on attributes-on-types.
kdashg commented 3 years ago
WGSL meeting minutes 2021-09-14 * JG: Sounds like we roughly agree we should have it, but not decided what’s implementable well on various backends, and what those mappings look like. Would be great to surface the “cool” parts of underlying platforms. * JG: Should we take a stab at trying it, then iterate? * DN: Suppose this is the same as what happened at C++ constexpr. Don’t paint yourself into a corner. * JG: Might be reasonable to start with just integers, then expand later. Would be cool to do floats too. Try any place where you currently need an integer literal, e.g. offset. Then we would have to decide what syntax you want. * DM: Earlier feedback from Google was constexpr didn’t solve the problems we wanted solved. E.g. sizing your array proportional to workgroup dimensions. * JG: Even if you can’t nail down everything, trying something limited can give us something structured to work with. Can revisit before v1 and cut it if it’s not very useful. * DN: Browser is also not necessarily the only tooling that will need to interpret/understand WGSL. Other tooling would need to. * DM: The scope can be too big. Do you want the whole standard library? * JG: That’s one reason to limit initial scope to integers. Cuts down a lot. * DM: Initial issue was about texel offsets. * DM: For that, assumed we could use a module-scope let, if it’s not overridable. * JG: That’s not allowed by the grammar right now. See const_expression grammar rule. * DN: There’s a validation rule on the ‘offset’ parameter that it must be const_expression. * BC: At least one backend needs a literal. * JP: module-scope let must not have arithmetic in its initializer. * AB: This is where I get into how big the scope is. If this is one problem we want to solve, then it adds restrictions on how it’s implemented. * BC: Module-scope let is our current const-expr but highly constrained. * DM: Why not just fix this by updating the grammar to allow the module-scope let in more places. * JG: Its good to have first-class constexpr. * BC: It’s offputting to have separation of module-scope and function-scope ‘let’ being different. * JG: Would like to require use of keyword ‘constexpr’ to make it behave as one. * JG: Think we need (1) list of problems to solve, and (2) proposals to address them. ---
alan-baker commented 2 years ago

Google is happy with @litherum's proposal in conjunction with the discussion in #2238.

dneto0 commented 2 years ago

Something to watch for:

Any constexpr parts of attributes on a declaration must not depend on the declaration itself (directly or indirectly). I think this is already covered by the spec, because the attributes on a declaration form a part of the definition of the declaration. (And if that's not clear then we should make it clear).

One example is:

[[stage(vertex)]]
fn constexpr vert(                    // made up constexpr syntax for illustration
       [[location(i32(vert(5).x))]] f: f32           // What's that location?!!
  ) -> [[builtin(position)]] vec4<f32> {
  return vec4<f32>(f);
}

One thing we can do to keep things a bit simple is to say that an entry point function must not be constexpr.

kdashg commented 2 years ago
WGSL meeting minutes 2022-02-01 * Where are we? What do we need? * AB: Think this is tied up with literal typing unification. But have a bunch of decisions * E.g. which types: scalars, vectors, sure. Structures and arrays interesting. * Have broad consensus * MM: Should be the same set of types and operations that overrides support. If you have decl foo = 1+1; // debate over whether ‘foo’ has concrete type or not. * MM: Think should decide same way for override and constexpr. * KB: override must have concrete representation since you can set it from the API side. * .. * BC: think override should have concrete type. Google thinks a const definition _can_ have a non-concrete type, to allow refactoring into decls. E.g. declare pi once in your shader, and use it in other non-concrete expressions, with various other types. * MM: you don’t have substitution ability for override. Design tension with making similar language features work similarly. * KG: As first approximation, yes, make them have same feature set. Break away from that when it’s justified. * BC: Not clear that const and override have to be very similar. Can see override and let to be very similar. * KG: Const to feed into override, not the other way around. * BC: Three tiers: const (shader-creation time), in const domain; override (pipeline time) can use const and other overrides; runtime-tier (let and var). Anything below can use something from above. * MM: Had early proposal: that if we land constexpr, we can remove let at module-scope. (Which Ben was saving for later) * BC: Clearly runtime types need a concrete type. Pretty clear overrides need concrete type (because feed values from API side). Need a contract for that. The only place we don’t necessarily need the concrete type are the ones at shader-creation time. Google stance is not that all const are typeless. But instead, much like var and let, you can specify a concrete type, or not. * const pi : f32 = 3.14… ; // e.g. this make pi have an f32 value. * Const abstractpi = 3.14….; // is the more abstract type * KG: Think we all agree that const can have concrete types. Can land an initial constexpr which _requires_ concrete types. And can enhance later. * BC: Don’t think it’s any additional work to support the more abstract types for constexpr evaluation. * MM: Ben, I think you convinced me. We’re both comfortable with allowing abstract types for constexpr. Convinced by the statement that it’s about “storage”. Storage for constexpr is in the compiler, not the GPU memory. * MM: Should still be possible to put explicit declared type for const. * KG: Need beginnings of spec text for this now. * Grammar * Annotations of builtins BC: Need agreement on whether override…… (will provide example)
dneto0 commented 2 years ago

Fixed by #2668