-
-
Notifications
You must be signed in to change notification settings - Fork 21
feat: Expected type stack in WgslGenerator #1532
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
feat: Expected type stack in WgslGenerator #1532
Conversation
Co-authored-by: Iwo Plaza <[email protected]>
Co-authored-by: Konrad Reczko <[email protected]>
Co-authored-by: Iwo Plaza <[email protected]>
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.
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 spectacular, makes things so much more robust now 👏👏👏
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.
Amazing work! 🙇
if (!isMarkedInternal(callee.value)) { | ||
throw new Error( | ||
`Function ${String(id.value)} ${ | ||
getName(id.value) | ||
`Function ${String(callee.value)} ${ | ||
getName(callee.value) | ||
} has not been created using TypeGPU APIs. Did you mean to wrap the function with tgpu.fn(args, return)(...) ?`, | ||
); | ||
} | ||
|
||
const argTypes = id.value[$internal]?.argTypes as | ||
| FnArgsConversionHint | ||
| undefined; | ||
let convertedResources: Snippet[]; | ||
try { | ||
if (!argTypes || argTypes === 'keep') { | ||
convertedResources = resolvedSnippets; | ||
} else if (argTypes === 'coerce') { | ||
convertedResources = convertToCommonType(ctx, resolvedSnippets) ?? | ||
resolvedSnippets; | ||
} else { | ||
const pairs = | ||
(Array.isArray(argTypes) ? argTypes : (argTypes(...resolvedSnippets))) | ||
.map((type, i) => [type, resolvedSnippets[i] as Snippet] as const); | ||
|
||
convertedResources = pairs.map(([type, sn]) => { | ||
if (sn.dataType.type === 'unknown') { | ||
console.warn( | ||
`Internal error: unknown type when generating expression: ${expression}`, | ||
); | ||
return sn; | ||
} | ||
// Other, including tgsl functions, std and vector/matrix schema calls. | ||
|
||
const conv = convertToCommonType(ctx, [sn], [type])?.[0]; | ||
if (!conv) { | ||
throw new ResolutionError( | ||
`Cannot convert argument of type '${sn.dataType.type}' to '${type.type}' for function ${ | ||
getName(id.value) | ||
}`, | ||
[{ | ||
function: id.value, | ||
callStack: ctx.callStack, | ||
error: | ||
`Cannot convert argument of type '${sn.dataType.type}' to '${type.type}'`, | ||
toString: () => getName(id.value), | ||
}], | ||
const argConversionHint = callee.value[$internal] | ||
?.argConversionHint as FnArgsConversionHint ?? 'keep'; |
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.
Maybe we create something like:
export function isDualImpl<T extends (...args: unknown[]) => unknown>(
value: unknown | TgpuDualFn<T>,
): value is TgpuDualFn<T> {
const internal = (value as TgpuDualFn<T>)?.[$internal];
return !!(value as TgpuDualFn<T>)?.[$internal] &&
'jsImpl' in internal &&
'gpuImpl' in internal &&
'argConversionHint' in internal;
}
This sound useful and would clean up our types here (and potentially in other places - I didn't check)
if (!isDualImpl(callee.value)) {
throw new Error(
`Function ${String(callee.value)} ${
getName(callee.value)
} has not been created using TypeGPU APIs. Did you mean to wrap the function with tgpu.fn(args, return)(...) ?`,
);
}
// Other, including tgsl functions, std and vector/matrix schema calls.
- const argConversionHint = callee.value[$internal]
- ?.argConversionHint as FnArgsConversionHint ?? 'keep';
+ const argConversionHint = callee.value[$internal].argConversionHint;
...
- const fnRes =
- (callee.value as unknown as (...args: unknown[]) => unknown)(
- ...convertedArguments,
- ) as Snippet;
+ const fnRes = (callee.value)(
+ ...convertedArguments,
+ ) as Snippet;
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.
Dual impls are not the only callables (e.g., tgpu.fn), so this was breaking a lot of tests. I guess we'd have to check for all callables, but we can do that in another pull request.
Closes #1510, #1508.
Changes:
expectedTypeStack
that lets us infer the struct types of JS objects and array types of JS arrays,generateTypedExpression
function, that pushes to theexpectedTypeStack
, callsgenerateExpression
, pops fromexpectedTypeStack
and tries to coerce the result to the correct type,callStack
,Out
struct by default,argTypes
toargConversionHint
, renamed'coerce'
to'convert-arguments-to-common-type'
,Expressions that use
generateTypedExpression
:Expressions that do not:
I did not change how JS variables defined outside of TGSL are parsed, I think it can be separated to another ticket. Same goes for optimizing number casting.
I am not convinced about the current implementation of the
expectedTypeStack
:generateTypedExpression
swap it, since the stack is exclusively modified in this function.when generating the code for
(1+2)
, the stack is empty, and the existence of the stack might mislead developers to think that it would holdu32
in this case. If we allowed unknown type and multiple types to be on the stack, we would be able to update this stack at all times. It would require many more changes, especially to std functions and vec/mat constructors that currently have noargTypes
(as most of them cannot be given a single signature, they have overloads).Let me know what direction would you like this taken to.