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

Prefer 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

LayerMechanismWhat gets removed
1 — Lazy preludeAuto-prelude symbol indexUnused .ny stdlib modules never enter the AST
2 — Micro-modulesSmall files under stdlib/ (e.g. strings/ops.ny)Only referenced symbols pull their defining file
3 — Runtime profileDemand-driven runtime linkingOnly the runtime your program actually uses gets linked
4 — LLVM + Thin LTOopt -O3 then clang -flto=thin on --releaseUncalled 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

FlagEffect
--release-O3, LLVM opt, thin LTO
--opt LEVELClang 0–3
--lto / --lto-fullThin / full LTO
--no-llvm-optSkip opt pass
--pgoAutomated profile-guided optimization — see PGO guide
--pgo-generate / --pgo-useManual Clang PGO (low-level)
--native-cpu-march=native (default on host --release; use --no-native-cpu for portable artifacts)
--no-preludeSkip stdlib auto-prelude — smaller IR/link for single-file programs
--no-native-cpuDisable default native CPU tuning with --release
--verbose / -vPrint 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.