-
-
Notifications
You must be signed in to change notification settings - Fork 35
feat: Ref/Value behavior tracking #1755
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
Open
iwoplaza
wants to merge
70
commits into
main
Choose a base branch
from
feat/ref-value
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+4,081
−2,136
Open
Changes from all commits
Commits
Show all changes
70 commits
Select commit
Hold shift + click to select a range
e5068dd
Tracking referentiality
iwoplaza c5189be
More progress on the implementation
iwoplaza eb8f520
Mostly works now
iwoplaza 5312d1d
Track ref address space
iwoplaza 52124a7
Enforcing copy when assigning
iwoplaza 07ed411
JS const with ref value is WGSL let with pointer
iwoplaza ba2a4ff
Fix ptr return types, and invalid ref and deref op order
iwoplaza 7b7e5dc
Const statements
iwoplaza 9a75b1e
Not allowing references to be returned from a function (unless it's a
iwoplaza 5dc3884
Move member access exceptions to `accessProp` so that it's shared with
iwoplaza 755d13b
Better indexing
iwoplaza ec2657f
Indexing arrays at comptime
iwoplaza 8497a35
Constant tracking
iwoplaza cc349ff
Infix
iwoplaza 77ac0cc
Apply formatting
iwoplaza 04fa183
Merge branch 'main' into feat/ref-value
iwoplaza aee85a6
A few tweaks
iwoplaza 5173502
Update wgslGenerator.ts
iwoplaza c3bf598
Fixes
iwoplaza 40b3f7a
Self review
iwoplaza 7c5900b
Update accessor.ts
iwoplaza 50741ce
More tweaks
iwoplaza 4dff99e
Merge branch 'main' into feat/ref-value
iwoplaza 51c2919
Apply suggestion from @aleksanderkatan
iwoplaza b035d45
Apply suggestion from @aleksanderkatan
iwoplaza 3932b2f
Update stable-fluid.test.ts
iwoplaza 62936f8
Simplify 3D Fish compute
iwoplaza 14c7282
Update compute.ts
iwoplaza e90832d
Review fixes
iwoplaza 549bc71
Merge branch 'main' into feat/ref-value
iwoplaza e3b3bba
Merge branch 'main' into feat/ref-value
iwoplaza 1e2df56
Updates after changing 'kernel' to 'use gpu'
iwoplaza bbc0508
feat: Better constant handling for ref/value tracking (#1801)
iwoplaza 1f8392e
Merge branch 'main' into feat/ref-value
iwoplaza 3824a3a
Update snapshots
iwoplaza b4f2c0b
Rename ref to origin
iwoplaza beb2295
Explicit refs
iwoplaza 808deef
Merge branch 'main' into feat/ref-value
iwoplaza 109dbe9
Implicit function pointers don't cause shell-less functions to generate
iwoplaza 0fdadf5
Using std.neg when resolving unary `-` operator, and emitting `let` when
iwoplaza 04fb153
Fix Disco example
iwoplaza 2a6527e
🦕
iwoplaza 59f2618
Updating gravity example
iwoplaza f85150b
More updates
iwoplaza 05270d0
Update gravity.test.ts
iwoplaza 66b89cf
Fixed!
iwoplaza 2ed5955
Update Gravity code
iwoplaza 4ccdd96
Working on umiform refs
iwoplaza dbcd394
Merge branch 'main' into feat/ref-value
iwoplaza 07ee6b7
Writing internal docs about shader generation
iwoplaza 79252cb
More useful refs
iwoplaza c853f5c
Simplify and document
iwoplaza 21776b3
Test for updating a whole struct, returning refs
iwoplaza b1fe352
Updates
iwoplaza 5805cac
Simplify implicit pointer dereferencing
iwoplaza 37914d5
Merge branch 'main' into feat/ref-value
iwoplaza 685479d
More tests and restrictions
iwoplaza 171af79
More test coverage for argument origin tracking
iwoplaza c6b0537
Update shader-generation.mdx
iwoplaza b3e979e
Update shader-generation.mdx
iwoplaza e0b5cc7
🦕
iwoplaza 39b5704
Merge branch 'main' into feat/ref-value
iwoplaza c31bb13
Better handling of arguments
iwoplaza 6003bc3
Update pointers.ts
iwoplaza 74d1291
Fix for referencing implicit pointers
iwoplaza 756384d
Cleanup 🧹
iwoplaza 38e37bc
Review fixes
iwoplaza df869a0
Review fixes
iwoplaza 8e6c93c
Better handling of arguments
iwoplaza ec4fc64
🦕
iwoplaza File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
205 changes: 205 additions & 0 deletions
205
apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| --- | ||
| title: Shader Generation | ||
| draft: true | ||
| --- | ||
|
|
||
| TypeGPU houses a very powerful shader generator, capable of generating efficient WGSL code that closely matches the input | ||
| JavaScript code. | ||
|
|
||
| ## The phases of code generation | ||
|
|
||
| The whole end-to-end process of turning JS into WGSL can be split into two phases: | ||
| - Parse the JS code into an AST. | ||
| - Collapse each AST node into a [snippet](#snippets) depth first, gradually building up the final WGSL code. | ||
|
|
||
| We found that we don't always have enough information to do both phases as a build step (before the code reaches the browser). | ||
| For example, the type of a struct could only be known at runtime, or could be imported from another file, which complicates static analysis: | ||
|
|
||
| ```ts twoslash | ||
| import * as d from 'typegpu/data'; | ||
|
|
||
| declare const getUserSettings: () => Promise<{ halfPrecision: boolean }>; | ||
| // ---cut--- | ||
| const half = (await getUserSettings()).halfPrecision; | ||
|
|
||
| // Determining the precision based on a runtime parameter | ||
| const vec3 = half ? d.vec3h : d.vec3f; | ||
|
|
||
| const Boid = d.struct({ | ||
| pos: vec3, | ||
| vel: vec3, | ||
| }); | ||
|
|
||
| const createBoid = () => { | ||
| 'use gpu'; | ||
| return Boid({ pos: vec3(), vel: vec3(0, 1, 0) }); | ||
| }; | ||
|
|
||
| const boid = createBoid(); | ||
| // ^? | ||
| ``` | ||
|
|
||
| :::caution | ||
| We could do everything at runtime, transforming the code a bit so that the TypeGPU shader generator can have access to | ||
| a function's JS source code, along with references to values referenced from the outer scope. | ||
iwoplaza marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| The code shipped to the browser could look like so: | ||
| ```js | ||
| const createBoid = () => { | ||
| 'use gpu'; | ||
| return Boid({ pos: vec3(), vel: vec3(0, 1, 0) }); | ||
| }; | ||
|
|
||
| // Associate metadata with the function, which the TypeGPU generator can later use | ||
| (globalThis.__TYPEGPU_META__ ??= new WeakMap()).set(createBoid, { | ||
| v: 1, | ||
| name: 'createBoid', | ||
| code: `() => { | ||
| 'use gpu'; | ||
| return Boid({ pos: vec3(), vel: vec3(0, 1, 0) }); | ||
| }`, | ||
| get externals() { return { Boid, vec3 }; }, | ||
| }); | ||
| ``` | ||
|
|
||
| However, parsing code at runtime requires both shipping the parser to the end user, and having to spend time parsing the code, | ||
| sacrificing load times and performance. | ||
| ::: | ||
|
|
||
| In order to avoid parsing at runtime while keeping the desired flexibility, we parse the AST at build time and compress it into | ||
| our custom format called [tinyest](https://npmjs.com/package/tinyest). It retains only information required for WGSL code | ||
| generation. | ||
|
|
||
| The code shipped to the browser looks more like this: | ||
| ```js | ||
| const createBoid = () => { | ||
| 'use gpu'; | ||
| return Boid({ pos: vec3(), vel: vec3(0, 1, 0) }); | ||
| }; | ||
|
|
||
| (globalThis.__TYPEGPU_META__ ??= new WeakMap()).set(createBoid, { | ||
| v: 1, | ||
| name: 'createBoid', | ||
| // NOTE: Not meant to be read by humans | ||
| ast: {params:[],body:[0,[[10,[6,"Boid",[[104,{pos:[6,"vec3",[]],vel:[6,"vec3",[[5,"0"],[5,"1"],[5,"0"]]]}]]]]]],externalNames:["Boid","vec3"]}, | ||
| get externals() { return { Boid, vec3 }; }, | ||
| }); | ||
| ``` | ||
|
|
||
| ## Snippets | ||
|
|
||
| Snippets are the basis for TypeGPU shader code generation. They are immutable objects that hold three values: | ||
| - *value*: A piece of WGSL code, or something "resolvable" to a piece of WGSL code | ||
| - *dataType*: The inferred WGSL type of `value` [(more here)](#data-types) | ||
| - *origin*: An enumerable of where the value came from (if it's a reference to an existing value, or ephemeral) | ||
| [(more here)](#origins) | ||
|
|
||
| ```ts | ||
| // A simple snippet of a piece of WGSL code | ||
| const foo = snip( | ||
| /* value */ 'vec3f(1, 2, 3)', | ||
| /* dataType */ d.vec3f, | ||
| /* origin */ 'constant' | ||
| ); // => Snippet | ||
|
|
||
| // A simple snippet of something resolvable to a piece of WGSL code | ||
| const bar = snip( | ||
| /* value */ d.vec3f(1, 2, 3), | ||
| /* dataType */ d.vec3f, | ||
| /* origin */ 'constant' | ||
| ); // => Snippet | ||
| ``` | ||
|
|
||
| If a snippet contains a value that isn't yet resolved WGSL, we defer that resolution as late as possible, so that we can | ||
| perform optimizations as we generate. For example, if we're evaluating the given expression `3 * 4`, we first interpret | ||
| both operands as snippets `snip(3, abstractInt, 'constant')` and `snip(4, abstractInt, 'constant')` respectively. | ||
| Since both are not yet resolved (or in other words, known at compile time), we can perform the multiplication at compile time, | ||
| resulting in a new snippet `snip(12, abstractInt, 'constant')`. | ||
|
|
||
| :::note | ||
| If we were instead resolving eagerly, the resulting snippet would be `snip('3 * 4', abstractInt, 'constant')`. | ||
| ::: | ||
|
|
||
| ### Data Types | ||
|
|
||
| The data types that accompany snippets are just [TypeGPU Data Schemas](/TypeGPU/fundamentals/data-schemas). This information | ||
| can be used by parent expressions to generate different code. | ||
|
|
||
| :::note | ||
| Data type inference is the basis for generating signatures for functions just from the arguments passed to them. | ||
| ::: | ||
|
|
||
| ### Origins | ||
|
|
||
| Origins are enumerable values that describe where a value came from (or didn't come from). Used mainly for: | ||
| - Determining if we're using a value that refers to something else (to create an implicit pointer). This mimics the behavior we | ||
| expect in JS, and doesn't perform unwanted copies on data. Example: | ||
| ```ts | ||
| const foo = () => { | ||
| 'use gpu'; | ||
| // The type of both expressions is `Boid`, yet one is a | ||
| // reference to an existing value, and the other is a | ||
| // value-type (ephemeral) and would disappear if we didn't | ||
| // assign it to a variable or use it. | ||
| const firstBoid = layout.$.boids[0]; | ||
| const newBoid = Boid(); | ||
| const copiedBoid = Boid(firstBoid); | ||
|
|
||
| const boidPos = newBoid.pos; | ||
| }; | ||
| ``` | ||
| Generates: | ||
| ```wgsl | ||
| fn foo() { | ||
| let firstBoid = (&boids[0]); // typed as ptr<storage, Boid, read_write> | ||
| var newBoid = Boid(); // typed as Boid | ||
aleksanderkatan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| var copiedBoid = firstBoid; // typed as Boid | ||
|
|
||
| let boidPos = (&newBoid.pos); // typed as ptr<function, vec3f> | ||
| } | ||
| ``` | ||
| - Detecting illegal uses of our APIs. One example is mutating a value that was passed in as an argument. Since we want the developer to have control over | ||
| passing something as value or as reference (pointer), we have to limit the dev's ability to mutate values that were passed in as arguments if they didn't | ||
| use refs (pointer instances). Otherwise, the generated WGSL won't act as we expect. | ||
| ```ts | ||
| const advance = (pos: d.v3f) => { | ||
| 'use gpu'; | ||
| // `pos` has the origin 'argument'. Property accesses on arguments | ||
| // return snippets that also have the origin 'argument'. | ||
| // | ||
| // If we try to mutate a snippet that has the origin 'argument', | ||
| // we'll get a resolution error. | ||
| pos.x += 1; | ||
| }; | ||
|
|
||
| const main = () => { | ||
| 'use gpu'; | ||
| const pos = d.vec3f(0, 0, 0); | ||
| advance(pos); | ||
| // pos.x === 1 in JS | ||
| }; | ||
| ``` | ||
| Would generate: | ||
| ```wgsl | ||
| fn advance(pos: vec3f) { | ||
| pos.x += 1; | ||
| } | ||
|
|
||
| fn main() { | ||
| let pos = vec3f(0, 0, 0); | ||
| advance(pos); | ||
| // pos.x === 0 in WGSL | ||
| } | ||
| ``` | ||
|
|
||
| There are essentially three types of origins: | ||
| - **Ephemeral Origins**: These origins represent values that are created or derived from other values. They are typically used for creating new instances or | ||
| performing operations that produce new values. Examples include creating a new `Boid` instance or calculating a new position based on an existing one. These | ||
| include `'runtime'` and `'constant'`. | ||
| - **Referential Origins**: These origins represent references to existing values. They are typically used for accessing or modifying existing data. Examples | ||
| include accessing the position of an existing `Boid` instance or modifying the position of an existing `Boid` instance. These include `'uniform'`, `'mutable'`, `'readonly'`, `'workgroup'`, `'private'`, `'function'`, `'handle'`, `'constant-ref'` and `'this-function'`. | ||
| - `'uniform'`, `'mutable'`, `'readonly'`, `'workgroup'`, `'private'`, `'function'`, `'handle'` all reflect address spaces that values can belong to, and | ||
| we use them to determine what kind of pointer type they are. | ||
| - `'constant-ref'` is a reference to a value stored in a `tgpu.const`. They're different from `constant`s, as we know that even if they're referential (non-primitive), the developer cannot mutate them. | ||
| - `'this-function'` lets us track whether values originates from the function we're currently generating, or the function that called us. | ||
| - **Argument Origins**: This group is dedicated to exactly one origin: 'argument'. It represents values that are passed as arguments to functions. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.