Skip to content

Conversation

@kvark
Copy link
Contributor

@kvark kvark commented Jan 8, 2021

See #1333 for some context. I don't think either tools or users gain anything from this validation rule. It doesn't need to be there.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 8, 2021

Previews, as seen at the time of posting this comment:
WebGPU | IDL
WGSL
f887779

@ben-clayton
Copy link
Contributor

What can you do with a shader with no entry point?

@kvark
Copy link
Contributor Author

kvark commented Jan 8, 2021

You can validate it :)
I don't think the tools need to do any extra work to support this.

@ben-clayton
Copy link
Contributor

I've spent the last 2 days converting glsl shaders to wgsl, and this validation has caught a whole bunch of cases where I forgot to annotate main with an entry point. I think it has value.

Why not just add a flag to the compiler to say dont-warn-if-no-entry-point, if validation of the rest of the code is what you want?

In terms of passing a WGSL shader to WebGPU, I really don't see a good reason why you'd not want the compiler to scream?

@kvark
Copy link
Contributor Author

kvark commented Jan 8, 2021

and this validation has caught a whole bunch of cases where I forgot to annotate main with an entry point. I think it has value.

I disagree. This has very little to do with a validation error in question. You may equally likely to forget to annotate another entry point in a module (with otherwise at least one annotated entry point), and nothing will guard you against that.

I'd argue that this missing annotation is not a problem at WGSL level. It's still a valid WGSL module if your entry point functions are not exposed as entry points. It's a shader interface problem, so it has to be validated wrt the pipeline (a shader is used in).

@ben-clayton
Copy link
Contributor

I'm also not even sure if all backend compilers support compiling a shader without an entry point.
Isn't an entrypoint required to compile an HLSL shader?

@kvark
Copy link
Contributor Author

kvark commented Jan 8, 2021

@ben-clayton nobody is producing HLSL or MSL at createShaderModule time.

@ben-clayton
Copy link
Contributor

Okay, so the picked entry point is late determined, but still there has to a an entry point for the shader compile to ever succeed, so I still think catching the case of no-entrypoints early in the pipeline still makes sense.

If we supported shader linking of multiple modules, then I'd certainly agree this would be undesirable, but as far as I'm aware, we don't.

@kvark
Copy link
Contributor Author

kvark commented Jan 8, 2021

@ben-clayton the general terminology we've been trying to use is:

  • createShaderModule is "compiling" the shader. This is where the validation rule of this PR applies. It doesn't consider pipeline interface in any way.
  • createXxxPipleine is "linking" the shader. This is where an entry point is required, obviously. Just because a module has at least one entry point provides no guarantee or protection against createXxxPipeline failing: it may expect a different name, a different shader stage, not to mention dozens of pipeline validation rules that may go wrong. So I don't see how requiring the 1 entry point in the module is making it better.

@dj2
Copy link
Member

dj2 commented Jan 12, 2021

I'm with Ben here, I'd prefer if we kept the validation. While the shader maybe invalid if you provided the wrong entry point, it's definitely invalid if you don't provide an entry point. So, determining here that the shader just cannot work seems like the right thing to do to me. We can validate later that the content of the shader is expected.

If we decide to have linkable shaders in the end, we can relax the rule without causing breaking backwards compatibility.

@kvark
Copy link
Contributor Author

kvark commented Jan 12, 2021

@dj2 entry points are a part of the shader interface, and should be validated as such. There is no need to try validating that ahead of time, it helps nobody at all.
What if the user generates the shaders programmatically, or using a define-like syntax, which ends up in some cases generating no entry points? That should be totally valid as long as no actual pipelines are created from such shader.

@dj2
Copy link
Member

dj2 commented Jan 12, 2021

But you can't do anything with the shader, so what's the point?

@kvark
Copy link
Contributor Author

kvark commented Jan 12, 2021

Would you want the following code be failing at runtime based on the user input?

