feat: Early return pruning#2711
Conversation
|
pkg.pr.new packages benchmark commit |
Bundle size comparison (
|
| 🟢 Decreased | ➖ Unchanged | 🔴 Increased (max 0.46%) | ❔ Unknown |
|---|---|---|---|
| 0 | 301 | 21 | 0 |
import { ... } in PR vs import * as ... in PR (is the library tree-Shakeable?):
| Test | tsdown |
|---|---|
| tgpu_init.ts | 260.13 kB ( |
| tgpu_initFromDevice.ts | 259.62 kB ( |
| tgpu_resolve.ts | 165.83 kB ( |
| tgpu_resolveWithContext.ts | 165.76 kB ( |
| tgpu_bindGroupLayout.ts | 68.97 kB ( |
| tgpu_mutableAccessor.ts | 65.97 kB ( |
| tgpu_accessor.ts | 65.96 kB ( |
| tgpu_privateVar.ts | 65.31 kB ( |
| tgpu_workgroupVar.ts | 65.30 kB ( |
| tgpu_const.ts | 64.55 kB ( |
| tgpu_fn.ts | 38.14 kB ( |
| tgpu_fragmentFn.ts | 38.14 kB ( |
| tgpu_vertexFn.ts | 37.96 kB ( |
| tgpu_computeFn.ts | 37.66 kB ( |
| tgpu_vertexLayout.ts | 26.79 kB ( |
| tgpu_comptime.ts | 14.48 kB ( |
| tgpu_unroll.ts | 1.66 kB ( |
| tgpu_slot.ts | 1.54 kB ( |
| tgpu_lazy.ts | 1.19 kB ( |
If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.
Resolution Time Benchmark---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Random Branching (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [1.04, 2.01, 4.56, 6.97, 8.16, 13.48, 23.86, 23.65]
line [0.93, 2.00, 4.34, 6.20, 7.89, 12.78, 23.12, 26.22]
line [0.92, 1.94, 4.18, 6.42, 7.53, 12.46, 22.04, 24.30]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Linear Recursion (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.33, 0.53, 0.70, 0.85, 1.18, 1.28, 1.47, 1.65]
line [0.32, 0.52, 0.72, 0.91, 1.20, 1.29, 1.46, 1.64]
line [0.35, 0.56, 0.69, 0.86, 1.22, 1.28, 1.49, 1.60]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Full Tree (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.87, 2.20, 3.92, 6.96, 13.61, 27.34, 58.17, 116.64]
line [0.83, 2.27, 4.72, 7.43, 13.00, 26.72, 55.55, 113.65]
line [0.87, 2.10, 4.20, 6.50, 13.09, 26.72, 58.11, 118.23]
|
f395a3e to
d0fdeb4
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates the WGSL/GLSL code generators to inline trivially-scoped blocks, track simple control-flow, and prune unreachable statements—resulting in cleaner generated shader code and enabling early-return pruning for compile-time-known branches.
Changes:
- Introduces
ResolvedStatement+ block inlining/pruning logic in the WGSL generator (with control-flow tracking). - Adds
blockDepthto the generation context to support “safe” cosmetic pruning (e.g., redundantreturn;). - Updates a broad set of snapshots/docs to match the new, cleaner generated output (including unroll labeling and block formatting).
Reviewed changes
Copilot reviewed 37 out of 37 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/typegpu/src/tgsl/wgslGenerator.ts | Implements block inlining + control-flow-aware pruning via ResolvedStatement, and updates statement/block generation accordingly. |
| packages/typegpu/src/tgsl/generationHelpers.ts | Extends GenerationCtx with blockDepth used by generator heuristics. |
| packages/typegpu/src/resolutionCtx.ts | Implements blockDepth on the resolution/generation context stack. |
| packages/typegpu-gl/src/glslGenerator.ts | Adapts GLSL generator to new _block(...): ResolvedStatement API and empty-body handling. |
| packages/typegpu-gl/tests/glslGenerator.test.ts | Updates snapshots to match new inlined-block output. |
| packages/typegpu/tests/vector.test.ts | Updates snapshots for simplified return formatting (no redundant braces). |
| packages/typegpu/tests/unroll.test.ts | Updates snapshots for unrolled-loop formatting + new // --- separators. |
| packages/typegpu/tests/tgslFn.test.ts | Updates snapshots for empty function body formatting (fn x() {} style). |
| packages/typegpu/tests/tgsl/wgslGenerator.test.ts | Updates snapshots across many cases to reflect block inlining and pruning behavior. |
| packages/typegpu/tests/tgsl/typeInference.test.ts | Updates snapshots for empty functions ({}) and removed redundant return;. |
| packages/typegpu/tests/tgsl/ternaryOperator.test.ts | Updates snapshot formatting for empty function output. |
| packages/typegpu/tests/tgsl/extensionEnabled.test.ts | Updates snapshots reflecting inlined returns in generated WGSL. |
| packages/typegpu/tests/tgsl/entryFnParamPruning.test.ts | Updates snapshots reflecting inlined single-statement blocks in entry functions. |
| packages/typegpu/tests/struct.test.ts | Updates snapshots removing redundant return; and simplifying empty helper bodies. |
| packages/typegpu/tests/std/environment.test.ts | Updates snapshots reflecting inlined returns. |
| packages/typegpu/tests/slot.test.ts | Updates snapshots reflecting inlined single-statement blocks. |
| packages/typegpu/tests/pipelineInit.test.ts | Updates snapshots for empty compute entry formatting (fn ...() {}) and helper formatting. |
| packages/typegpu/tests/mutabilityTracking.test.ts | Updates snapshots reflecting inlined unrolled blocks and simplified empty loops. |
| packages/typegpu/tests/lazy.test.ts | Updates snapshots for empty overload bodies (fn fill(...) {}). |
| packages/typegpu/tests/computePipeline.test.ts | Updates snapshots for empty pipeline entry formatting and inlined assignments. |
| packages/typegpu/tests/array.test.ts | Updates snapshots removing redundant trailing return;. |
| apps/typegpu-docs/tests/individual-example-tests/*.test.ts | Updates example snapshots to match new unroll labeling, block inlining, and // --- separators. |
| apps/typegpu-docs/src/content/docs/apis/utils.mdx | Updates documentation examples to match the new generated formatting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
d0fdeb4 to
ce42040
Compare
|
@author — I finished reviewing PR #2711. I found a correctness regression in the early-return pruning logic, so I’m requesting changes. BugIn if (body === '') {
return { code: '', controlFlow: undefined };
}If an inlineable block’s only executable statement is const f = tgpu.fn([d.ptrFn(d.i32)])((out) => {
'use gpu';
{
return;
}
out.$ = 2; // incorrectly preserved
});It should produce an empty/equivalent function; instead it emits Suggested fixPreserve the terminator even when the body is empty: if (body === '') {
return { code: '', controlFlow };
}I verified this one-line change fixes the regression and the existing unit-test suite (167 files / 2190 tests) still passes. Other notes
Please apply the fix, add a regression test, and re-request review.
|
ce42040 to
637d800
Compare
There was a problem hiding this comment.
I found a correctness regression: an inlineable block whose only statement is return; loses its terminator when the body is pruned to empty, so unreachable code after it is wrongly emitted. Details inline.
I also left a maintainability note about the magic blockDepth thresholds.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Latest commit fixes the empty-inline-block regression I called out (preserving controlFlow when the pruned body is empty) and adds regression tests for return/break/continue pruning. The full relevant test suite passes (166 files / 2189 tests). I still think the blockDepth <= 2 / <= 3 thresholds would benefit from a clearer explanation, but that’s non-blocking.
Approving.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
637d800 to
89c5524
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — since the last review, the author squashed the branch to a single commit that adds early-return termination to unrolled loops.
- Stopped generating remaining unrolled iterations once a control-flow terminator is encountered —
wgslGenerator.tsnow propagates the resolved block'scontrolFlowfrom the unrolled loop and breaks the iteration loop. - Added a regression test in
unroll.test.tsthat verifies an earlyreturninside atgpu.unrollstops further iterations and prunes the trailing unreachablereturn.
Targeted tests pass: 2 test files / 122 tests.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
| } | ||
|
|
||
| if (willInline) { | ||
| return { code: body.trim().replaceAll('\n ', '\n'), controlFlow }; |
There was a problem hiding this comment.
| return { code: body.trim().replaceAll('\n ', '\n'), controlFlow }; | |
| return { code: body.trim().replaceAll(`\n${INDENT[1]}`, '\n'), controlFlow }; |
I case we change INDENT array.

Improvements: