Performance toolchain
LLVM opt, release flags, LTO, PGO, and benchmarks.
Pipeline
.ny → expand → monomorph → typecheck → LLVM IR → opt -O3 → clang [-flto=thin] + rt/*.c (demand-driven)
Monomorphization & dead code elimination
Nyra is designed so you get a batteries-included stdlib but pay only for what you call. LLVM optimizes best when dispatch is static and symbols are small; the compiler and stdlib layout support that at every layer.
Static dispatch (monomorphization)
Generic functions and types are specialized at compile time before LLVM IR is emitted. A call like id(42) on fn id<T>(x: T) -> T becomes a direct call to id__i32 — no runtime type information, no vtable on the hot path.
// expand → monomorph → codegen
fn id<T>(x: T){ return x }
let a = id(1) // → call @id__i32
let b = id("x") // → call @id__string// expand → monomorph → codegen
fn id<T>(x: T){ return x }
let a = id<i32>(1) // → call @id__i32
let b = id<string>("x") // → call @id__stringPrefer generics and impl Trait for Type on hot paths. Reserve dyn Trait (vtable + indirect call) for plugins, heterogeneous collections, or other cases that truly need runtime polymorphism. Math helpers such as abs_i32 lower to LLVM intrinsics and are not emitted as separate functions.
Pipeline order: expand → monomorph (+ call-site type inference) → typecheck → borrow → escape analysis → codegen. See Generics and Traits & macros.
Four layers of DCE
| Layer | Mechanism | What gets removed |
|---|---|---|
| 1 — Lazy prelude | Auto-prelude symbol index | Unused .ny stdlib modules never enter the AST |
| 2 — Micro-modules | Small files under stdlib/ (e.g. strings/ops.ny) | Only referenced symbols pull their defining file |
| 3 — Runtime profile | Demand-driven runtime linking | Only the runtime your program actually uses gets linked |
| 4 — LLVM + Thin LTO | opt -O3 then clang -flto=thin on --release | Uncalled functions inlined away or dropped |
Example: one stdlib call
main.ny
str.trim() // lazy prelude → strings/ops.ny only
→ runtime profile // only the string runtime you use is linked
→ Thin LTO // unused string helpers dropped if uncalled
Opt out of auto-prelude for embedded or single-file builds: # no_std, nyra build --no-prelude. Example: examples/toolchain/lazy_prelude.ny, examples/toolchain/monomorph_static_dispatch.ny.
Stdlib layout rules: stdlib/README.md · project guidelines: one function per focused file where practical so the linker (especially with Thin LTO) can strip dead symbols.
benchmark { }
Built-in statement — no import. Wraps a block and prints wall time, RSS delta, and process CPU usage when the block finishes.
benchmark {
run()
}
Example output:
Time: 14.2 ms
Memory: 1.8 MB
CPU: 38%
For iteration loops use stdlib/bench/mod.ny; for labeled timers use time_start / mem_start. See examples/benchmark_block.ny.
CLI flags
| Flag | Effect |
|---|---|
--release | -O3, LLVM opt, thin LTO |
--opt LEVEL | Clang 0–3 |
--lto / --lto-full | Thin / full LTO |
--no-llvm-opt | Skip opt pass |
--pgo | Automated profile-guided optimization — see PGO guide |
--pgo-generate / --pgo-use | Manual Clang PGO (low-level) |
--native-cpu | -march=native (default on host --release; use --no-native-cpu for portable artifacts) |
--no-prelude | Skip stdlib auto-prelude — smaller IR/link for single-file programs |
--no-native-cpu | Disable default native CPU tuning with --release |
--verbose / -v | Print escape-analysis report — see Escape analysis |
nyra build --release examples/syntax/math.ny -o bench
nyra build --release --no-prelude examples/toolchain/no_prelude.ny
nyra build --pgo examples/comparison/cpu_bound/bench.ny --no-prelude
nyra build --verbose . # stack promotion, LocalChannel, no_escape
./scripts/bench.sh
./scripts/bench.sh uses --release --no-prelude for Nyra comparison binaries. PGO on comparison benchmarks also trains examples/comparison/pgo_training/*.ny (branch/mod diversity). Constant positive % in hot loops lowers to LLVM urem when the dividend is proven non-negative (including parenthesized sums like (acc + term) % 997 and loop-carried accumulators initialized from zero). Static print("…") uses puts instead of printf.
Full PGO walkthrough: Profile-Guided Optimization (PGO). Stack/lock optimizations: Escape analysis.