Skip to content

Commit c97e537

Browse files
committed
fix: Choose f32 when trying to find a type common with abstractFloat and i32 (or u32)
1 parent 6d2959c commit c97e537

4 files changed

Lines changed: 153 additions & 4 deletions

File tree

packages/typegpu/src/tgsl/conversion.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { stitch } from '../core/resolve/stitch.ts';
22
import { UnknownData } from '../data/dataTypes.ts';
33
import { undecorate } from '../data/dataTypes.ts';
4+
import { f32 } from '../data/numeric.ts';
45
import { derefSnippet, RefOperator } from '../data/ref.ts';
56
import { schemaCallWrapperGPU } from '../data/schemaCallWrapper.ts';
67
import { snip, type Snippet } from '../data/snippet.ts';
@@ -120,12 +121,18 @@ function getImplicitConversionRank(src: BaseData, dest: BaseData): ConversionRan
120121
}
121122
}
122123

124+
if ((trueSrc.type === 'u32' || trueSrc.type === 'i32') && trueDst.type === 'abstractFloat') {
125+
// When one of the types is a float (abstract or not), we don't want to cast it to a non-float type,
126+
// which would cause it to lose precision. We instead choose the common type to be f32.
127+
return { rank: 1, action: 'cast', targetType: f32 };
128+
}
129+
123130
if (trueSrc.type === 'abstractFloat') {
124131
if (trueDst.type === 'u32') {
125-
return { rank: 2, action: 'cast', targetType: trueDst };
132+
return { rank: 3, action: 'cast', targetType: trueDst };
126133
}
127134
if (trueDst.type === 'i32') {
128-
return { rank: 1, action: 'cast', targetType: trueDst };
135+
return { rank: 2, action: 'cast', targetType: trueDst };
129136
}
130137
}
131138

