Skip to content

feat: Early return pruning#2711

Open
iwoplaza wants to merge 1 commit into
mainfrom
feat/early-return-pruning
Open

feat: Early return pruning#2711
iwoplaza wants to merge 1 commit into
mainfrom
feat/early-return-pruning

Conversation

@iwoplaza

@iwoplaza iwoplaza commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Improvements:

  • Early return in comptime
  • Cosmetic improvements in generated code

@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown

pkg.pr.new

packages
Ready to be installed by your favorite package manager ⬇️

https://pkg.pr.new/software-mansion/TypeGPU/eslint-plugin-typegpu@89c55242bb66ec054737f3197c2599b03ceea31a
https://pkg.pr.new/software-mansion/TypeGPU/tgpu-gen@89c55242bb66ec054737f3197c2599b03ceea31a
https://pkg.pr.new/software-mansion/TypeGPU/tinyest-for-wgsl@89c55242bb66ec054737f3197c2599b03ceea31a
https://pkg.pr.new/software-mansion/TypeGPU/typegpu@89c55242bb66ec054737f3197c2599b03ceea31a
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/cli@89c55242bb66ec054737f3197c2599b03ceea31a
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/color@89c55242bb66ec054737f3197c2599b03ceea31a
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/gl@89c55242bb66ec054737f3197c2599b03ceea31a
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/noise@89c55242bb66ec054737f3197c2599b03ceea31a
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/radiance-cascades@89c55242bb66ec054737f3197c2599b03ceea31a
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/react@89c55242bb66ec054737f3197c2599b03ceea31a
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/sdf@89c55242bb66ec054737f3197c2599b03ceea31a
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/three@89c55242bb66ec054737f3197c2599b03ceea31a
https://pkg.pr.new/software-mansion/TypeGPU/unplugin-typegpu@89c55242bb66ec054737f3197c2599b03ceea31a

benchmark
view benchmark

commit
view commit

@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown

Bundle size comparison (import * as ... in PR vs import * as ... in target):

🟢 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 ($${\color{green}-3.2\%}$$)
tgpu_initFromDevice.ts 259.62 kB ($${\color{green}-3.4\%}$$)
tgpu_resolve.ts 165.83 kB ($${\color{green}-38.3\%}$$)
tgpu_resolveWithContext.ts 165.76 kB ($${\color{green}-38.3\%}$$)
tgpu_bindGroupLayout.ts 68.97 kB ($${\color{green}-74.3\%}$$)
tgpu_mutableAccessor.ts 65.97 kB ($${\color{green}-75.4\%}$$)
tgpu_accessor.ts 65.96 kB ($${\color{green}-75.4\%}$$)
tgpu_privateVar.ts 65.31 kB ($${\color{green}-75.7\%}$$)
tgpu_workgroupVar.ts 65.30 kB ($${\color{green}-75.7\%}$$)
tgpu_const.ts 64.55 kB ($${\color{green}-76.0\%}$$)
tgpu_fn.ts 38.14 kB ($${\color{green}-85.8\%}$$)
tgpu_fragmentFn.ts 38.14 kB ($${\color{green}-85.8\%}$$)
tgpu_vertexFn.ts 37.96 kB ($${\color{green}-85.9\%}$$)
tgpu_computeFn.ts 37.66 kB ($${\color{green}-86.0\%}$$)
tgpu_vertexLayout.ts 26.79 kB ($${\color{green}-90.0\%}$$)
tgpu_comptime.ts 14.48 kB ($${\color{green}-94.6\%}$$)
tgpu_unroll.ts 1.66 kB ($${\color{green}-99.4\%}$$)
tgpu_slot.ts 1.54 kB ($${\color{green}-99.4\%}$$)
tgpu_lazy.ts 1.19 kB ($${\color{green}-99.6\%}$$)

If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.

@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown

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]
Loading
---
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]
Loading
---
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]
Loading

@iwoplaza
iwoplaza force-pushed the feat/early-return-pruning branch 11 times, most recently from f395a3e to d0fdeb4 Compare July 16, 2026 15:59
@iwoplaza
iwoplaza marked this pull request as ready for review July 16, 2026 16:01
Copilot AI review requested due to automatic review settings July 16, 2026 16:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 blockDepth to the generation context to support “safe” cosmetic pruning (e.g., redundant return;).
  • 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.

Comment thread packages/typegpu/src/tgsl/wgslGenerator.ts
@iwoplaza
iwoplaza force-pushed the feat/early-return-pruning branch from d0fdeb4 to ce42040 Compare July 16, 2026 16:35
@pullfrog

pullfrog Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

@author — I finished reviewing PR #2711. I found a correctness regression in the early-return pruning logic, so I’m requesting changes.

Bug

In packages/typegpu/src/tgsl/wgslGenerator.ts, _block strips a trailing return; and then handles an empty body like this:

if (body === '') {
  return { code: '', controlFlow: undefined };
}

If an inlineable block’s only executable statement is return;, the regex removes it and the block becomes empty. This early-return path then discards the controlFlow value, so the parent block keeps emitting statements that should be unreachable after the return. Example that currently miscompiles:

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 out.$ = 2i;.

Suggested fix

Preserve 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

  • The snapshot churn is large; adding targeted tests for return-pruning edge cases would make the correctness guarantees much clearer.
  • The magic depth thresholds (blockDepth <= 3 / <= 2) work because of how function scopes are currently nested, but a named helper or comment explaining why nested branches/loops are safe would improve maintainability.

Please apply the fix, add a regression test, and re-request review.

Pullfrog  | View workflow run | via Pullfrog | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

@iwoplaza
iwoplaza force-pushed the feat/early-return-pruning branch from ce42040 to 637d800 Compare July 16, 2026 16:45

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Pullfrog  | Fix it ➔View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Pullfrog  | View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

@iwoplaza
iwoplaza force-pushed the feat/early-return-pruning branch from 637d800 to 89c5524 Compare July 16, 2026 17:19

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 encounteredwgslGenerator.ts now propagates the resolved block's controlFlow from the unrolled loop and breaks the iteration loop.
  • Added a regression test in unroll.test.ts that verifies an early return inside a tgpu.unroll stops further iterations and prunes the trailing unreachable return.

Targeted tests pass: 2 test files / 122 tests.

Pullfrog  | Fix it ➔View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

}

if (willInline) {
return { code: body.trim().replaceAll('\n ', '\n'), controlFlow };

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return { code: body.trim().replaceAll('\n ', '\n'), controlFlow };
return { code: body.trim().replaceAll(`\n${INDENT[1]}`, '\n'), controlFlow };

I case we change INDENT array.

@cieplypolar cieplypolar left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants