Skip to content

Commit 7975fd0

Browse files
committed
Better ref restrictions
1 parent 4839547 commit 7975fd0

2 files changed

Lines changed: 126 additions & 5 deletions

File tree

packages/typegpu/src/data/ref.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { $gpuCallable, $internal, $ownSnippet, $resolve } from '../shared/symbol
55
import type { DualFn, SelfResolvable } from '../types.ts';
66
import { UnknownData } from './dataTypes.ts';
77
import { createPtrFromOrigin, explicitFrom } from './ptr.ts';
8-
import { type ResolvedSnippet, snip, type Snippet } from './snippet.ts';
9-
import { isPtr, type Ptr, type StorableData } from './wgslTypes.ts';
8+
import { isAlias, type ResolvedSnippet, snip, type Snippet } from './snippet.ts';
9+
import { isNaturallyEphemeral, isPtr, type Ptr, type StorableData } from './wgslTypes.ts';
1010

1111
// ----------
1212
// Public API
@@ -47,10 +47,30 @@ export const _ref = (() => {
4747
impl.toString = () => 'ref';
4848
impl[$internal] = true;
4949
impl[$gpuCallable] = {
50-
call(_ctx, [value]) {
50+
call(ctx, [value]) {
5151
if (value.origin === 'argument') {
5252
throw new WgslTypeError(
53-
stitch`d.ref(${value}) is illegal, cannot take a reference of an argument. Copy the value locally first, and take a reference of the copy.`,
53+
stitch`d.ref(${value}) is illegal, cannot take a reference of an argument. Copy the value first, and take a reference of the copy.`,
54+
);
55+
}
56+
57+
if (value.origin === 'constant-immutable-def' || value.origin === 'runtime-immutable-def') {
58+
const typeStr = ctx.resolve(value.dataType).value;
59+
throw new WgslTypeError(
60+
stitch`d.ref(${value}) is illegal, cannot take a reference to a constant.
61+
-----
62+
- Try 'd.ref(${typeStr}(${value}));' instead to create a new referencable value.
63+
-----`,
64+
);
65+
}
66+
67+
if (isAlias(value) && isNaturallyEphemeral(value.dataType)) {
68+
const typeStr = ctx.resolve(value.dataType).value;
69+
throw new WgslTypeError(
70+
stitch`d.ref(${value}) is illegal, cannot take a reference to a scalar value.
71+
-----
72+
- Try 'd.ref(${typeStr}(${value}));' instead to create a new referencable scalar.
73+
-----`,
5474
);
5575
}
5676

packages/typegpu/tests/ref.test.ts

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,108 @@ describe('d.ref', () => {
277277
- fn*:main
278278
- fn*:main()
279279
- fn*:foo(vec3f)
280-
- fn:ref: d.ref(hello) is illegal, cannot take a reference of an argument. Copy the value locally first, and take a reference of the copy.]
280+
- fn:ref: d.ref(hello) is illegal, cannot take a reference of an argument. Copy the value first, and take a reference of the copy.]
281+
`);
282+
});
283+
284+
it('fails when taking a reference of an naturally ephemeral local definition', () => {
285+
const myConst = tgpu.const(d.vec2u, d.vec2u());
286+
287+
function modify(n: d.ref<number>) {
288+
'use gpu';
289+
n.$++;
290+
}
291+
292+
function good() {
293+
'use gpu';
294+
const a = d.f32(1);
295+
const aRef = d.ref(d.f32(a)); // we copy the value and create a new referencable value
296+
modify(aRef);
297+
}
298+
299+
function bad() {
300+
'use gpu';
301+
const a = d.f32(1);
302+
const aRef = d.ref(a); // we try to reference a scalar
303+
modify(aRef);
304+
}
305+
306+
expect(() => tgpu.resolve([good])).not.toThrow();
307+
expect(() => tgpu.resolve([bad])).toThrowErrorMatchingInlineSnapshot(`
308+
[Error: Resolution of the following tree failed:
309+
- <root>
310+
- fn*:bad
311+
- fn*:bad()
312+
- fn:ref: d.ref(a) is illegal, cannot take a reference to a scalar value.
313+
-----
314+
- Try 'd.ref(f32(a));' instead to create a new referencable scalar.
315+
-----]
316+
`);
317+
});
318+
319+
it('fails when taking a reference of a tgpu.const', () => {
320+
const myConst = tgpu.const(d.vec2u, d.vec2u());
321+
322+
function modify(n: d.ref<d.v2u>) {
323+
'use gpu';
324+
n.$.x++;
325+
}
326+
327+
function good() {
328+
'use gpu';
329+
const aRef = d.ref(d.vec2u(myConst.$)); // we copy the value and create a new referencable value
330+
modify(aRef);
331+
}
332+
333+
function bad() {
334+
'use gpu';
335+
const aRef = d.ref(myConst.$); // we try to reference a constant
336+
modify(aRef);
337+
}
338+
339+
expect(() => tgpu.resolve([good])).not.toThrow();
340+
expect(() => tgpu.resolve([bad])).toThrowErrorMatchingInlineSnapshot(`
341+
[Error: Resolution of the following tree failed:
342+
- <root>
343+
- fn*:bad
344+
- fn*:bad()
345+
- fn:ref: d.ref(myConst) is illegal, cannot take a reference to a constant.
346+
-----
347+
- Try 'd.ref(vec2u(myConst));' instead to create a new referencable value.
348+
-----]
349+
`);
350+
});
351+
352+
it('fails when taking a reference of an naturally ephemeral piece of tgpu.const', () => {
353+
const myConst = tgpu.const(d.vec2u, d.vec2u());
354+
355+
function modify(n: d.ref<number>) {
356+
'use gpu';
357+
n.$++;
358+
}
359+
360+
function good() {
361+
'use gpu';
362+
const aRef = d.ref(d.u32(myConst.$.x)); // we copy the value and create a new referencable value
363+
modify(aRef);
364+
}
365+
366+
function bad() {
367+
'use gpu';
368+
const aRef = d.ref(myConst.$.x); // we try to reference a constant
369+
modify(aRef);
370+
}
371+
372+
expect(() => tgpu.resolve([good])).not.toThrow();
373+
expect(() => tgpu.resolve([bad])).toThrowErrorMatchingInlineSnapshot(`
374+
[Error: Resolution of the following tree failed:
375+
- <root>
376+
- fn*:bad
377+
- fn*:bad()
378+
- fn:ref: d.ref(myConst.x) is illegal, cannot take a reference to a constant.
379+
-----
380+
- Try 'd.ref(u32(myConst.x));' instead to create a new referencable value.
381+
-----]
281382
`);
282383
});
283384

0 commit comments

Comments
 (0)