@@ -4,11 +4,6 @@ sidebar_position: 4
44
55import SpecLink from " @site/src/components/SpecLink" ;
66import SchemaExample from " @site/src/components/SchemaExample" ;
7- import { TraceExample } from " @theme/ProgramExample" ;
8- import {
9- tailRecursiveSum ,
10- tailRecursiveFactorial ,
11- } from " ./tracing-examples" ;
127
138# Tracing execution
149
@@ -178,44 +173,14 @@ instead of (or alongside) a reason pointer:
178173} ` }
179174</SchemaExample >
180175
181- ## Tail-call optimization
182-
183- The recursion examples above push a new frame for every call — step
184- through them and watch the call stack grow. A compiler can often avoid
185- that. When a recursive call sits in ** tail position** — its result is
186- returned directly, with no further work after it — the compiler can
187- reuse the current frame instead of pushing a new one. This is
188- ** tail-call optimization** (TCO), and it turns recursion into a loop.
189-
190- The two programs below are written so bugc's optimizer folds them. Each
191- accumulates its result in an ` acc ` parameter and hands it to the next
192- call in tail position, so no work is left pending on the stack.
193-
194- Use the ** Opt** selector in the trace drawer to set the optimization
195- level. Compile at ** O0** (no optimization) and again at ** O2**
196- (optimizations on, including TCO), then step through and compare the
197- call stack. TCO kicks in at ** O2** .
198-
199- <TraceExample
200- title = " Tail-recursive sum"
201- description = " Sums 1..5 with an accumulator; TCO folds it into a loop"
202- source = { tailRecursiveSum }
203- />
204-
205- <TraceExample
206- title = " Tail-recursive factorial"
207- description = " Computes 5! with an accumulator; TCO folds it into a loop"
208- source = { tailRecursiveFactorial }
209- />
210-
211- At ** O0** , each ` sum ` /` fact ` call is a real invoke/return pair and the
212- call stack grows one frame per iteration. At ** O2** , the recursive
213- call becomes a ** back-edge** : a single JUMP that ends one iteration and
214- begins the next without pushing a frame. The call stack stays flat.
215-
216- That one JUMP carries three facts at once, composed as sibling keys on a
217- single context — the flat form described on the
218- [ transform context] ( /spec/program/context/transform ) page:
176+ ## Optimized code: the tailcall transform
177+
178+ When the compiler optimizes tail-recursive calls, it turns the recursion
179+ into a loop: the recursive call becomes a ** back-edge** — a single JUMP that
180+ ends one iteration and begins the next without pushing a frame. That one
181+ JUMP carries three facts at once, composed as sibling keys on a single
182+ context (the flat form described on the
183+ [ transform context] ( /spec/program/context/transform ) page):
219184
220185<SchemaExample
221186 schema = " program/context/transform"
@@ -239,14 +204,17 @@ single context — the flat form described on the
239204
240205The ` return ` and ` invoke ` state the source-level facts — the previous
241206iteration returned, the next was invoked — and ` transform: ["tailcall"] `
242- explains how the compiler realized that pair as one JUMP. Because no
243- value crosses a frame boundary here, the ` return ` carries no ` data ` and
244- the ` invoke ` no ` arguments ` : the accumulator is threaded through the
245- loop directly, and the invoke ` target ` points at the loop header the
246- JUMP re-enters. A debugger that ignores the transform still reads a
247- coherent invoke/return sequence; one that understands it can show that
248- the call stack isn't really growing, and present the recursion as the
249- loop it compiled to.
207+ explains how the compiler realized that pair as one JUMP. Because no value
208+ crosses a frame boundary here, the ` return ` carries no ` data ` and the
209+ ` invoke ` no ` arguments ` : the accumulator is threaded through the loop
210+ directly, and the invoke ` target ` points at the loop header the JUMP
211+ re-enters. A debugger that ignores the transform still reads a coherent
212+ invoke/return sequence; one that understands it can show that the call stack
213+ isn't really growing.
214+
215+ To watch this happen — flipping the optimizer between ** O0** and ** O2** and
216+ stepping through the back-edge — see the tail-call optimization examples in
217+ the [ Trace playground] ( /docs/explore/trace-playground ) .
250218
251219## Trace data structure
252220
0 commit comments