Skip to content

Commit 5625b66

Browse files
committed
refactor: Better origin tracking
1 parent 4c4d4e7 commit 5625b66

19 files changed

Lines changed: 280 additions & 306 deletions

apps/typegpu-docs/src/content/docs/advanced/shader-generation.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,10 @@ There are essentially three types of origins:
197197
performing operations that produce new values. Examples include creating a new `Boid` instance or calculating a new position based on an existing one. These
198198
include `'runtime'` and `'constant'`.
199199
- **Referential Origins**: These origins represent references to existing values. They are typically used for accessing or modifying existing data. Examples
200-
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'`.
200+
include accessing the position of an existing `Boid` instance or modifying the position of an existing `Boid` instance:
201201
- `'uniform'`, `'mutable'`, `'readonly'`, `'workgroup'`, `'private'`, `'function'`, `'handle'` all reflect address spaces that values can belong to, and
202202
we use them to determine what kind of pointer type they are.
203203
- `'constant-tgpu-const-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.
204204
- `'runtime-tgpu-const-ref'` is a reference to a value stored in a `tgpu.const` but accessed in a way that isn’t constant at shader‑creation time (for example, indexed using a runtime value).
205-
- `'this-function'` lets us track whether values originates from the function we're currently generating, or the function that called us.
205+
- `'local'` lets us track whether values originate from the function we're currently generating.
206206
- **Argument Origins**: This group is dedicated to exactly one origin: 'argument'. It represents values that are passed as arguments to functions.

packages/typegpu/src/core/buffer/bufferUsage.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts';
22
import { type ResolvedSnippet, snip } from '../../data/snippet.ts';
3-
import { type AnyWgslData, type BaseData, isNaturallyEphemeral } from '../../data/wgslTypes.ts';
3+
import { type AnyWgslData, type BaseData } from '../../data/wgslTypes.ts';
44
import { IllegalBufferAccessError } from '../../errors.ts';
55
import { getExecMode, inCodegenMode, isInsideTgpuFn } from '../../execMode.ts';
66
import { isUsableAsStorage, type StorageFlag } from '../../extension.ts';
@@ -129,7 +129,7 @@ class TgpuFixedBufferImpl<TData extends BaseData, TUsage extends BindableBufferU
129129
`@group(${group}) @binding(${binding}) var<${usage}> ${id}: ${ctx.resolve(dataType).value};`,
130130
);
131131

132-
return snip(id, dataType, isNaturallyEphemeral(dataType) ? 'runtime' : this.usage);
132+
return snip(id, dataType, this.usage);
133133
}
134134

135135
toString(): string {
@@ -144,7 +144,7 @@ class TgpuFixedBufferImpl<TData extends BaseData, TUsage extends BindableBufferU
144144
{
145145
[$internal]: true,
146146
get [$ownSnippet]() {
147-
return snip(this, dataType, isNaturallyEphemeral(dataType) ? 'runtime' : usage);
147+
return snip(this, dataType, usage);
148148
},
149149
[$resolve]: (ctx) => ctx.resolve(this),
150150
toString: () => `${this.usage}:${getName(this) ?? '<unnamed>'}.$`,
@@ -251,7 +251,7 @@ export class TgpuLaidOutBufferImpl<TData extends BaseData, TUsage extends Bindab
251251
};`,
252252
);
253253

254-
return snip(id, this.dataType, isNaturallyEphemeral(this.dataType) ? 'runtime' : this.usage);
254+
return snip(id, this.dataType, this.usage);
255255
}
256256

