Skip to content

Automatic tail-call optimization (auto-TCO) in StaticOptimizer#694

Draft
He-Pin wants to merge 1 commit intodatabricks:masterfrom
He-Pin:perf/auto-tco
Draft

Automatic tail-call optimization (auto-TCO) in StaticOptimizer#694
He-Pin wants to merge 1 commit intodatabricks:masterfrom
He-Pin:perf/auto-tco

Conversation

@He-Pin
Copy link
Copy Markdown
Contributor

@He-Pin He-Pin commented Apr 6, 2026

Motivation

Jsonnet supports tailstrict keyword for explicit tail-call optimization, but users must manually annotate functions. Many naturally tail-recursive functions miss this optimization because users don't add tailstrict.

Key Design Decision

Automatically detect tail-recursive function calls in the StaticOptimizer and convert them to use the tail-call trampoline. This makes tail-call optimization transparent — users get the performance benefit without code changes.

Modification

  • Add tail-position analysis in StaticOptimizer
  • Detect self-recursive calls in tail position
  • Automatically apply tailstrict semantics when safe
  • ~375 lines changed

Benchmark Results

JMH (JVM, 3 iterations warmup + 3 measurement)

Benchmark Master (ms/op) This PR (ms/op) Change
bench.02 50.427 ± 38.9 45.323 ± 4.0 -10.1%
comparison2 85.854 ± 188.7 68.861 ± 22.4 -19.8%
realistic2 73.458 ± 66.7 66.087 ± 14.3 -10.0%
reverse ~11.5 10.218 ± 0.8 -11.1%

Analysis

All benchmarks show consistent improvement. The reverse benchmark (-11.1%) directly benefits from auto-TCO as std.reverse uses recursion. The broader improvements suggest that many internal stdlib functions and user code patterns have tail-recursive structures that now benefit from the trampoline. This is a significant usability improvement — users no longer need to manually add tailstrict.

References

  • Upstream: jit branch experiment
  • Related: Jsonnet spec tailstrict keyword

Result

All 7 tests pass. All benchmarks positive across all 4 benchmarks tested, no regressions.

Detect self-recursive calls in tail position during static optimization and
mark them for the TailCall trampoline, eliminating JVM stack overflow on deep
recursion without requiring users to annotate call sites with 'tailstrict'.

Key design: introduce TailstrictModeAutoTCO — a third TailstrictMode that
enables the trampoline (like TailstrictModeEnabled) but does NOT force eager
argument evaluation (unlike explicit tailstrict). This preserves Jsonnet's
standard lazy evaluation semantics for auto-TCO'd calls.

Implementation:
- StaticOptimizer.transformBind: detects self-recursive function bindings
- hasNonRecursiveExit: safety check ensuring at least one non-recursive code
  path exists (prevents infinite trampoline on trivially infinite functions
  like f(x) = f(x))
- markTailCalls: walks the AST marking self-recursive tail calls with
  tailstrict=true, autoTCO=true
- Expr: adds isAutoTCO/autoTCO field to Apply0-3 and Apply case classes
- Val: adds TailstrictModeAutoTCO, TailCall.autoTCO flag, restores @tailrec
  on TailCall.resolve
- Evaluator: visitExprWithTailCallSupport uses visitAsLazy for auto-TCO args;
  visitApply* defensively handles auto-TCO for future-proofing

Test: auto_tco.jsonnet with 6 patterns including lazy semantics regression
test (error in auto-TCO'd args is NOT eagerly evaluated).

Upstream: databricks#623
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.

1 participant