software-mansion / software-mansion/TypeGPU

std.intdiv with two runtime f32 operands emits unsigned division, so negative inputs silently return 0 on GPU

Open
#2,892 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
3.2k
Forks
122
Avg merge
3d 5h
Merged PRs (30d)
34

Description

What's wrong

When std.intdiv is called with two runtime floating-point values, the generated shader casts both operands to u32 (an unsigned integer) before dividing. Negative inputs cannot be represented as unsigned, so they saturate to 0 and the division quietly returns the wrong answer. The CPU implementation handles the same inputs correctly, so the two backends disagree and nothing warns about it: std.intdiv(-5.9, 2.1) is -2 on the CPU and 0 on the GPU.

Where

At HEAD (186a026):

packages/typegpu/src/std/numeric.ts lines 1215-1239. The signature unifies the operands against [u32, i32] and the CPU path is sign-preserving:
https://github.com/software-mansion/TypeGPU/blob/186a026c36ac55268377dac3bb233c19c0410f06/packages/typegpu/src/std/numeric.ts#L1215-L1239

function cpuIntdiv(lhs: number, rhs: number): number {
  ...
  return Math.trunc(Math.trunc(lhs) / Math.trunc(rhs));
}
...
signature: (lhs, rhs) => {
  const unified = unify([lhs, rhs], [u32, i32]);

packages/typegpu/src/tgsl/conversion.ts lines 103-200 is why u32 wins. For an f32 source, primitivePreference plus the rank formula at line 123 gives i32 and u32 identical rank (20 each, sum 40 vs 40). The tie-break at line 199 uses strict <, so the first candidate in the list wins, and the list is [u32, i32] with u32 first:
https://github.com/software-mansion/TypeGPU/blob/186a026c36ac55268377dac3bb233c19c0410f06/packages/typegpu/src/tgsl/conversion.ts#L103-L200

How it manifests

  1. Define a GPU function with two runtime f32 parameters that returns std.intdiv(a, b).
  2. Resolve it. The emitted WGSL is:
fn foo(a: f32, b: f32) -> u32 {
  return (u32(a) / u32(b));
}
  1. With a = -5.9, b = 2.1: CPU returns -2, but in WGSL u32(-5.9) saturates to 0, so the GPU returns 0.

Reproduced against typegpu 0.12.1 with unplugin-typegpu 0.12.2 in a production build; no warning is emitted (the implicit-conversion log category is dev-only, and even in dev it doesn't say a signed value became unsigned).

Expected behavior

Float operands should unify to i32, matching the sign-preserving CPU semantics and the doc comment ("Coerces both arguments to integers"), which says nothing about unsignedness. The repo's own float test (intdiv coerces float arguments to integers in packages/typegpu/tests/std/numeric/intdiv.test.ts) resolves intdiv(d.f32(5.5), 2) to i32, because the mixed f32 + abstractInt case isn't a tie. The all-float case appears to be an unintended tie-break artifact. Reordering the candidates to [i32, u32], or preferring signed targets for float sources, would fix it; a regression test with two runtime f32 operands and a negative value would lock it in.

For context, this seems adjacent to #2293 (integer division generating f32 division), which std.intdiv was added to address.

Found while running Ito (AI code review, free for open source) against recently merged PRs. Full analysis: https://app.ito.ai/share/ad79650a-6683-4f6b-9241-e1d9ccb0070e.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read packages/typegpu/src/std/numeric.ts and packages/typegpu/src/tgsl/conversion.ts, focusing on std.intdiv's operand unification and the signed-versus-unsigned tie-break. Run packages/typegpu/tests/std/numeric/intdiv.test.ts, then add coverage for two runtime f32 operands with a negative value; done means the generated shader and CPU path preserve the expected signed result.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers, computer-graphics
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.