This guide focuses on practical performance work for Reflaxe.Elixir’s AST pipeline: fast feedback in development, predictable output in CI, and a clear strategy for diagnosing slow builds.
Note
This is an advanced guide. Command blocks are for local investigation and are not CI-smoked unless explicitly stated.
Start with Haxe’s built‑in timings:
haxe build.hxml --timesIf you’re investigating macro‑time work specifically, enable macro timings:
haxe build.hxml -D macro-times --times- Full pass set enabled
- Highest output quality and hygiene
fast_boot is an opt‑in profile designed to speed up iteration on large modules by skipping
or simplifying selected expensive macro/transform work.
- Enable in Haxe:
-D fast_boot - Enable in Mix (this repo’s convention):
HAXE_FAST_BOOT=1 mix compile
Details and tradeoffs are documented in:
docs/01-getting-started/development-workflow.mddocs/06-guides/KNOWN_LIMITATIONS.md
When output shape changes unexpectedly or compilation work spikes:
-D debug_pass_metrics— prints which passes changed the AST-D debug_ast_pipeline/-D debug_ast_transformer— focused traces for pipeline stages
These are intended for contributor workflows; keep them off by default.
Do not use -D analyzer-optimize for Elixir output. It can destroy functional/idiomatic shapes
and makes downstream transforms harder. See docs/01-getting-started/compiler-flags-guide.md.
For Phoenix projects, prefer incremental compilation to avoid recompiling everything on each change:
- The Mix tasks integrate a background Haxe server when available.
- Phoenix watchers use
haxe ... --wait <port>for client builds.
If you hit EADDRINUSE from --wait, prefer reusing/adjusting the wait port (see the todo‑app’s
config/dev.exs) rather than disabling watching entirely.
If you’re touching transformer passes:
- Prefer single‑pass analyzers (
VarUse/symbol tables) over repeated tree scans. - Avoid O(n²) “fix‑it” passes that re‑walk the full AST multiple times.
- Gate expensive cosmetic hygiene behind profiles (
fast_boot,disable_hygiene_final) only when semantics remain correct.
If you find a pass that must exist but is too expensive, the fix is algorithmic (data structures, single‑pass analysis), not arbitrary limits.
To guard against “it got slow” / “output order changed” regressions without flakiness, CI runs a budget check on the todo‑app’s server + client builds:
npm run ci:budgetsThis script:
- Builds the todo‑app server and client twice and diffs the outputs (determinism).
- Enforces generous per‑build timeouts via
scripts/with-timeout.sh(no tight wall‑time asserts).
You can override the timeouts locally:
SERVER_TIMEOUT_SECS=240 CLIENT_TIMEOUT_SECS=180 npm run ci:budgets