Getting started
Quick start
Install Rayzor, create a Haxe project, and run it with the commands below.
$ curl -fsSL https://rayzor.tech/install.sh | sh
$ rayzor init --name my-app
$ rayzor run src/Main.hx
Compilation modes
Use run during development, aot for a native executable, or build for WebAssembly. The table below covers the main commands.
You want to
Command
What happens
Run code while developing
rayzor run main.hx
Starts executing immediately, then compiles and optimizes frequently called functions
Type-check only
rayzor check main.hx
Parse and type-check, no codegen
Ship a native binary
rayzor aot main.hx -o app
Whole program compiled ahead of time, linked against the runtime
Ship one portable artifact
rayzor bundle main.hx
Serialized MIR in a single .rzb, run later with rayzor run
Target the browser or WASI
rayzor build --target wasm
WebAssembly module; --browser also emits an HTML harness
Tiering
rayzor run starts executing immediately in the MIR interpreter, then compiles functions as their call counts increase. Presets control when functions move to a more optimized tier.
script
CLI tools and short scripts. Instant startup, no tier promotion
application
Desktop apps and web servers. Balanced (default)
server
Long-running services. Optimizes aggressively
benchmark
Performance testing. Compiles immediately and finishes optimizing during warm-up
development
Debugging. Verbose logging
embedded
Constrained environments. Interpreter only
--tier-thresholds <I/W/H[/B]> overrides the preset's thresholds directly.
Memory model
Rayzor has no garbage collector. It uses last-use and escape analysis to insert cleanup at compile time. Analysis starts in HIR, with a MIR pass handling cases that become visible after lowering. You can add ownership annotations to individual classes.
@:move class UniqueResource { ... } // move semantics, no aliasing
@:arc class SharedState { ... } // atomic reference counting
@:derive([Send, Sync]) class Data { ... } // thread-safety markers
@:safety on the Main class selects a program-wide mode: strict requires every class to be annotated, non-strict wraps the rest in Rc. Use-after-move is a hard error for @:move types and a warning otherwise.
Artifacts
.blade
Per-module MIR cache for incremental builds. On by default; --no-cache disables it.
.rzb
Every compiled module in one file. rayzor run app.rzb loads the bundle without compiling it again.
.rpkg
Haxe sources plus optional native libraries. strip reduces it to one platform.
Cache entries depend on source content, the compiler version, and a cache ABI identifier derived from compiler sources. Relinking unchanged compiler sources preserves the cache.
Project manifest
Put your project settings in rayzor.toml at the project root.
rayzor.toml
[project]
name = "my-app"
version = "0.1.0"
entry = "src/Main.hx"
[build]
class-paths = ["src"]
opt-level = 2
preset = "application"
[cache]
enabled = true
To use an existing HXML build, set hxml = "build.hxml" under [project]. A workspace lists members instead.
For native plugins, declare both the class paths and the native libraries. Rayzor needs both to resolve the plugin at runtime.
Macros
Macros run at compile time in Rayzor's interpreter. Modules expand in a fixed order: dependencies first, then by module path. Within a module, expansion follows source order, and @:build and @:autoBuild run in declaration order. This keeps the generated program consistent across builds of the same sources.
The official Haxe compiler types modules lazily, which can change macro execution order when unrelated code changes. When porting macros to Rayzor, keep these differences in mind:
Macro state is shared in compile order
Static variables of a macro class keep their values for the whole compile, as in Haxe. They are updated in the fixed module order above, so a registry filled by several modules' build macros is complete once those modules have expanded. Changing which modules import which can change that order.
Macros that need type information run once
A macro that calls Context.typeof, typeExpr, getType or unify waits until typing reaches its call site. Its first attempt is rolled back, so its side effects happen once.
Context.defineType adds the type to the defining module
The type is compiled with the module whose macro defined it, so it is in scope there and importable from it. Haxe gives a defined type a module of its own.
Generation hooks run once, after typing
Context.onAfterTyping and then Context.onGenerate callbacks run after every module is typed. Each ModuleType they receive exposes the type's name, pack, module and meta; other fields are not filled in yet.
More detail in the repository
See the full CLI reference, compiler pass order, runtime ABI, and file format specifications.
Open docs/ ↗