257257
toString(): string {
@@ -266,7 +266,7 @@ export class TgpuLaidOutBufferImpl<TData extends BaseData, TUsage extends Bindab
266266
{
267267
[$internal]: true,
268268
get [$ownSnippet]() {
269-
return snip(this, schema, isNaturallyEphemeral(schema) ? 'runtime' : usage);
269+
return snip(this, schema, usage);
270270
},
271271
[$resolve]: (ctx) => ctx.resolve(this),
272272
toString: () => `${this.usage}:${getName(this) ?? '<unnamed>'}.$`,

packages/typegpu/src/core/constant/tgpuConstant.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import { isData, type AnyData } from '../../data/dataTypes.ts';
22
import { type ResolvedSnippet, snip } from '../../data/snippet.ts';
3-
import {
4-
type AnyWgslData,
5-
type BaseData,
6-
isNaturallyEphemeral,
7-
type WgslArray,
8-
} from '../../data/wgslTypes.ts';
3+
import { type AnyWgslData, type BaseData, type WgslArray } from '../../data/wgslTypes.ts';
94
import { inCodegenMode } from '../../execMode.ts';
105
import type { TgpuNamable } from '../../shared/meta.ts';
116
import { getName, setName } from '../../shared/meta.ts';
@@ -119,11 +114,7 @@ class TgpuConstImpl<TDataType extends BaseData> implements TgpuConst<TDataType>,
119114

120115
ctx.addDeclaration(`const ${id}: ${resolvedDataType} = ${resolvedValue};`);
121116

122-
return snip(
123-
id,
124-
this.dataType,
125-
isNaturallyEphemeral(this.dataType) ? 'constant' : 'constant-tgpu-const-ref',
126-
);
117+
return snip(id, this.dataType, 'constant-tgpu-const-ref');
127118
}
128119

129120
toString() {
@@ -137,11 +128,7 @@ class TgpuConstImpl<TDataType extends BaseData> implements TgpuConst<TDataType>,
137128
{
138129
[$internal]: true,
139130
get [$ownSnippet]() {
140-
return snip(
141-
this,
142-
dataType,
143-
isNaturallyEphemeral(dataType) ? 'constant' : 'constant-tgpu-const-ref',
144-
);
131+
return snip(this, dataType, 'constant-tgpu-const-ref');
145132
},
146133
[$resolve]: (ctx) => ctx.resolve(this),
147134
toString: () => `const:${getName(this) ?? '<unnamed>'}.$`,

packages/typegpu/src/core/rawCodeSnippet/tgpuRawCodeSnippet.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ export interface TgpuRawCodeSnippet<TDataType extends BaseData> {
2525
}
2626

2727
// The origin 'function' refers to values passed in from the calling scope, which means
28-
// we would have access to this value anyway. Same goes for 'argument' and 'this-function',
28+
// we would have access to this value anyway. Same goes for 'argument' and 'local',
2929
// the values literally exist in the function we're writing.
3030
//
31-
// 'constant-ref' was excluded because it's a special origin reserved for tgpu.const values.
31+
// '*-tgpu-const-ref' were excluded because they're a special origin reserved for tgpu.const values.
3232
export type RawCodeSnippetOrigin = Exclude<
3333
Origin,
34-
'function' | 'this-function' | 'argument' | 'constant-ref'
34+
'function' | 'local' | 'argument' | 'constant-tgpu-const-ref' | 'runtime-tgpu-const-ref'
3535
>;
3636

3737
/**

packages/typegpu/src/core/variable/tgpuVariable.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { AnyData } from '../../data/dataTypes.ts';
22
import { type ResolvedSnippet, snip } from '../../data/snippet.ts';
3-
import { type BaseData, isNaturallyEphemeral } from '../../data/wgslTypes.ts';
3+
import type { BaseData } from '../../data/wgslTypes.ts';
44
import { IllegalVarAccessError } from '../../errors.ts';
55
import { getExecMode, isInsideTgpuFn } from '../../execMode.ts';
66
import type { TgpuNamable } from '../../shared/meta.ts';
@@ -96,7 +96,7 @@ class TgpuVarImpl<TScope extends VariableScope, TDataType extends BaseData>
9696
ctx.addDeclaration(`${pre};`);
9797
}
9898

99-
return snip(id, this.#dataType, isNaturallyEphemeral(this.#dataType) ? 'runtime' : this.#scope);
99+
return snip(id, this.#dataType, this.#scope);
100100
}
101101

102102
$name(label: string) {
@@ -110,7 +110,7 @@ class TgpuVarImpl<TScope extends VariableScope, TDataType extends BaseData>
110110

111111
get [$gpuValueOf](): InferGPU<TDataType> {
112112
const dataType = this.#dataType;
113-
const origin = isNaturallyEphemeral(dataType) ? 'runtime' : this.#scope;
113+
const origin = this.#scope;
114114

115115
return new Proxy(
116116
{

packages/typegpu/src/data/ref.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { DualFn, SelfResolvable } from '../types.ts';
66
import { UnknownData } from './dataTypes.ts';
77
import { createPtrFromOrigin, explicitFrom } from './ptr.ts';
88
import { type ResolvedSnippet, snip, type Snippet } from './snippet.ts';
9-
import { isNaturallyEphemeral, isPtr, type Ptr, type StorableData } from './wgslTypes.ts';
9+
import { isPtr, type Ptr, type StorableData } from './wgslTypes.ts';
1010

1111
// ----------
1212
// Public API
@@ -172,11 +172,10 @@ export function derefSnippet(snippet: Snippet): Snippet {
172172
}
173173

174174
const innerType = snippet.dataType.inner;
175-
const origin = isNaturallyEphemeral(innerType) ? 'runtime' : snippet.origin;
176175

177176
if (snippet.value instanceof RefOperator) {
178-
return snip(stitch`${snippet.value.snippet}`, innerType, origin);
177+
return snip(stitch`${snippet.value.snippet}`, innerType, snippet.origin);
179178
}
180179

181-
return snip(stitch`(*${snippet})`, innerType, origin);
180+
return snip(stitch`(*${snippet})`, innerType, snippet.origin);
182181
}

packages/typegpu/src/data/snippet.ts

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,39 @@
11
import { undecorate } from './dataTypes.ts';
22
import type { UnknownData } from './dataTypes.ts';
33
import { DEV } from '../shared/env.ts';
4-
import { type BaseData, isNumericSchema } from './wgslTypes.ts';
4+
import { type BaseData, isNaturallyEphemeral, isNumericSchema } from './wgslTypes.ts';
55

66
export type Origin =
7-
| 'uniform'
8-
| 'readonly' // equivalent to ptr<storage, ..., read>
9-
| 'mutable' // equivalent to ptr<storage, ..., read-write>
10-
| 'workgroup'
11-
| 'private'
12-
| 'function'
13-
| 'this-function'
14-
| 'handle'
15-
// is an argument (or part of an argument) given to the
16-
// function we're resolving. This includes primitives, to
17-
// catch cases where we update an argument's primitive member
18-
// prop, e.g.: `vec.x += 1;`
7+
// ADDRESS SPACE ORIGINS
8+
| 'uniform' /* defined in the 'uniform' address space */
9+
| 'readonly' /* defined in the 'storage' address space, with 'read' access */
10+
| 'mutable' /* defined in the 'storage' address space, with 'read-write' access */
11+
| 'workgroup' /* defined in the 'workgroup' address space */
12+
| 'private' /* defined in the 'private' address space */
13+
| 'handle' /* defined in the 'handle' address space */
14+
| 'function' /* defined in a callee, passed down to us as an argument ('function' address space) */
15+
// ---------
16+
// non-pointer function arguments (or part of an argument).
1917
| 'argument'
20-
// not a ref to anything, known at runtime
18+
// defined in the current function
19+
| 'local'
20+
// not a reference to anything, known at runtime
2121
| 'runtime'
22-
// not a ref to anything, known at pipeline creation time
23-
// (not to be confused with 'comptime')
24-
// note that this doesn't automatically mean the value can be stored in a `const`
25-
// variable, more so that it's valid to do so in WGSL (but not necessarily safe to do in JS shaders)
22+
// an ephemeral value that is a valid WGSL 'constant' (not to be confused with 'comptime')
23+
// doesn't always lead to creating a `const` variable, as we cannot always guarantee that
24+
// the value won't be mutated in JS
2625
| 'constant'
27-
// don't even get me started on these. They're references to non-primitive values that originate
28-
// from a tgpu.const(...).$ call.
29-
| 'constant-tgpu-const-ref' /* turns into a `const` when assigned to a variable */
30-
| 'runtime-tgpu-const-ref' /* turns into a `let` when assigned to a variable */;
31-
32-
export function isEphemeralOrigin(space: Origin) {
33-
return space === 'runtime' || space === 'constant' || space === 'argument';
34-
}
26+
//
27+
// TGPU.CONST REFERENCES
28+
// A reference to a tgpu.const().$ value (which is frozen), that is treated by WGSL as a 'constant'.
29+
// This is the usual case, read about 'runtime-tgpu-const-ref' to know when this doesn't apply.
30+
// Turns into a `const` when assigned to a variable
31+
| 'constant-tgpu-const-ref'
32+
// A reference to a tgpu.const().$ value (which is frozen), that is NOT treated by WGSL as a 'constant'.
33+
// This can happen if say a constant is accessed with a runtime-known index. WGSL doesn't treat it like
34+
// a 'constant' anymore, but it's still a frozen value in JS, so we must treat it like it's immutable.
35+
// Turns into a `let` when assigned to a variable
36+
| 'runtime-tgpu-const-ref';
3537

3638
/**
3739
* What happens to a snippet's origin when it's deep copied in JS, and left as is in WGSL?
@@ -49,8 +51,29 @@ export function fallthroughCopyOrigin(origin: Origin): Origin {
4951
return 'runtime';
5052
}
5153

52-
export function isEphemeralSnippet(snippet: Snippet) {
53-
return isEphemeralOrigin(snippet.origin);
54+
export function isReference(snippet: Snippet) {
55+
return !(snippet.origin === 'runtime' || snippet.origin === 'constant');
56+
}
57+
58+
/**
59+
* TODO: Remove this in favor of just writing plain logic. This removes the need to name this behavior in any way
60+
*
61+
* Snippets that "carry mutable references" are those that, when assigned to a variable,
62+
* assign a name to a value that is already mutable by other means in the shader.
63+
*
64+
* ```ts
65+
* // "d.vec3f()" is a fresh value, doesn't carry a reference to anything already existing before
66+
* const vec = d.vec3f();
67+
* const foo = vec; // the vector now is mutable through `vec` and accessible through `foo`.
68+
* ```
69+
*/
70+
export function assignmentIsReassignment(snippet: Snippet): boolean {
71+
if (isNaturallyEphemeral(snippet.dataType)) {
72+
return false;
73+
}
74+
if (snippet.origin === 'function' && snippet.dataType) {
75+
}
76+
return !(snippet.origin === 'runtime' || snippet.origin === 'constant');
5477
}
5578

5679
export const originToPtrParams = {
@@ -60,7 +83,8 @@ export const originToPtrParams = {
6083
workgroup: { space: 'workgroup', access: 'read-write' },
6184
private: { space: 'private', access: 'read-write' },
6285
function: { space: 'function', access: 'read-write' },
63-
'this-function': { space: 'function', access: 'read-write' },
86+
// Local declarations are also in the `function` address space
87+
local: { space: 'function', access: 'read-write' },
6488
} as const;
6589
export type OriginToPtrParams = typeof originToPtrParams;
6690

packages/typegpu/src/tgsl/accessIndex.ts

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
import { stitch } from '../core/resolve/stitch.ts';
22
import { isDisarray, MatrixColumnsAccess } from '../data/dataTypes.ts';
33
import { derefSnippet } from '../data/ref.ts';
4-
import { isEphemeralSnippet, type Origin, snip, type Snippet } from '../data/snippet.ts';
4+
import { type Origin, snip, type Snippet } from '../data/snippet.ts';
55
import { vec2f, vec3f, vec4f } from '../data/vector.ts';
6-
import {
7-
type BaseData,
8-
isNaturallyEphemeral,
9-
isPtr,
10-
isVec,
11-
isWgslArray,
12-
isWgslStruct,
13-
} from '../data/wgslTypes.ts';
6+
import { type BaseData, isPtr, isVec, isWgslArray, isWgslStruct } from '../data/wgslTypes.ts';
147
import { isKnownAtComptime } from '../types.ts';
158
import { accessProp } from './accessProp.ts';
169
import { ArrayExpression, coerceToSnippet } from './generationHelpers.ts';
@@ -27,31 +20,18 @@ export function accessIndex(target: Snippet, indexArg: Snippet | number): Snippe
2720
// array
2821
if (isWgslArray(target.dataType) || isDisarray(target.dataType)) {
2922
const elementType = target.dataType.elementType;
30-
const isElementNatEph = isNaturallyEphemeral(elementType);
31-
const isTargetEphemeral = isEphemeralSnippet(target);
32-
const isIndexConstant = index.origin === 'constant';
3323

3424
let origin: Origin;
3525

3626
if (target.origin === 'constant-tgpu-const-ref') {
3727
// Constant refs stay const unless the element/index forces runtime materialization
38-
if (isIndexConstant) {
39-
origin = isElementNatEph ? 'constant' : 'constant-tgpu-const-ref';
40-
} else {
41-
origin = isElementNatEph ? 'runtime' : 'runtime-tgpu-const-ref';
42-
}
43-
} else if (target.origin === 'runtime-tgpu-const-ref') {
44-
// Runtime refs keep their ref semantics unless the element is ephemeral only
45-
origin = isElementNatEph ? 'runtime' : 'runtime-tgpu-const-ref';
46-
} else if (!isTargetEphemeral && !isElementNatEph) {
47-
// Stable containers can forward their origin information
48-
origin = target.origin;
49-
} else if (isIndexConstant && target.origin === 'constant') {
50-
// Plain constants indexed with constants stay constant
51-
origin = 'constant';
28+
origin = index.origin === 'constant' ? 'constant-tgpu-const-ref' : 'runtime-tgpu-const-ref';
29+
} else if (target.origin === 'constant') {
30+
// Ephemeral constants indexed with constants stay constant, otherwise they become runtime-known
31+
origin = index.origin === 'constant' ? 'constant' : 'runtime';
5232
} else {
53-
// Everything else must be produced at runtime
54-
origin = 'runtime';
33+
// Fallthrough
34+
origin = target.origin;
5535
}
5636

5737
if (target.value instanceof ArrayExpression && isKnownAtComptime(index)) {
@@ -76,9 +56,7 @@ export function accessIndex(target: Snippet, indexArg: Snippet | number): Snippe
7656
(target.value as any)[index.value as any]
7757
: stitch`${target}[${index}]`,
7858
target.dataType.primitive,
79-
/* origin */ target.origin === 'constant' || target.origin === 'constant-tgpu-const-ref'
80-
? 'constant'
81-
: 'runtime',
59+
/* origin */ target.origin,
8260
);
8361
}
8462

packages/typegpu/src/tgsl/accessProp.ts

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from '../data/dataTypes.ts';
1111
import { abstractInt, bool, f16, f32, i32, u32 } from '../data/numeric.ts';
1212
import { derefSnippet } from '../data/ref.ts';
13-
import { isEphemeralSnippet, isSnippet, snip, type Snippet } from '../data/snippet.ts';
13+
import { isSnippet, snip, type Snippet } from '../data/snippet.ts';
1414
import {
1515
vec2b,
1616
vec2f,
@@ -31,7 +31,6 @@ import {
3131
import {
3232
type BaseData,
3333
isMat,
34-
isNaturallyEphemeral,
3534
isPtr,
3635
isVec,
3736
isWgslArray,
@@ -138,17 +137,7 @@ export function accessProp(target: Snippet, propName: string): Snippet | undefin
138137
}
139138
propType = undecorate(propType);
140139

141-
return snip(
142-
stitch`${target}.${propName}`,
143-
propType,
144-
/* origin */ target.origin === 'argument'
145-
? 'argument'
146-
: !isEphemeralSnippet(target) && !isNaturallyEphemeral(propType)
147-
? target.origin
148-
: target.origin === 'constant' || target.origin === 'constant-tgpu-const-ref'
149-
? 'constant'
150-
: 'runtime',
151-
);
140+
return snip(stitch`${target}.${propName}`, propType, /* origin */ target.origin);
152141
}
153142

154143
if (target.dataType instanceof AutoStruct) {
@@ -215,9 +204,9 @@ export function accessProp(target: Snippet, propName: string): Snippet | undefin
215204
: stitch`${target}.${propName}`,
216205
swizzleType,
217206
// Swizzling creates new vectors (unless they're on the lhs of an assignment, but that's not yet supported in WGSL)
218-
/* origin */ target.origin === 'argument' && propLength === 1
219-
? 'argument'
220-
: target.origin === 'constant' || target.origin === 'constant-tgpu-const-ref'
207+
/* origin */ propLength === 1
208+
? target.origin
209+
: target.origin === 'constant'
221210
? 'constant'
222211
: 'runtime',
223212
);

0 commit comments

Comments
 (0)