@@ -167,6 +174,10 @@ function findBestType(
167174
let bestResult: { type: BaseData; details: ConversionRankInfo[]; sum: number } | undefined;
168175

169176
for (const targetType of uniqueTypes) {
177+
/**
178+
* The type we end up converting to. Will be different than `targetType` if `targetType === abstractFloat`
179+
*/
180+
let destType = targetType;
170181
const details: ConversionRankInfo[] = [];
171182
let sum = 0;
172183
for (const sourceType of types) {
@@ -176,9 +187,12 @@ function findBestType(
176187
break;
177188
}
178189
details.push(conversion);
190+
if (conversion.action === 'cast') {
191+
destType = conversion.targetType;
192+
}
179193
}
180194
if (sum < (bestResult?.sum ?? Number.POSITIVE_INFINITY)) {
181-
bestResult = { type: targetType, details, sum };
195+
bestResult = { type: destType, details, sum };
182196
}
183197
}
184198
if (!bestResult) {

packages/typegpu/tests/indent.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ describe('indents', () => {
333333
})((input) => {
334334
'use gpu';
335335
const uniBoid = layout.$.boids;
336-
for (let i = d.u32(); i < std.floor(std.sin(Math.PI / 2)); i++) {
336+
for (let i = d.u32(); i < d.u32(std.sin(Math.PI / 2)); i++) {
337337
const sampled = std.textureSample(layout.$.sampled, layout.$.sampler, d.vec2f(0.5, 0.5), i);
338338
const someVal = std.textureLoad(layout.$.smoothRender, d.vec2i(), 0);
339339
if (someVal.x + sampled.x > 0.5) {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@ describe('convertToCommonType', () => {
260260
expect(result).toBeUndefined();
261261
});
262262

263+
it('chooses abstractFloat over i32', () => {
264+
const result = convertToCommonType(ctx, [snippetI32, snippetAbsFloat]);
265+
expect(result).toBeDefined();
266+
expect(result?.[0]?.dataType.type).toBe('f32');
267+
});
268+
263269
it('respects restrictTo types', () => {
264270
// [abstractInt, i32] -> common type i32
265271
// Restrict to f32: requires cast for i32
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { test } from 'typegpu-testing-utility';
2+
import { expect, vi } from 'vitest';
3+
import tgpu, { d } from 'typegpu';
4+
5+
import { expectSnippetOf } from '../utils/parseResolved.ts';
6+
7+
test('multiplying i32 with a float literal should implicitly convert to an f32', () => {
8+
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
9+
10+
function main() {
11+
'use gpu';
12+
const a = d.i32(1) * 0.001;
13+
const int = d.i32(1);
14+
return int * 0.001;
15+
}
16+
17+
expect(tgpu.resolve([main])).toMatchInlineSnapshot(`
18+
"fn main() -> f32 {
19+
const a = 1e-3f;
20+
const int = 1i;
21+
return (f32(int) * 1e-3f);
22+
}"
23+
`);
24+
25+
expectSnippetOf(() => {
26+
'use gpu';
27+
return d.i32(1) * 0.001;
28+
}).toStrictEqual([0.001, d.f32, 'constant']);
29+
30+
expect(consoleWarnSpy.mock.calls).toMatchInlineSnapshot(`
31+
[
32+
[
33+
"Implicit conversions from [
34+
1: i32
35+
] to f32 are supported, but not recommended.
36+
Consider using explicit conversions instead.",
37+
],
38+
[
39+
"Implicit conversions from [
40+
int: i32
41+
] to f32 are supported, but not recommended.
42+
Consider using explicit conversions instead.",
43+
],
44+
[
45+
"Implicit conversions from [
46+
1: i32
47+
] to f32 are supported, but not recommended.
48+
Consider using explicit conversions instead.",
49+
],
50+
]
51+
`);
52+
});
53+
54+
test('multiplying u32 with a float literal should implicitly convert to an f32', () => {
55+
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
56+
57+
function main() {
58+
'use gpu';
59+
const a = d.u32(10) * 0.001;
60+
const int = d.u32(100);
61+
return int * 0.001;
62+
}
63+
64+
expect(tgpu.resolve([main])).toMatchInlineSnapshot(`
65+
"fn main() -> f32 {
66+
const a = 0.01f;
67+
const int = 100u;
68+
return (f32(int) * 1e-3f);
69+
}"
70+
`);
71+
72+
expectSnippetOf(() => {
73+
'use gpu';
74+
return d.u32(1) * 0.001;
75+
}).toStrictEqual([0.001, d.f32, 'constant']);
76+
77+
expect(consoleWarnSpy.mock.calls).toMatchInlineSnapshot(`
78+
[
79+
[
80+
"Implicit conversions from [
81+
10: u32
82+
] to f32 are supported, but not recommended.
83+
Consider using explicit conversions instead.",
84+
],
85+
[
86+
"Implicit conversions from [
87+
int: u32
88+
] to f32 are supported, but not recommended.
89+
Consider using explicit conversions instead.",
90+
],
91+
[
92+
"Implicit conversions from [
93+
1: u32
94+
] to f32 are supported, but not recommended.
95+
Consider using explicit conversions instead.",
96+
],
97+
]
98+
`);
99+
});
100+
101+
test('multiplying u32 with an i32 should implicitly convert to an i32', () => {
102+
using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
103+
104+
function main() {
105+
'use gpu';
106+
const uint = d.u32(5);
107+
const int = d.i32(3);
108+
return uint * int;
109+
}
110+
111+
expect(tgpu.resolve([main])).toMatchInlineSnapshot(`
112+
"fn main() -> i32 {
113+
const uint = 5u;
114+
const int = 3i;
115+
return (i32(uint) * int);
116+
}"
117+
`);
118+
119+
expect(consoleWarnSpy.mock.calls).toMatchInlineSnapshot(`
120+
[
121+
[
122+
"Implicit conversions from [
123+
uint: u32
124+
] to i32 are supported, but not recommended.
125+
Consider using explicit conversions instead.",
126+
],
127+
]
128+
`);
129+
});

0 commit comments

Comments
 (0)