-
Notifications
You must be signed in to change notification settings - Fork 3
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
5 WESL Proposals #20
base: main
Are you sure you want to change the base?
5 WESL Proposals #20
Conversation
|
||
This scheme means that if a given symbol has been included into the global scope, it is not mangled at all. It has the absolute minimum number of parts in the path! | ||
|
||
An example of how to apply the algorithm described above, would be the name used in mangling the `quat_from_euler` function. The name chosen to be mangled would be `Math::Float::quat_from_euler`, rather than `Math::FloatMath::quat_from_euler`. This is because while they have the same number of parts in their paths, `Math::Float::quat_from_euler` has fewer number of characters. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand this correctly, this means that the host code (Rust or Typescript) doesn't know what the mangled name is until after the modules have been compiled. Is that correct?
With conditional compilation, the mangled name could also unexpectedly change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm. Interesting point. My motivation was originally to reduce the size of the code using this approach and resolve to the most public facing name. Most modules outside of the global scope would contain implementation details and not be for public use anyway.
However bindings are a bit of an exception as users may still want to be able to introspect on them and to also generate binding code.
So a dilemma!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding "generate binding code", would it be reasonable to say that that is part of what a good linker should offer? Or rather, a good linker should offer some language specific APIs for it.
As in, we aren't expecting people to generate perfect bindings from generated code. Generated code could, for example, no longer have comments. Good bindings code would include documentation comments.
So for the generated output, we only need something that is predictable and usable. (compared to raw WGSL)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. Do you have a suggestion for achieving this? Would prefer if we didn't have to special case the output names of entry points (which was another reason for choosing this scheme) though understand it might be necessary.
} | ||
|
||
// Abstract representation of a binary operation. | ||
mod sig BinaryOp<OpElem: Type, LoadElem: Type> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think generics w/o type bounds are a useful intermediate step?
I'm not certain, but I'd been hoping so to make things easier.
(w/o type constraints generics would be more about getting the flexibility to replace the string templating that current wgsl users do. But that weaker form wouldn't add type safety, so some errors would be caught later perhaps by the dawn/wgpu parser)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be but would entail a breaking change once support for constraints were added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why necessarily a breaking change? I'd think simple substitutions e.g. f32 for LoadElem would be sound even when better typechecking comes in later. Inference would reduce boilerplate, but not render the boilerplate unsound.. hmm.. I bet there's a case I'm missing, can you explain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mighdoll The problem is that this design does not allow you to access any members of the module were the generic constraint omitted (which is why it's not permitted to omit the constraint in the grammar in the first place). So if you added typechecking after the fact, you'd get a lot of errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because this design for simplicity does not use type inference. Only checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me restate to see if I understand:
. if we were to release an edition with a generics-light (no contraints, no typechecking, no inference)
. it's perhaps possible to add typechecking in a later edition in principle, but
. if the next stage is generics-checked (constraints, typechecking, no inference)
. generics-checked will want to require explicit constraints for every type parameter (because it doesn't have inference)
. so best not to allow unconstrained type parameters, lest generics-light code become incompatible with generics-checked
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned on discord, there is one way to preserve compatibility in this design which is to add the concept of an "any" module type, which much like in typescript would mean that module arguments that do not specify type constraints would be unchecked
// Tells the linker to resolve calls to `resolve_pbr_inputs` | ||
// to this function instead of the base. This is constrained to the | ||
// current namespace; in this case the namespace is the global scope. | ||
patch fn resolve_pbr_inputs(pbr_inputs: &PbrInputs) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Patch seems powerful! too powerful?
Are patch targets constrained somehow? (I'm thinking about the library case, where a library author wouldn't want messy internals to become public api because a user can patch them.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The visibility mechanism in this proposal would be the only way to constrain patch targets which is quite limiting I agree. An alternative idea would be something like a "virtual" keyword.
|
||
Currently all symbols in wgsl share a single global namespace with a prohibition against symbols with the same name. Many existing implementations of imports (not unlike [our proposal](./Imports.md)) simply add to this global scope. For relatively self contained projects this is not a problem, however when considering a broader ecosystem of packages containing reusable shaders, collisions become much more likely. | ||
|
||
Additionally WGSL provides very little way to encapsulate and organise code. This proposal would pave the way for both |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a keen observation. there's just no current way to describe a group of elements in wgsl right now!
Currently all symbols in wgsl share a single global namespace with a prohibition against symbols with the same name. Many existing implementations of imports (not unlike [our proposal](./Imports.md)) simply add to this global scope. For relatively self contained projects this is not a problem, however when considering a broader ecosystem of packages containing reusable shaders, collisions become much more likely. | ||
|
||
Additionally WGSL provides very little way to encapsulate and organise code. This proposal would pave the way for both | ||
[Module Interfaces](./ModulesInterfaces.md) and [Generic Modules](./GenericModules.md). The former would allow a graphics programmer to optionally control the visibility and typecheck the symbols within a module, while the latter would allow for more reusable code and the ability to build powerful abstractions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's impressive how you show the mod scaffolding can be stretched to cover so many features.
What's the +/- for building those features as 'mod' extensions vs building them separately?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While hard to list the pros and cons without having something concrete to compare it against, there are a few things I'd call out.
On the plus side, the contents of modules are valid wgsl; there are no additional constructs to transpile other than mangling the symbols within a module.
On the minus is that sometimes it may be more verbose than e.g. generic functions — this could be solved by syntactic sugar that coerces functions and constants into modules.
Another minus is that this style of programming may be unfamiliar to many graphics programmers. Luckily this is somewhat ameliorated by having few additional concepts in the WESL language to learn other than modules.
This is a really extensive proposal! It'd take us quite a while to work through all of it, figure out any potential issues, improve parts, etc. Can you see a way to split it up into smaller pieces? |
For bevy PBR stuff, think import, modules and include would be enough. Typechecking/module signatures could probably be separate. And generics could be a dependency on one or both. |
Oh! I would have guessed patch (+ import of course). Can you explain in a toy example? (I guessed that the key PBR problem was enabling a library to provide a large set of extension points in a deep chain of library functions, and that patch lets the library user replace any one of those functions, efficiently turning every function into an extension point.) |
@mighdoll you're right patch needs to be in that too. |
This draft PR includes several proposals that build upon one another.
In order these are:
Import
- Simplification of importsModules
- Encapsulation and namespacing of elementsModule Signatures
- Visibility & TypecheckingInclude
- Inheritance/ExtensibilityModule Generics
- Abstraction & Reusability