Skip to content

Commit 4a0f67c

Browse files
authored
impr: block externals (#2188)
1 parent 1baffd1 commit 4a0f67c

6 files changed

Lines changed: 164 additions & 14 deletions

File tree

packages/typegpu/src/resolutionCtx.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ class ItemStateStackImpl implements ItemStateStack {
163163
this._stack.push({
164164
type: 'blockScope',
165165
declarations: new Map(),
166+
externals: new Map(),
166167
});
167168
}
168169

@@ -232,7 +233,8 @@ class ItemStateStackImpl implements ItemStateStack {
232233
}
233234

234235
if (layer?.type === 'blockScope') {
235-
const snippet = layer.declarations.get(id);
236+
// the order matters
237+
const snippet = layer.declarations.get(id) ?? layer.externals.get(id);
236238
if (snippet !== undefined) {
237239
return snippet;
238240
}
@@ -260,6 +262,30 @@ class ItemStateStackImpl implements ItemStateStack {
260262

261263
throw new Error('No block scope found to define a variable in.');
262264
}
265+
266+
setBlockExternals(externals: Record<string, Snippet>) {
267+
for (let i = this._stack.length - 1; i >= 0; --i) {
268+
const layer = this._stack[i];
269+
if (layer?.type === 'blockScope') {
270+
Object.entries(externals).forEach(([id, snippet]) => {
271+
layer.externals.set(id, snippet);
272+
});
273+
return;
274+
}
275+
}
276+
throw new Error('No block scope found to set externals in.');
277+
}
278+
279+
clearBlockExternals() {
280+
for (let i = this._stack.length - 1; i >= 0; --i) {
281+
const layer = this._stack[i];
282+
if (layer?.type === 'blockScope') {
283+
layer.externals.clear();
284+
return;
285+
}
286+
}
287+
throw new Error('No block scope found to clear externals in.');
288+
}
263289
}
264290

265291
const INDENT = [
@@ -429,6 +455,14 @@ export class ResolutionCtxImpl implements ResolutionCtx {
429455
this._itemStateStack.pop('blockScope');
430456
}
431457

458+
setBlockExternals(externals: Record<string, Snippet>) {
459+
this._itemStateStack.setBlockExternals(externals);
460+
}
461+
462+
clearBlockExternals() {
463+
this._itemStateStack.clearBlockExternals();
464+
}
465+
432466
generateLog(op: string, args: Snippet[]): Snippet {
433467
return this.#logGenerator.generateLog(this, op, args);
434468
}

packages/typegpu/src/tgsl/generationHelpers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ export type GenerationCtx = ResolutionCtx & {
9292
generateLog(op: string, args: Snippet[]): Snippet;
9393
getById(id: string): Snippet | null;
9494
defineVariable(id: string, snippet: Snippet): void;
95+
setBlockExternals(externals: Record<string, Snippet>): void;
96+
clearBlockExternals(): void;
9597

9698
/**
9799
* Types that are used in `return` statements are

packages/typegpu/src/tgsl/shaderGenerator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import type { Block, Expression, Statement } from 'tinyest';
22
import type { Snippet } from '../data/snippet.ts';
33
import type { GenerationCtx } from './generationHelpers.ts';
44
import type { BaseData } from '../data/wgslTypes.ts';
5+
import type { ExternalMap } from '../core/resolve/externals.ts';
56

67
export interface ShaderGenerator {
78
initGenerator(ctx: GenerationCtx): void;
8-
block(body: Block): string;
9+
block(body: Block, externalMap?: ExternalMap): string;
910
identifier(id: string): Snippet;
1011
typedExpression(expression: Expression, expectedType: BaseData): Snippet;
1112
expression(expression: Expression): Snippet;

packages/typegpu/src/tgsl/wgslGenerator.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
} from './conversion.ts';
3737
import {
3838
ArrayExpression,
39+
coerceToSnippet,
3940
concretize,
4041
type GenerationCtx,
4142
numericLiteralToSnippet,
@@ -51,6 +52,7 @@ import type { AnyFn } from '../core/function/fnTypes.ts';
5152
import { arrayLength } from '../std/array.ts';
5253
import { AutoStruct } from '../data/autoStruct.ts';
5354
import { mathToStd } from './math.ts';
55+
import type { ExternalMap } from '../core/resolve/externals.ts';
5456

5557
const { NodeTypeCatalog: NODE } = tinyest;
5658

@@ -200,8 +202,19 @@ class WgslGenerator implements ShaderGenerator {
200202

201203
public block(
202204
[_, statements]: tinyest.Block,
205+
externalMap?: ExternalMap,
203206
): string {
204207
this.ctx.pushBlockScope();
208+
209+
if (externalMap) {
210+
const externals = Object.fromEntries(
211+
Object.entries(externalMap).map((
212+
[id, value],
213+
) => [id, coerceToSnippet(value)]),
214+
);
215+
this.ctx.setBlockExternals(externals);
216+
}
217+
205218
try {
206219
this.ctx.indent();
207220
const body = statements.map((statement) => this.statement(statement))
@@ -1233,8 +1246,6 @@ ${this.ctx.pre}else ${alternate}`;
12331246
// If it's ephemeral, it's a value that cannot change. If it's a reference, we take
12341247
// an implicit pointer to it
12351248
let loopVarKind = 'let';
1236-
const loopVarName = this.ctx.makeNameValid(loopVar[1]);
1237-
12381249
if (!isEphemeralSnippet(elementSnippet)) {
12391250
if (elementSnippet.origin === 'constant-tgpu-const-ref') {
12401251
loopVarKind = 'const';
@@ -1260,21 +1271,13 @@ ${this.ctx.pre}else ${alternate}`;
12601271
}
12611272
}
12621273

1263-
const loopVarSnippet = snip(
1264-
loopVarName,
1265-
elementType,
1266-
elementSnippet.origin,
1267-
);
1268-
1269-
this.ctx.defineVariable(loopVar[1], loopVarSnippet);
1270-
12711274
const forStr =
12721275
stitch`${this.ctx.pre}for (var ${index} = 0u; ${index} < ${
12731276
tryConvertSnippet(this.ctx, elementCountSnippet, u32, false)
12741277
}; ${index}++) {`;
1275-
12761278
this.ctx.indent();
12771279

1280+
const loopVarName = this.ctx.makeNameValid(loopVar[1]);
12781281
const loopVarDeclStr =
12791282
stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${
12801283
tryConvertSnippet(
@@ -1286,7 +1289,9 @@ ${this.ctx.pre}else ${alternate}`;
12861289
};`;
12871290

12881291
const bodyStr = `${this.ctx.pre}${
1289-
this.block(blockifySingleStatement(body))
1292+
this.block(blockifySingleStatement(body), {
1293+
[loopVar[1]]: snip(loopVarName, elementType, elementSnippet.origin),
1294+
})
12901295
}`;
12911296

12921297
this.ctx.dedent();

packages/typegpu/src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export type SlotBindingLayer = {
124124
export type BlockScopeLayer = {
125125
type: 'blockScope';
126126
declarations: Map<string, Snippet>;
127+
externals: Map<string, Snippet>;
127128
};
128129

129130
export type StackLayer =
@@ -151,6 +152,8 @@ export interface ItemStateStack {
151152
externalMap: Record<string, unknown>,
152153
): FunctionScopeLayer;
153154
pushBlockScope(): void;
155+
setBlockExternals(externals: Record<string, Snippet>): void;
156+
clearBlockExternals(): void;
154157

155158
pop<T extends StackLayer['type']>(type: T): Extract<StackLayer, { type: T }>;
156159
pop(): StackLayer | undefined;

packages/typegpu/tests/tgsl/wgslGenerator.test.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,4 +1740,109 @@ describe('wgslGenerator', () => {
17401740
}"
17411741
`);
17421742
});
1743+
1744+
it('block externals do not override identifiers', () => {
1745+
const f = () => {
1746+
'use gpu';
1747+
const y = 100;
1748+
const x = y;
1749+
return x;
1750+
};
1751+
1752+
const parsed = getMetaData(f)?.ast?.body as tinyest.Block;
1753+
1754+
provideCtx(ctx, () => {
1755+
ctx[$internal].itemStateStack.pushFunctionScope(
1756+
'normal',
1757+
[],
1758+
{},
1759+
d.u32,
1760+
{},
1761+
);
1762+
1763+
const res = wgslGenerator.block(
1764+
parsed,
1765+
{ x: 42 },
1766+
);
1767+
1768+
expect(res).toMatchInlineSnapshot(`
1769+
"{
1770+
const y = 100;
1771+
const x = y;
1772+
return u32(x);
1773+
}"
1774+
`);
1775+
});
1776+
});
1777+
1778+
it('block externals are injected correctly', () => {
1779+
const f = () => {
1780+
'use gpu';
1781+
for (const x of []) {
1782+
const y = x;
1783+
}
1784+
};
1785+
1786+
const parsed = getMetaData(f)?.ast?.body as tinyest.Block;
1787+
1788+
provideCtx(ctx, () => {
1789+
ctx[$internal].itemStateStack.pushFunctionScope(
1790+
'normal',
1791+
[],
1792+
{},
1793+
d.Void,
1794+
{},
1795+
);
1796+
1797+
const res = wgslGenerator.block(
1798+
(parsed[1][0] as tinyest.ForOf)[3] as tinyest.Block,
1799+
{ x: 67 },
1800+
);
1801+
1802+
expect(res).toMatchInlineSnapshot(`
1803+
"{
1804+
const y = 67;
1805+
}"
1806+
`);
1807+
});
1808+
});
1809+
1810+
it('block externals are respected in nested blocks', () => {
1811+
const f = () => {
1812+
'use gpu';
1813+
let result = d.i32(0);
1814+
const list = d.arrayOf(d.i32, 3)([1, 2, 3]);
1815+
for (const elem of list) {
1816+
{
1817+
// We use the `elem` in a nested block
1818+
result += elem;
1819+
}
1820+
}
1821+
};
1822+
1823+
const parsed = getMetaData(f)?.ast?.body as tinyest.Block;
1824+
1825+
provideCtx(ctx, () => {
1826+
ctx[$internal].itemStateStack.pushFunctionScope(
1827+
'normal',
1828+
[],
1829+
{},
1830+
d.Void,
1831+
{},
1832+
);
1833+
1834+
const res = wgslGenerator.block(
1835+
(parsed[1][2] as tinyest.ForOf)[3] as tinyest.Block,
1836+
{ result: snip('result', d.i32, 'function'), elem: 7 },
1837+
);
1838+
1839+
expect(res).toMatchInlineSnapshot(`
1840+
"{
1841+
{
1842+
result += 7i;
1843+
}
1844+
}"
1845+
`);
1846+
});
1847+
});
17431848
});

0 commit comments

Comments
 (0)