struct ShaderOptions {
  blits: bool,
  copies: bool,
};
...
self.shader_options = ShaderOptions::from_user_input();
let code = compose_shader(&self.shader_options); // builds shader code based on what needs to be included
let shader = device.createShaderModule(code); // <----- important bit
let blit_pipeline = if self.shader_options.blits {
  Some(device.createComputePipeline(shader,  "cs_blit"))
} else { None };
let copy_pipeline = if self.shader_options.copies {
  Some(device.createComputePipeline(shader, "cs_copy"))
} else { None };

Allowing modules with no entry points makes us more generic from the developer point of view. It also doesn't cost us anything - in fact, it has negative cost!

@kvark
Copy link
Contributor Author

kvark commented Jan 12, 2021

Another example: imagine we have documentation about the shader code, perhaps even the very spec we are writing. Imagine that every piece of code there has a little "Play" button that copies it into another tab with some sort of a shader playground (like Rust docs do), where you can see how it gets transformed into native APIs, analyze the control flow uniformity, etc. Your pieces of code may be as short as:

var<private> x: i32;

And of course the playground would do the shader validation as the first step. So would we be forced to inject a dummy entry point in each of the snippets? Again, extra work that doesn't do any benefit.

Both of my points essentially converge to the fact that supporting no-entry-points is more tool-friendly (and it doesn't cost us anything).

@ben-clayton
Copy link
Contributor

#1340 (comment) - If you're forced to deal with a no-entry-point error at createShaderModule(), that seems reasonable? You'll need to add error handling here anyway for garbage input. If there are no entry points, then the if-statements below should be pointless to evaluate. Anything attempting to use shader below createShaderModule() with no entry points is going to be a fail, no?

Your argument about tooling is more compelling to me, but I think your tooling example extends beyond entry points.
There are spec examples my spec-checking tool cannot currently test because they are non-statements (in particular type declarations). These would also be interesting to translate (and ensure they're syntactically correct), despite being neither a statement nor expression on their own.
Even less complex than that, my spec-checking tool has to wrap free-statements with a dummy function.

If we want to support compiling WGSL without a full shader program for tooling purposes, then I think no-entry-points is just one of many things you'd have to solve.


FYI: Shader playground now supports WGSL. This was based on a PR from a few months ago, so the language has changed quite a bit in the meantime. I'll try and get it updated again once some of the larger language changes have landed.

@dj2
Copy link
Member

dj2 commented Jan 12, 2021

Would you want the following code be failing at runtime based on the user input?

struct ShaderOptions {
  blits: bool,
  copies: bool,
};
...
self.shader_options = ShaderOptions::from_user_input();
let code = compose_shader(&self.shader_options); // builds shader code based on what needs to be included
let shader = device.createShaderModule(code); // <----- important bit
let blit_pipeline = if code.blits {
  Some(device.createComputePipeline(shader,  "cs_blit"))
} else { None };
let copy_pipeline = if code.copies {
  Some(device.createComputePipeline(shader, "cs_copy"))
} else { None };

Allowing modules with no entry points makes us more generic from the developer point of view. It also doesn't cost us anything - in fact, it has negative cost!

I'm not sure I understand the example, that shader would have to have 2 entry points, so it would validate fine? It would fail at pipeline creation time if those entry points don't exist.

@kainino0x
Copy link
Contributor

I'm not sure I understand the example, that shader would have to have 2 entry points, so it would validate fine? It would fail at pipeline creation time if those entry points don't exist.

If blits and copies were both false, a shader module with no entry points would be created, and both pipeline creations would be skipped.

@kvark
Copy link
Contributor Author

kvark commented Jan 12, 2021

I just got stabbed in the back - SPIR-V doesn't like this either:

error: line 0: No OpEntryPoint instruction was found. This is only allowed if the Linkage capability is being used.

Et tu, Brute?

@dj2
Copy link
Member

dj2 commented Jan 12, 2021

Ah, I see. That makes sense to me as a good reason to drop the validation at that point.

@dj2
Copy link
Member

dj2 commented Jan 12, 2021

Right, you won't be able to generate the SPIR-V until the pipeline creation.

@kainino0x
Copy link
Contributor

Or you could just enable the linkage capability and do a no-op "link" step later which just strips it?

@kvark
Copy link
Contributor Author

kvark commented Jan 12, 2021

I suspect the SPIR-V rule may originate from the fact that both GLSL and HLSL producers of SPIR-V had the entry point guaranteed (i.e. it's explicitly specified for HLSL, and it's implicit for GLSL), and it was expected to only have one entry point. I don't think it's needed in SPIR-V either, but it's fairly easy to work around in the backend for us.

@dj2
Copy link
Member

dj2 commented Jan 13, 2021

Given that SPIR-V requires the entry point in shader module, I think we should keep this validation in place. The example posted can still be done, just conditionally create the shader module instead of unconditionally.

@kvark
Copy link
Contributor Author

kvark commented Jan 22, 2021

@dj2 going back to this main concern you had:

But you can't do anything with the shader, so what's the point?

We don't want to intercept it because we don't know what the user intention is. There actually was a very similar discussion in WebGPU on zero-sized buffers - #546. It was not without controversy, but the resolution is there: we want to allow them even if the user doesn't do anything useful with them. So there is a precedent here. Reason is the same as I stated - some user code may be structured in a way that may have or have not some stuff inside the shader module, and there is no need to make it a special case where nothing ends up in the shader module.

In terms of our target and input languages, the situation isn't clear, so we are free to take any direction:

  • SPIRV input - unaffected
  • SPIRV output - may need to either add a dummy entry point, or not crate a Vulkan shader module in the first place
  • Metal output - works totally fine with any number of entry points, including zero (kudos to Metal compiler team!)
  • HLSL output - doesn't care, strictly speaking. I wasn't able to find a standalone HLSL validator. it's only validated for a specific entry point, which we do at pipeline creation. So we might as well say that HLSL supports no entry points.
  • DXIL output - currently specced to have an entry point, but the tooling also requires it to be specified ahead of time, so we can't use the existing dxc to build a shader module anyway. With this in mind, we can say that DXIL path works totally fine with no entry points as well.

@dj2
Copy link
Member

dj2 commented Jan 25, 2021

For the SPIR-V output, injecting a fake entry point seems ... wrong? We're faking things to make a module which is invalid anyway. If we don't create a shader module, what does that mean? It fails to create, but then were does the error happen? In pipeline create, but how do we know why the shader module failed to create, we would have lost the error by this point.

It still feels like requiring an entry point is the right way to go. Your code example above can be refactored to only create the shader module under the same conditions as you create the pipeline, so we aren't removing the ability to do what you want to do.

@kainino0x
Copy link
Contributor

For the SPIR-V output, injecting a fake entry point seems ... wrong?

I don't think that weird workaround is needed. I think instead WGSL shaders could be defined to translate to SPIR-V-with-Linkage (even though we don't have a way to do linking on the API side (yet)). Is this reasonable?

@kvark
Copy link
Contributor Author

kvark commented Jan 26, 2021

Your code example above can be refactored to only create the shader module under the same conditions as you create the pipeline, so we aren't removing the ability to do what you want to do.

I didn't say we are removing any ability. I pointed to an example where the validation error is sneaking up on the unaware user. If it's an implementation detail that SPIR-V doesn't like it, we shouldn't force it on users. The workarounds are fairly simple. In return we get a slightly simpler spec, one less test, and better composability of the API.

Consider another case. The user has entry points, but all of them are invalid to use because they use WGSL features that are not enabled on the device. Say, f64 computations. So technically, at shader module creation you know they aren't going to be able to create any pipelines with it. Do you really want this to be an error at createShaderModule?

Keep in mind that in this case you can't generate a valid SPIR-V, since it requires at least one entry point, and you can't include the entry points that would make SPIR-V invalid on this device. So you'd basically have to do exactly the same kind of workarounds anyway: either add a dummy entry point, or not attempt to pass the SPIR-V to the device until the pipeline creation. You have to have this already.

@dj2
Copy link
Member

dj2 commented Jan 26, 2021

If you know you can't generate valid backend code, then yes, it should be a validation error, at least in my opinion.

@kvark
Copy link
Contributor Author

kvark commented Jan 26, 2021

If you know you can't generate valid backend code, then yes, it should be a validation error, at least in my opinion.

But you can't specify this at WGSL level! Because WGSL doesn't know which features are enabled on the device. Because the entry points are a part of the shader interface, they aren't self-contained in the shader module, and therefore their presence shouldn't be validated at createShaderModule.

So the rule under question here is useless: it doesn't prevent a shader module that has entry points, but which happen to be gated by the device features. If you want to produce an error in this case, anyway, we'd need to talk about a validation rule in the host WebGPU API, not in WGSL. So the rule in WGSL can be removed either way.

... And if you don't want this rule in the host API, then the implementations would need to implement the workarounds (suggested here) anyway, so there is no cost in removing the rule from WGSL.

@dj2
Copy link
Member

dj2 commented Jan 26, 2021

Sure, the generated SPIR-V may not work with your device capabilities and that should be validated at the pipeline creation time. The SPIR-V is still valid, it just isn't compatible with that pipeline combination. If the SPIR-V has no entry point it just isn't valid.

The rule isn't useless. The SPIR-V without entry points is invalid. SPIR-V with entry points is valid, it just may conflict with the pipeline creation.

@kvark
Copy link
Contributor Author

kvark commented Jan 26, 2021

That's true. However, for us, SPIR-V being valid by itself doesn't mean anything. SPIR-V is an internal representation of a shader in the Vulkan backend. There is nothing requiring the SPIR-V to be valid before it's passed to the vkCreateShaderModule. So for us, at least, the "spirv is valid" is only important in the context of - "is my vkCreateShaderModule call is valid with regards to Vulkan validation". And in this sense, with an actual device in the context, such SPIR-V (with gated entry poitns) is not valid.

Do you intent to call the SPIR-V validator on produced SPIR-V in Dawn, even if you are not going to pass it to vkCreateShaderModule? What would be the reason for doing this?

@dj2
Copy link
Member

dj2 commented Jan 26, 2021

Except, WGSL is being spec'd in terms of SPIR-V. So, generating invalid SPIR-V, to me, means you've got invalid WGSL.

@kvark
Copy link
Contributor Author

kvark commented Jan 26, 2021

@dj2 the specification is not an algorithm of converting WGSL to SPIR-V. It can define operations in terms of opcodes, since it helps clarity, but it doesn't spell out all the details of the conversion process. An entry point in WGSL is still an entry point in SPIR-V, there is no problem here. There are always implementation details on how exactly a SPIR-V is generated. And same applies to HLSL and MSL.

@dneto0
Copy link
Contributor

dneto0 commented Feb 2, 2021

A WGSL program without an entry point is useless.
It can't be used in a WebGPU pipeline.

@dneto0
Copy link
Contributor

dneto0 commented Feb 2, 2021

@ben-clayton nobody is producing HLSL or MSL at createShaderModule time.

Have I completely misunderstood #1064 then? The point is to allow doing more work up front, if the technology permits, and given more (browser) development investment.

@litherum
Copy link
Contributor

litherum commented Feb 2, 2021

@ben-clayton nobody is producing HLSL or MSL at createShaderModule time.

Have I completely misunderstood #1064 then? The point is to allow doing more work up front, if the technology permits, and given more (browser) development investment.

It is a P0 for us to be able to generate MSL at createShaderModule() time. (Hence #1064)

@litherum
Copy link
Contributor

litherum commented Feb 2, 2021

I don't understand why this is hard. If a WGSL program doesn't have any entry points, when a compiler wants to compile it to SPIR-V, just ... don't generate any SPIR-V.

@litherum
Copy link
Contributor

litherum commented Feb 2, 2021

Related: #1404

@kvark
Copy link
Contributor Author

kvark commented Feb 2, 2021

@ben-clayton nobody is producing HLSL or MSL at createShaderModule time.

Have I completely misunderstood #1064 then? The point is to allow doing more work up front, if the technology permits, and given more (browser) development investment.

My comment was about the actual state of things today. We aren't generating MSL or HLSL at this moment, Google doesn't, and there are no immediate plans to do so. The discussion in #1064 didn't come to a consensus on how to get there. Besides, this isn't a problem for MSL - it's totally fine to build a library with no entry points.

@litherum
Copy link
Contributor

litherum commented Feb 3, 2021

there are no immediate plans to do so.

We have immediate plans to do so 😎

@dj2
Copy link
Member

dj2 commented Feb 3, 2021

Not sure what you mean, we generate both MSL and HLSL in Tint. It is not enabled by default in Chrome yet, but we're working on enabling it in the near future.

@kainino0x
Copy link
Contributor

I think Dzmitry meant "inside createShaderModule"

@kdashg
Copy link
Contributor

kdashg commented Feb 4, 2021

WGSL meeting minutes 2021-02-02
  • DN: Surprised at this, to have a useful WGSL program you need an entry point. So, no point to not have it.
  • DM: Incomplete rule that doesn’t help anyone
  • MM: We expect JS preprocessors to mangle the strings and if they want to call the function on a string without entry point they should be able to do it
  • DN: No semantics for what it means
  • MM: You can create a shader module but not pipeline
  • KN: Exactly
  • JG: Fail at creating a shader module or pipeline. Similar to if you have something and don’t use it should we forbid it. Sometimes applications end up with 0 lights and don’t want to special case that. Generally expect the rest to work and they don’t ever use it, and as long as they dont’ use it it’s fine even though there are no entry points, So similar to if we allow 0 sized buffers
  • MM: You can have a var and never use it. You can have a func and never use it. If the compiler sees there are no entry points and early outs, that’s fine.
  • DN: Maybe ties into 1241 which is to spell out when you reject a module. Maybe distinction if you can reject at pipeline creation or shader module time.
  • JG: Hoping we could close this out before doing that discussion.
  • DN: Could temper perspective if we were more clear on that. Have in mind there are 2 places we can reject. An f64 rejects at shader module creation as we don’t have that yet.
  • DM: Suppose we add f64 to the language, and .. (missed) … and the device does not have the feature enabled, when do we reject?
  • MM: Alternative approach, extensions are part of the create shader module input and if you want to compile and f64 shader you have to pass that in
  • KN: f64 tied to a device so passed in.
  • DM: But then you can’t use that device as it’s a WGSL rule. This is the interface and the interface should not be defined in this document.
  • KN: If f64 can’t be used on the device then create shader module fails
  • DM: Not what I thought. Number of ways a shader could be used and only use entry points for ways that it’s used.

@dj2 dj2 added the wgsl WebGPU Shading Language Issues label Feb 17, 2021
@dj2 dj2 added this to the MVP milestone Feb 17, 2021
@kdashg
Copy link
Contributor

kdashg commented Mar 5, 2021

WGSL meeting minutes 2021-02-16
  • JG: Having trouble getting consensus but maybe able to push out past MVP and focus on other issues, sound find?
  • MM: Try for consensus?
  • MM: Think DM has made a fairly compelling case why it’s valuable. Haven’t heard a compelling case about why to not allow this.
  • DN: What I’ve come to understand is disconnect on when we should be able to gen lower level code from WGSL. ShaderModule or PipelineCreation. If we should put features in the language which require waiting until pipeline creation. Against that, want features to align so we can validate as soon as possible (createShaderModule). Fundamental disconnect. Comes up for extensions being per entry point or per module. Resolving that may point this in the right direction
  • MM: By “generate as much code as possible”, are you talking about SPIR-V or all browsers on all OSs?
  • DN: Should cover as much as can be exposed. Some won’t be as easy as others, HLSL needs deferment, DXIL maybe able to do that easier. Would be fine to have that for HLSL if DXIL can generate it earlier.
  • JG: For this request, could a potential implementation assume we could generate SPIR-v at shaderModuleCreate, we could generate all the SPIR-V needed at that time, but if you don’t have any entry points the SPIR-V for the pipeline is the empty string. So, you’re generating at shader module creation, but impossible to use.
  • GR: Seems like motivation is to validate the shader, but a lot of authors pack a number of shaders into a single src file, the entry point determines which one you’re running. Can’t really be validated because there are things that aren’t valid for a given shader stage. How you’d remove dead code, etc.
  • MM: Expect validation of shader stage specific code to state the rules as if a barrier is reachable from a compute entry point then that is a compile error. If there are no entry points then you can put barriers where you want.
  • GR: So, the compiler has to trace all the call stacks to determine if any end in compute shader
  • JG: Maybe, but no entry points no trace
  • GR: But apart from that.
  • DM: Yes.
  • JG: One of the objections is that you can’t use the shader without entry points. If the issue is you can’t create the shader module as there is no valid SPIR_V. Then the SPIR-V is just no SPIR-V. That’s all that’s needed. Potential way to implement this if you wanted.
  • DN: I can’t use a module that has no entry points. THere is no linkage module, no way to glue wgsl srcs after given to implementation. We can make a pipeline from the stage, but that must have had a shader in it. Don’t want to spec for code that i’m going to dead code eliminate.
  • MM: Dead code still has to type check.
  • DM: You could have one entry point with lots of issues, but won't’ reach them from that entry point
  • DN: I can’t use the thing you want to validate, what’s the point
  • GR: Can’t validate a shader without an entry point
  • JG: You can fully validate. There are 2 phases for validation, 1 type checking, 2 entry point validation. Only things used in frag entry point work in fragment. You always do the first, the second for entry point behaviours loop over the entry points. Exit immediately as you’ve done them all and all (0) entry points validated successfully. Question is if the user wants no entry should we allow the shader module for it even though it can’t be used in a pipeline.
  • GR: Are we introducing a pitiful that users can fall into given the intended use. Don’t see it that useful, it’s probably harmless, until we get complaints that the pipeline won't create.
  • MM: Thought experiment, consider a function that is never called but has a type error?
  • GR: You can validate that with an entry point? Not really validation, if that falls under the definition that’s fine
  • JG: Folks want to talk about whole shader module validation. If they want that as a prerequisite then that’s OK.
  • DM: Agree, what’s missing is the use cases. When would the user want no entry points. Not when they write the shader, but when they have multiple entry points but filtered them out. Preprocessor or something that filtered out. If they’re filtering out the entry points then filtering out the pipeline creation. But forcing them to do the extra checks if entry points factored out then redundant
  • AB: Just sounds like a different error place. Why not just get a null back from createShader?
  • JG: Difference is functional. I generated all shaders for this module and if i need them i’ll create the pipeline, but never create the pipeline so never use the shader module. If you mark that as illegal then you have to move back up and decide how to handle the missing shader module. Create a fake shader module in SPIR-V.
  • MM: Like a zero input/output compute shader that does nothing?
    JG: Or system with different entry points per lights and use 0 lights, so create no entry points. Does this shader module fail to create if it never uses it?
  • MM: Interested in making the shader module return null? That maybe OK, could be a middle ground. You’re allowed to have this function return null, but not expose an error.
  • JG: Would match SPIR-V.
  • DN: Imagine that is the billion dollar webgpu bug. Expect that to trip folks up. Making a choice on what’s more likely to trip up on, and will we guide them the right way of an error vs un-usable object.

@kvark
Copy link
Contributor Author

kvark commented Mar 29, 2021

Discussed on WGSL 29-03-2021 meeting, agreed to proceed.

@kvark kvark requested a review from dneto0 March 29, 2021 19:59
@github-actions
Copy link
Contributor

Previews, as seen at the time of posting this comment:
WebGPU | IDL
WGSL
6b08f87

@dneto0 dneto0 merged commit b6dc833 into main Mar 29, 2021
@kvark kvark deleted the kvark-ep branch March 29, 2021 20:54
@kdashg
Copy link
Contributor

kdashg commented Mar 29, 2021

WGSL meeting minutes 2021-03-29
  • DN: For the cases that you might not have an entry point, can always insert a dummy you’ll never call.
  • MM: Can lead to cargo-culting, where it’s always added even when not needed. which would be unfortunate.
  • DN: Didn’t think there would be such strong feelings about it. Thought this was an opportunity to help the user when they forget the “stage” attribute. If createShaderModule is given a module with no entry point, can always save a dummy empty module.
  • JG: Give a warning if there’s no stage attribute.
  • Consensus to remove the requirement.

ben-clayton pushed a commit to ben-clayton/gpuweb that referenced this pull request Sep 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

wgsl WebGPU Shading Language Issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants