Skip to content

WGSL 2020 06 16

François Daoust edited this page Dec 2, 2020 · 1 revision
Dean Jackson

:

Chair
Mehmet Oguz Derin

:𐰆𐰍𐰔 :

⌨️ Scribe
Google Meet

:

Location
https://webgpu.dev/wgsl

:

Specification
WGSL Issues

:

Open Issues
Marked Issues

:

Meeting Issues

Tentative Agenda

  • Choose from one of these two for discussion:
    • Task: Invent a standard library (#667)
    • Define the interface of an entry point in a WGSL program (#774)
  • wgsl: Fix conditions on break from continuing (#837)
  • Requiring integral-valued float literals to have .0 seems gratuitous (#739)
  • entry_point_decl should be an annotation on the function (#662)
  • Add spec text around vector accessors. (#852)
  • No explicit grammar for decorations (#689)
  • C-like declaration order (#677)
  • Use brackets for array types (#854)
  • Support #line directive (#606)
  • Updating loop section to include for (#757)

📋 Attendance

WIP, the list of all the people invited to the meeting. In bold, the people that have been seen in the meeting:

  • Apple
    • Dean Jackson
    • Fil Pizlo
    • Myles C. Maxfield
    • Robin Morisset
  • Google
    • Dan Sinclair
    • David Neto
    • Kai Ninomiya
    • Ryan Harrison
    • Sarah Mashayekhi
  • Intel
    • Yunchao He
    • Narifumi Iwamoto
  • Microsoft
    • Damyan Pepper
    • Rafael Cintron
    • Greg Roth
    • Michael Dougherty
    • Tex Riddell
  • Mozilla
    • Dzmitry Malyshau
    • Jeff Gilbert
  • Joshua Groves
  • Mehmet Oguz Derin
  • Timo de Kort
  • Lukasz Pasek
  • Tyler Larson
  • Lukasz Pasek
  • Pelle Johnsen
  • Matijs Toonen

⚖️ Discussion

Agenda for VF2F

  • Namespacing & imports & modules (#777)
  • Timing: what time works best folks (start of day?)
  • Interface matching rules (#644)
  • DN: We’ll probably want to present a list of things that are blocking landing CTS. More of an announcement followed by discussion
  • Add to the ‘for next meeting’ list or a note on discussions if there is more to add

Task: Invent a standard library (#667)

  • MM: Need to make a big chart of what is in all 3 languages. As a first pass, add what’s in all 3. As a second pass, look at things that don’t match and see how/if we can add those
  • DJ: Could that be done as a draft by next week?
  • MM: Could probably be done for next week. Most of work was done for WSL already, can go back over, collate and make a specific proposal
  • DN: Do we want to discuss organization? If we have namespaces, do we want a strawman of how those are organized?
  • MM: Yes. All of these topics are related. Hard to talk about imports without knowing how many sets of things in the standard library. Which functions are there, how namespaces work and how imports work all need to be discussed at the same time.
  • JG: Discussion of if we require folks to import the STL or if it’s always imported.

Define the interface of an entry point in a WGSL program (#774)

  • MM: Off top of my head, the interface is all of the resources as grouped in bind groups and the imports (talking about rendering not compute pipeline), bind groups with resources, vertex attributes as inputs and render target outputs
  • JG: How do we know what they are?
  • MM: Discussed a few weeks ago, we’ll scan the entry points in the PSO and look through all the called functions. Anything mentioned in those is part f the interface
  • JG: Implied by static use?
    MM: Yes. Worth trying to see if it’s reasonable
  • JG: Easier to write then being explicit. Also easy to write spec text.
  • MM: Start with that and write some programs and see if things get accidentally included.
  • DM: SO they’d get a pipeline creation error?
  • MM: Yes.
  • DN: Formally in Vulkan the builtin variables are part of the interface. Something I’ll have to check. They don't have locations but do appear as input/outputs. Other apis abstract as builtins
  • DJ: What built ins?
    DN: Position, etc
  • MM: Things shader expects but that shader author doesn’t have to type, but the browser might have to create it. What does it mean by interface. Between WebGPU JS and Shader? If so those aren’t part of this interface
  • DN: By this I was thinking about the pipeline interface so you know frag and vert shaders are compatible. The set of things that get setup.
  • JG: Do users care/need to know? They don’t need to know for pipeline compatibility, right?
  • DN: I’ll have to check.Vulkan has a formal rule but webgpu may not have any features which make that rule needed. Maybe we don't have to deal with it.
  • DM: Can only think of gl_FragDepth and we don't have any depth in the pipeline. Probably very few cases where we need to consider builtins.
  • MM: So that case would be an error?
  • DN: Yes. We can categorize this as homework for me.

wgsl: Fix conditions on break from continuing (#837)

  • DJ: Continuing from last week, we now have a few examples in the issue.
  • JG: I think DN has a solution in terms of spec’ing vanilla break/else. The last thing on the list was the thought I had that DN wrote up that if the restriction on break is so hard to capture it may be easier to capture as a do/while or continuing/while which is effectively the restriction we have. I’m stuck between that being a cleaner solution but on the other hand is nicer if it’s just if/else/break and we have rules around usage
  • RM: Question for DN, what is hard with allowing something a bit less restrictive like allowing arbitrary code in the branch before the break. I can’t see anything that would cause trouble compiling to SPIR-V.
  • DN: What I’ve written directly translates to the OpBranchConditional where the break is on the true or false and the other branch is the backend on the loop. With more statements before the break, you could hoist that code before the conditional and guard on the same condition twice, translating that way can get confusing as it isn’t a direct mapping.
  • RM: Couldn't you generate normal code and branch once and just have the jump outside of the loop at the end of one of the two blocks.
  • JG: I think you can’t do that in continuing as it’s one block, not multiple blocks.
  • RM: From a continuing block you can’t jump to other stuff which isn’t the next …
  • DN: When you lay out the basic blocks, when you topological sort the break out has to post-dominate the entry into the continuing. If the break was in the then clause and you have an else clause you have to re-arrange the code so the end clause comes first and then the end clause. So, you’d have an unless statement with the then second. Yes, you could do it but it ends up being extra complexity that isn’t formally needed. The way I’ve written it is break if or break unless. If we had those constructs the rule would be the break if or break unless or break has to come at the end at the continuing and not nested. That simply satisfies post-dominance
  • RM: Would have to double check that condition and why it exists. I’m ok with the current rules, prefer if they are shown with if/break instead of a new construct. Especially if we can find a way to make it more flexible in the future. For MVP, adopting the rules as proposed works for me.
  • JG: I think there are no objections to the if/else/break phrasing we can take for the time being and iterate later if we have more thoughts.
  • DJ: Ok, let’s take that.

Requiring integral-valued float literals to have .0 seems gratuitous (#739)

  • MM: Made a proposal, which was probably too long, but the proposal is there would be generic and concrete literals. Every type that has a generic literal has a concrete literal. 3 is a generic literal which could be concrete float or int. 3i would be a concrete int. There are only 2 generic types, int-ish (3) and float-ish (3.0). The ones that look int-ish can be int or float types. So var x : f32 = 3;, the 3 gets turned into an f32. The other type of generic is the 3.0 which could be unified with float but not int types. So var i : i32 = 3.0 would be an error. The remaining piece is where unification would occur. THere are 3. 1. As an assignment or variable declaration. The direct RHS of the =, that type gets unified with the LHS. As a return statement. In a function returning f32 you could say return 3; and it would unify with the function return. The complicated one is an argument passed to a function call. So a function accepting float you could pass 3 and it would become f32. For overload resolution, the rule is if there is more than one option then it’s a compile error. The general philosophy is as little magic as possible. You should be able to write vec4<f32>(1, 2, 3, 4) and have it just work. What this would solve would be a balance between authors just typing literals in places it’s natural but not having to make compilers too complicated and having to look over expression trees to assign types.
  • DM: Really like the no magic plan. Concerned about things like 1 + 1 are more magic then we need. Would prefer if the type was immediately obvious from the surrounding context which 1+1 does not fit.
  • MM: That’s fine, we’d be happy to accept that change. I think the way you’d write that in the spec is + acts as a function call with many overloads (+ i32 i32) (+ f32 f32).If there are 2 overloads, and it’s ambiguous it’s a compile error which then forbids the 1+1 case.
  • DM: That makes sense.
  • JG: Would be sort of nicer to unify the float-ish and int-ish into a literal number type and then if you coerce a 1.5 into an int you get a warning. Maybe that’s trying too hard to be simple.
  • MM: and the behaviour is clamping?
    JG: Yeah, we have the inverse where if you write out u32::max and unify that into a float that’s not actually representable.
  • MM: that’s one of the decisions we talked about internally. What to do if the literal is too big. The solution we came up with is if you’re unifying that with an integral type then you do checking of the bounds and it’s a compile error. Your literal doesn’t fit into the type. If you’re unifying with a float-ish then conceptually floating points to many authors have infinite precision and range. So for that we probably don’t want to be a compile error. There are lots of numbers between two adjacent float numbers.
  • JG: Like 0.1 is not representable and we need to do the obvious thing there
  • MM: I think that makes sense but we’re flexible on the specific rule.
  • JG: If it’s not too complex I see the value in a float-ish that can’t be an int.
  • MM: In the proposal 3.5 is a float-ish that can not be an int.
  • DN: I agree that using a float-ish to assign to an int should be a compile error. I have some concern with counting the number of overloads as mattering. If we want to evolve the language we may add a new overload which invalidates previous valid programs. Similar the as cast is a wildcard overload. So the literal for an as cast, it's not clear what it should be as<f32>(1) is the 1 an int or float. Not clear if that works.
  • MM: If we followed the proposal works, this would have multiple valid overloads so a compile error. You’d have to say as<f32>(1i).
  • DN: So you still have an explicit type syntax
  • MM: Yea, all types that can be unified too would have a prefix or suffix so you can tell the compiler what it is. Int, unsigned, float, half, etc.
  • JG: Don’t like that direction. One of the things I want is something where you never have to use the variable type annotation except for in scope variables. You need it for functions and what not. The way to get that in other languages is casting a literal on the RHS and imply the type from that type’d cast on the LHS. This would make that a compiler error.
  • MM: Isn't a typed cast the same as using the suffix? Doesn’t look like a function but the same idea.
  • JG: I think that’s a worse solution because you must remember all of the suffixes instead of just using types and literals. To me the suffixes are convenient shorthand and not the canonical way to declare something.
    MM: I see. One of the general principles we’ve stated is to not have something look like a function that isn’t a function (as discussed with swizzle). If we wanted to do this we’d need some way that isn’t a suffix and doesn’t look like a function as I don’t think the thing you suggested would be a real function.
  • JG: To me that’s a strike against the functions philosophy. This would be worth carving out an exception to that.
  • MM: Can we put some special character in the function name as a compromise.
  • DM: Why is it not behaving like a regular function?
    JG: Because it takes a literal type
  • DM: Supposing you have generics and you have fn&lt;T>(value: T) -> T then it’s that function. Then the rules Myles suggested would treat it as T. Except we don’t have generic functions but that’s minor.
  • MM: I think that’s a good solution, makes a lot of sense to me.
  • DJ: Hearing not much disagreement then the next step is a PR which we can then discuss.
  • MM: Will make a PR and try to incorporate the feedback.

entry_point_decl should be an annotation on the function (#662)

  • MM: Hasn’t had any comments since the last discussion. To recap, the idea was to have the entry point annotation on the function but not limit the expressivity of the entry point. Was thinking there is possibly another compromise, if we can’t come to consensus about lets put everything on the entry point statement in the [] on the function, another way would be adding a validation rule saying that the entry point and function entry point are distinct statements but they have to be adjacent to each other. I’d prefer the [] but as a fall back …
  • DN: This may play a bit into the namespace discussion. I can imagine we made the language such that if I wrote a module (assuming separate compilation) I might want to declare the entry point at the top so someone just has to look in one place. Contextually it’s separate as one is declaring and one is defining the interface. Having them textually beside each other forces your hand. The other thing is that entry points in some apis are not first class functions, so they can not be called by other functions. This is (i believe true in vulkan but false in OpenCL). So, entry points are potentially different from regular functions.
    MM: Could we say that rather than having entry _point adjacent to the function point or we say they’re all at the top or they’re all adjacent to what they’re decorating?
  • DN: That seems fine to me. Is that busy work?
  • MM: The thing I'm trying to prevent is it getting lost in the middle of the file.
  • JG: Isn’t that down to code style?
  • MM: We changed the language to add decorators to the global variables where in SPIR-V they’re distinct statements. It isn’t just style as we did it in one place
  • JG: Different in how they’re used and not many usages. Don’t generally separate the decoration and the annotation. David's point is for functions you’d more commonly do that. It's more interface and implementation. There is not implementation for a global variable.
  • MM: The counter argument is every popular shading language in the world puts the annotations in the functions.
  • DM: Is it important to support multiple annotations on a single function? Seems like we could have a wrapper function if we need multiple annotations. Doesn’t seem like a huge burden. If you only have a single entry point not awkward as an annotation just awkward when you have multiple things which need a list annotation.
  • MM: Sounds great to me.
  • DM: Does anyone know if it’s often used?
  • DN: Often done as an implementation detail because the actual entry point needs to do pre-amble to get parameters in. Don’t know if that is common. Have seen examples where some did this to abstract interface details with a trampoline function which they expect to be inlined where the body is a single call.
  • MM: Little different for us as the interface isn't’ explicit. We can through all the functions, you can change workgroup size but can’t change what the function accepts.
  • GR: What is it you're asking, do people use multiple entry points.
  • DM: Was asking if people use the same function with different annotations like workgroup sizes.
  • JG: Like Myles' comment on April 14th. If there isn’t a tonne of overhead, we can just say don’t have multiple definitions, just use multiple functions and call the helper. As long as there is no subtle overhead we’re forcing folks into.
  • DN: Wouldn't worry about overhead as this would get folded away.
  • JG: Seems like the easier thing.
  • DN: At some point I listed the SPIR-V execution modes , there are a bunch but many are ignorable. I think that would manifest as the decorations you’d have and I could see other decorations being usable on any function. So, two classes of decorations: one for entry points and one for any function. As long as we separate them in the spec that would be fine.
  • MM: In the spec but not source code
  • DN: Yes
  • DM: Can you give an example of a regular function annotation and entry point
  • DN: Things like PURE (details confusing) if you’re reading/write memory or just acting on arguments. CONSTANTS so you at most read the global state. Neither of those apply to entry points.
  • MM: The compiler can’t rely on the author telling the truth there.
  • DN: I guess it has to re-derive it
  • MM: So no point in getting told as we have to figure it out.
  • JG: Useful to have the compiler tell you if you did that wrong.
  • DM: Seems like 2 disjoint sets of annotations. At least so far.
  • MM: Which is fine both inside [[]].
  • DN: Just checking SPIR-V INLINE and NO INLINE, both ignorable.
  • MM: Another thing we could do for having an export list at the top of a module is union the two sets. So the export list unioned with the annotations later in the file. Or the export list could have a different syntax like export every module with details x and the workgroup size is on the function.
  • JG: Nice to not have sometimes in two places. Especially if you have to fight over which has an annotation or no annotations. If you have a declaration it has all the attributes and the definition has none.
  • MM: Third option, all the entry point statements at the top and they can’t go with the functions they decorate.
  • JG: Have to be in … and not in definitions.
  • MM: also solves the problem one getting lost
  • DN: That’s how they are in SPIR-V. OpEntryPoint comes at the start and points to a function. Moving entry points to the top works for me.
  • DJ: Great to change in two places (sarcastically)
  • DN: But what are you changing?
  • JG: Changing args in header and source file again.
  • MM: How does that work, entry points don't’ have overloads
  • DS: You can’t have duplicate names. The function names are unique.
  • MM: You can for vector constructors.
  • DS: Only for them.
  • MM: I will raise an issue on that then. I want duplicate names.
  • DS: Currently all names should be unique (variables and functions).
  • DJ: Should record those options in the issue. No-one was yelling at any particular option and people seemed generally ok with most options.
  • MM: The thing I’d like most is what I commented on April 14th PDT.
    DJ: Let’s copy and paste that one and say it’s the final proposal and people can comment on that.
  • DN: As naming, don’t like ExposeAs as it appears to be external linkage and this isn't’ that. So a better name like entry
  • JG: Can you use a function before it’s declared?
  • DS: Not currently
  • MM: I think it’s required. If we want people to write shaders in many different files. I thought we were going to just ask authors to concat and that requires the order of the files to not matter as making the authors sort is hard.
  • JG: Don’t think it’s required but should talk about that in a different issue.
  • DS: Currently have to be defined/declared before use.
  • DN: Same with types, it has to appear before any issues. The tye in is that if a function uses a type the type must be the before it’s used. I don’t think it’s a burden to do the topological sort as it’s that way anyway.

Add spec text around vector accessors. (#852)

  • DJ: Already resolved.

No explicit grammar for decorations (#689)

  • JG: I rebased this one. Please take a look.
Clone this wiki locally