Inline Functions Proposal#147
Conversation
| ### Parameter and Return Types | ||
|
|
||
| Inline functions lift some limitations on allowed parameters and return types. In WGSL, a function return type must be constructible. The return type of an inline function can be a constructible type, a texture, sampler, or pointer type. An inline function may also return a parameter or a module-scope declaration, including bindings. Returning parameters or module-scope declarations does not perform a copy, instead they must are “returned by reference” and can only be bound to a variable. | ||
|
|
There was a problem hiding this comment.
can the inline function call be a parameter to a function call?
There was a problem hiding this comment.
yes. There is one limitation though, that I didn't write down: order of evaluation.
let x = foo(sideEffect(), inlineFunc());If inlineFunc has statements, they will be inlined before the x declaration statement, so before the sideEffect.
{ /* inline inlineFunc's side-effects */ }
let x = foo(sideEffect(), /* inlineFunc's return */);Ofc a more complex codegen can circumvent this:
let arg1 = sideEffect();
{ /* inline inlineFunc's side-effects */ }
let arg2 = /* inlineFunc's return */;
let x = foo(arg1, arg2);|
|
||
| However, inline functions are hygienic, meaning that they can only access declarations in scope. In-scope declarations are module-scope declarations and function parameters. | ||
|
|
||
| (TODO) should we allow discard statements? diagnostics? what should be forbidden in inline functions? |
There was a problem hiding this comment.
disallowing discard and const_assert and diagnostics sounds like a good place to start..
There was a problem hiding this comment.
If we only allow for them to be called from other functions, could we scope the diagnosics by inserting a compound statement? { }
What happens when one calls discard in a normal function?
Also, wesl implementations will have to check for break and break ifs. Otherwise I could sneak illegal code past a WESL compiler.
@inline
fn b() { break; } // very illegal
fn main() {
while 1 == 1 {
b();
}
}|
A thought occurred to me... |
| The scope, address space and access mode of the declaration is inferred from the inline function return value. (TODO: should we allow explicit? in particular the access mode, if applicable.) | ||
| Non-constructible types cannot be bound to value declarations. | ||
|
|
||
| NOTE: In inline functions returning non-constructible types, the code path leading to the return statement cannot contain branches, so the returned value is determined at shader-creation time. But the function can contain runtime side-effects alongside. |
There was a problem hiding this comment.
Do we really need this restriction?
I thought I could write stuff like
var a: ptr<...> = &foo;
if something {
a = &bar;
}
It was just problematic for textures because of nom-uniform control flow leading to missing partial derivatives when sampling.
There was a problem hiding this comment.
Your code would be valid and I misphrased the sentence. It should have been
NOTE: Since there can be only one return statement AND non-constructible types are not assignable, the returned value cannot be depend on a runtime condition, so it can be determined at shader-creation time.
|
@ncthbrt That's a nifty idea. |
|
Another idea I had related to this is yield blocks... What if you could |
| // … | ||
| textureSample(myTexture, sampler_a, myCoords); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
What would the transformation look like in more complex cases?
I'm guessing this
@inline
fn selectSampler() -> sampler { return sampler_a; }
fn main() {
// …
var mySampler = selectSampler();
textureSample(myTexture, mySampler, myCoords);
textureSample(myOtherTexture, mySampler, myCoords);
}needs to be transformed into
fn main() {
textureSample(myTexture, sampler_a, myCoords);
textureSample(myOtherTexture, sampler_a, myCoords);
}because we cannot store a sampler in a local variable. See also gpuweb/gpuweb#2482
With that in mind, what data type does mySampler have?
There was a problem hiding this comment.
would it not have the type of sampler_a? e.g. sampler
I'd guess we should disallow a var. But let's say for a let.
Are you saying then we should also allow sampler after the colon in a let statement inside a fn?
let mySampler: sampler = selectSampler();There was a problem hiding this comment.
I'm asking, because that let mySampler variable needs to be removed during lowering. WGSL currently does not allow let mySampler = someSampler.
|
I suspect that this Case in point, the following code var<storage, read_write> counter: atomic<u32>;
@inline
fn next_random() -> u32 {
return atomicAdd(&counter, 1);
}
fn do_something(value: u32) { ... }
fn main() {
let a = next_random();
do_something(a);
do_something(a);
do_something(a);
}would turn into the following, given a naive desugaring implementation var<storage, read_write> counter: atomic<u32>;
fn do_something(value: u32) { ... }
fn main() {
do_something(atomicAdd(&counter, 1));
do_something(atomicAdd(&counter, 1));
do_something(atomicAdd(&counter, 1));
}But why? var<storage, read_write> counter: atomic<u32>;
@inline
fn get_texture_with_side_effect() -> texture_2d<f32> {
return binding_texture_array[atomicAdd(&counter, 1)];
}
fn do_something(value: texture_2d<f32>) { ... }
fn main() {
let a = get_texture_with_side_effect();
do_something(a); // increments
do_something(a); // the counter
do_something(a); // 3 times
} |
|
@stefnotch : the proposed codegen does not have this issue. It would fall into case 3.b. example 1 does NOT return an ident referring to param or module scope decl, so it generates example 2.. is invalid code per the proposalq, because the proposal does not properly take naga binding_array into consideration. To be fixed! |
|
Good to hear that you already thought about this 👍 |
|
I didn't properly think of this, It just happens to be a happy coincidence. Thanks for finding the cracks! |
|
Turns out that inlining is one of the only solutions for returning a texture https://matrix.to/#/!MFogdGJfnZLrDmgkBN:matrix.org/$4VgdOHPxPrkV7qWFkzyDLPvvgReaDzYNCCRGt-DiUzg?via=matrix.org&via=mozilla.org&via=beeper.com That's good to know :) I suspect that there's another solution that uses the type system. It'd be somewhat similar to what we'd use for lambda functions or function pointers. Basically // This creates a new, anonymous type for the lambda function
let callback = () => { return 3; };
// This fails type checking, because the two lambdas have different types
let other_callback = select(true, callback, () => 2);
// This uses a type specifically for this texture
let t = my_texture;
// Fails to type check
let other_t = select(true, t, other_texture);
// Coerces to `foo(my_texture)
foo(t);
fn foo(a: texture_2d<f32>) {
...
}
// We cannot return a texture, but we can return one of those "types specific to a texture"
fn bar() -> impl texture_2d<f32> {
if(true) {
return my_texture;
} else {
// Type error, all return types must match
return other_texture;
}
}Want to return two different textures/lambdas and select one at runtime? |
|
In our last meeting, @stefnotch noted that our goal of writing functions that return non-constructable types is separable from function inlining. We want a way to write a restricted function that can be statically analyzed to return a non-contructable type like a texture. In transpilation, we'll replace the references to the returned texture with references to the global texture, as described in the PR. The references to the non-constructable type will be inlined. But it's not actually necessary to inline the rest of the function, we just need to rewrite the access to the texture. The side-effecting remainder of the function could be rewritten as a function with no return value. There's no need to duplicate the side-effecting remainder in every caller as An alternate syntax could focus the feature without specifying inlining of the entire function. Rather than: @inline fn selectSampler() -> sampler { ... }we could have: fn selectSampler() -> @inline sampler { ... }(or some other keyword) |
Again, rough draft and see TODOs for comments.