Skip to content

compiler: add Enum.reduce_while/3 for integer range compilation#114

Merged
yevbar merged 1 commit intomasterfrom
happy/reduce-while-ranges
Mar 1, 2026
Merged

compiler: add Enum.reduce_while/3 for integer range compilation#114
yevbar merged 1 commit intomasterfrom
happy/reduce-while-ranges

Conversation

@yevbar
Copy link
Copy Markdown
Contributor

@yevbar yevbar commented Mar 1, 2026

Summary

Add compilation of Enum.reduce_while/3 over integer ranges to efficient tail-recursive WASM loops with early termination.

Motivation

Enum.reduce_while/3 is a common Elixir pattern for reductions that may terminate early based on a condition. Previously, this function wasn't supported in the compilable subset, forcing users to write manual recursive functions for early-exit accumulation patterns.

Implementation

The reducer function body uses {:halt, val} and {:cont, val} to signal early termination or continuation. These are transformed at the AST level before IR compilation:

  • {:halt, val} → return val immediately (loop exit)
  • {:cont, val} → recursive call to helper with val as new accumulator (loop continue)

Free variables from the outer function scope (e.g., function parameters like threshold referenced inside the reducer) are automatically detected and threaded through as extra helper function parameters.

Supported patterns

  • Plain ranges: Enum.reduce_while(1..n, init, fn i, acc -> ... end)
  • Stepped ranges: Enum.reduce_while(0..n//2, init, fn i, acc -> ... end)
  • Negative steps: Enum.reduce_while(n..1//-1, init, fn i, acc -> ... end)
  • Dynamic steps: Enum.reduce_while(a..b//s, init, fn i, acc -> ... end)
  • Block bodies with let bindings
  • Nested if/case/cond with halt/cont in any branch
  • Closures over outer function parameters

Example

# Sum numbers until the sum exceeds 100
Enum.reduce_while(1..1000, 0, fn i, acc ->
  new_acc = acc + i
  if new_acc > 100, do: {:halt, new_acc}, else: {:cont, new_acc}
end)

Compiles to a tail-recursive helper that TCO converts to a WASM loop with br_table exit.

Tests

11 new tests covering:

  • Basic reduce_while with halt conditions
  • Range exhaustion (never halts)
  • Immediate halt
  • Accumulator transformations
  • Stepped ranges (positive, negative)
  • End-to-end WASM execution with Wasmex
  • IR generation verification
  • Optimization pass compatibility
  • Block bodies with let bindings

Add compilation of Enum.reduce_while/3 over integer ranges (plain and
stepped) to efficient tail-recursive WASM loops with early termination.

The reducer function body containing {:halt, val} and {:cont, val}
is transformed at the AST level before IR compilation:
- {:halt, val} → return val immediately (no recursion)
- {:cont, val} → recursive call to helper with val as new accumulator

Free variables from the outer function scope (e.g., function parameters
referenced inside the reducer) are automatically detected and threaded
through as extra helper function parameters.

Supports:
- Plain ranges: Enum.reduce_while(1..n, init, fn i, acc -> ... end)
- Stepped ranges: Enum.reduce_while(0..n//2, init, fn i, acc -> ... end)
- Negative steps: Enum.reduce_while(n..1//-1, init, fn i, acc -> ... end)
- Dynamic steps: Enum.reduce_while(a..b//s, init, fn i, acc -> ... end)
- Block bodies with let bindings
- Nested if/case/cond with halt/cont in any branch
- Closures over outer function parameters

The generated helper function benefits from existing optimization passes:
TCO converts it to a loop, constant propagation simplifies invariants,
and strength reduction optimizes the arithmetic.
@yevbar yevbar merged commit f4b1cdc into master Mar 1, 2026
4 checks passed
@yevbar yevbar deleted the happy/reduce-while-ranges branch March 1, 2026 07:57
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