Vire is a programming language: as light as Python, as fast as C/Rust, memory-safe — without lifetimes, without ownership syntax, without manual memory management. It compiles AOT to native binaries through a whole-program solver and an LLVM backend, and runs (for the provable majority) without a runtime.
Name from the Latin vīrēs ("forces, strength") — light, yet powerful. File extension
.vr. Current state: language specified; front-end, solver and backend built, compiling.vrto native binaries and benchmarked against Rust/C++/gcc.
fn word_counts(text: Str) -> Map[Str, Int] {
mut counts = {}
for word in text.lower().split_whitespace() {
counts[word] = counts.get(word).or(0) + 1
}
counts
}
Reads like Python — compiles to a memory-safe, RC-eliminated native binary.
@vulkan lets you write the whole GPU-driven meshlet renderer as one single-source
Vire program — @compute build → @task cull → @mesh draw → @fragment shade —
that Vire compiles to the same SPIR-V as the handwritten GLSL. In a steady-state
mesh-shader benchmark (identical SPIR-V, 5000 frames) it shows no measurable runtime
overhead. The host side — pipeline, render pass, descriptors, synchronization — is
handled by a Vulkan runtime the compiler links in (vk_runtime.c),
so the application author writes only the ~9 lines of shader logic instead of the 85–132
lines of C++/Rust setup. The descriptor-set layout is now derived from the shader
(V3, partial): the compiler reflects each stage's resource usage into a VkIface, and
the runtime builds the VkDescriptorSetLayout from it via one generic reflected path —
the binding, descriptor type, and stage mask come from the shader, not a hardcoded
per-demo layout (verified pixel-identical across textured / multi-sampler / meshlet-SSBO
paths). For the mesh/meshlet path the whole pipeline layout is now shader-derived —
the descriptor set and the push-constant range (size + stage mask) come from the
shader, so the @task cull_plane() push targets exactly the stage that reads it. Still a
fixed runtime protocol (not shader-varying): the graphics vertex/fragment pipeline's
16-byte per-frame uniform() channel and the compute-dispatch count; and on the roadmap
a draw(pipe, mesh, uniforms) host surface (see TODO.md V3/V4).
See benchmarks/vulkan/ and language/GPU-VULKAN.md.
Classically, memory safety comes with one of three costs: a garbage collector (runtime/pauses), ownership + lifetimes (Rust's annotation burden), or reference counting (a small runtime). Vire resolves this per program site: a whole-program solver proves ownership where possible (→ zero runtime, like Rust), and falls back to lean RC where necessary. The programmer writes zero memory annotations. Un-annotated types are inferred (Python ergonomics without Python's dynamic cost) — today by a best-effort monomorphic unifier over a scalar type lattice (Int/Float/Bool/ ref/Unit); full Hindley–Milner with trait resolution and reference-type checking is roadmap, not shipped (see TODO.md Front-end completeness and language/EVALUATION.md §5). This is feasible because Vire is closed-world (all sources available at compile time) and sits on a backend that already delivers exactly these proofs.
Vire is a front-end on a built, measured backend. The whole pipeline is
functional: vire build foo.vr -o foo and vire run foo.vr produce and execute
native binaries today.
| Layer | Status |
|---|---|
Vire front-end (crates/vire) — lexer, parser, macro expansion, recursive inline, type inference, lowering to SSA IR |
built & working — compiles .vr end-to-end to native code |
Mid-level IR (crates/ir) |
built |
Whole-program solver (crates/solver) — devirtualization, inlining, escape/RC elision, bounds/null-check elision, field auto-narrowing, region inference |
built |
LLVM backend (crates/backend) — textual IR + clang -O2 -flto -march=native; TBAA, !invariant.load, branch weights, cold error paths; hosted/freestanding/threads |
built |
Runtime (crates/driver) — RC + Bacon–Rajan cycle collector, slab allocator, packed 16-byte header |
built |
GPU kernels (@gpu) — single-source device functions → NVPTX (llc) → PTX → CUDA Driver-API launch; up to 16× vs CPU on an RTX 5070, bit-exact for integer kernels |
built — language/GPU-KERNELS.md, benchmarks/gpu/ |
@vulkan graphics — Vire-authored shaders (@vertex/@fragment/@mesh/@task/@compute) → SPIR-V, a full GPU-driven meshlet renderer (compute-built scene → GPU frustum/backface cull → mesh-shader draw), plus textures, depth, a render graph (offscreen passes with automatic layout barriers), interactive rendering (per-frame loop + animated window), lifetime-safe RC-bound GPU handles (texture/buffer/session, 0-live verified), @gpuvk vendor-neutral compute, and a declarative frame { } — all from one Vire program; vendor-neutral (Intel iGPU + RTX 5070) |
built — language/GPU-VULKAN.md, examples/vire/ (vulkan_*.vr), tests/vire_vulkan.sh (35), benchmarks/vulkan/ |
Tooling — VS Code extension (syntax highlighting, vire check diagnostics, hover, go-to-definition, completion, quick fixes — via the frontend compiled to WebAssembly, so it needs no toolchain) + native debugging (breakpoints, stepping, call stack, local variables) via --debug DWARF + lldb-dap |
built — vscode-vire/ |
Cross-compilation (--target) — Windows produces a working .exe (MinGW + LLD); BSD compiles to an object; macOS needs the SDK. The runtime is portable C |
built — language/CROSS-COMPILE.md |
The backend emits -O2 -flto and caches the runtime bitcode + verifies inline
@c/@asm blocks in parallel, so incremental vire build is fast (a small
build ≈ 0.12 s). The backend was developed and hardened via a Java-bytecode
front-end prototype
(the fastjavac path), whose 65 heap-balance regression tests (0 live objects at
exit) are the soundness oracle — the floor every optimization must keep green. See
DESIGN.md and benchmarks/.
Cross-compiler on this machine (best-of-5, output-verified; Vire vs clang++ 22, g++
16, rustc 1.97, all -O2 -flto -march=native; measured 2026-07):
| Benchmark | Vire vs Rust | Vire vs clang++ | Notes |
|---|---|---|---|
| montecarlo / nbody / bitmanip | ~1.00× | ~1.00× | compute-bound, parity |
| struct (stack structs) | 0.90× | 0.89× | beats both |
| binary-trees | 0.91× | 1.29× | region inference + move-on-last-use |
| matmul (256³ ikj) | 0.98× | 0.91× | ikj order → vectorized SAXPY; affine index elided |
| vcall (dyn dispatch) | 1.00× | 0.44× (2.3× faster) | solver devirtualization; beats clang virtual |
| binsearch (10M) | 1.03× | 0.78× | midpoint check proved redundant + elided — safely |
| sort (quicksort 2M) | 1.06× | 1.33× | uncatchable checks abort noreturn (Rust's structure) |
| graph (BFS + Dijkstra, 1.6M edges) | ~1.00× | ~1.00× | was 1.61× — a region-array eager-zero fault, now fixed; RSS + time both at parity |
Across the Vire benchmarks (suite + benchmarks/vire-lang/),
memory-safe Vire vs memory-safe Rust is at Rust parity on the compute/struct/tree
kernels — every one within ~9% of Rust and several faster (struct 0.90×, binary-trees
0.91×, matmul 0.98×, vcall = Rust / 0.44× clang). graph (BFS + Dijkstra on a 1.6M-edge
digraph) was the one loss at 1.61×; it is now 1.12×, and the diagnosis is worth keeping
because two plausible explanations were wrong. It is a flat-i64-array, pointer-chasing
kernel — no objects, no reference counting, no cycle collector (six integer arrays,
never a heap object), so not an RC/object-graph residual. Nor was it a bounds-check tax:
with all bounds checks removed (FASTLLVM_NO_BOUNDS, bit-identical output) the gap
barely moved, and Vire emits fewer checks than Rust here (2 vs 32 panic sites — its BFS
inner loop is 7 instructions, check elided and base in a register, against Rust's 10 with
an inline check and a per-iteration base reload). The actual cause, found by isolating
steady-state compute (8 warm reps over pre-allocated arrays): compute was already 0.93×
Rust — Vire was faster. The entire gap was one-time paging. jrt_region_array
default-zeroed each array with a full memset, which faulted in the whole 56 MB working
set — including the tail of the two worst-case-sized binary-heap scratch arrays the
algorithm never touches — whereas Rust's vec![0; n] gets lazy zero pages. The runtime
now fills region arrays lazily (memset only the reused prefix below a dirty high-water
mark; the fresh mmap(MAP_ANONYMOUS) tail stays zero and unfaulted). Result: RSS 56 →
30 MB (= Rust's 30), time 38.8 ms vs Rust 39.9 (best of 10) — parity on both, bounds
checks included (an earlier 1.12× reading was a small-sample artefact). Codegen is
byte-identical; verified by the Java 0-live oracle (67/67) and the differential fuzzer.
On the Java→native
oracle path the same backend takes NBody 35.7× → 1.16× (Math.sqrt now lowers to the
sqrtsd intrinsic, not a 60-iteration Newton call) and binary-trees 1.73× → 0.81×,
beating Rust (a shape/freshness analysis drops the cycle collector for provably
tree-shaped types). The solver proves array indices in range (the (lo+hi)/2 midpoint,
the affine r*n+k) and, where a check can't be elided, makes it as cheap as Rust's (a
noreturn abort when provably uncatchable) — all fully memory-safe: a genuinely
out-of-bounds access still throws.
Memory (peak RSS) is reported alongside time in every suite: Vire is at or below
both Rust and C++ on essentially every benchmark — ~2 MB under clang everywhere (no
libstdc++/iostream baseline), level with Rust, and even binary-trees (pure alloc/GC)
peaks under both (RC frees eagerly, 0 live, no growing GC heap). graph used to be the
exception (56 MB) until the jrt_region_array lazy-fill fix above: it now peaks at 30
MB, level with Rust's 30 and under clang++'s 58 — a region array only makes resident the
pages the program actually touches, exactly like Rust's vec!.
Beyond single kernels, benchmarks/complex/ runs multi-algorithm
workloads (a generate→sort→search→histogram pipeline; integer k-means) and fair
fork/join multithreading — parallel Monte-Carlo and Mandelbrot with 4 threads in
Vire, Rust, and C++ (bit-identical output). The threading is real: pmontecarlo scales
3.98× on 4 cores and Vire is at/ahead of Rust/C++ (0.97×) — spawn/Atomic add no
overhead over raw std::thread. See TODO.md, benchmarks/.
The whole pipeline is one command. Optimization is on by default
(clang -O2 -flto -march=native, closed-world AOT for the host CPU).
$ vire build hello.vr -o hello # compile to a native binary
$ vire run hello.vr # compile to a temp binary and run itCommon flags (all additive to the same solver + backend):
| Flag | Effect |
|---|---|
-o FILE |
output path |
--emit=obj|asm|llvm|ir|staticlib |
stop at a .o (one relocatable C-ABI object, incl. main), assembly, LLVM/mid IR, or a .a |
--deps FILE |
write a Makefile/Ninja depfile (for incremental builds) |
-I DIR |
include path for native "c" blocks / headers |
--pkg NAME |
pull cflags+libs from pkg-config (first-class system deps) |
-l NAME / --obj FILE |
link a library / a .c/.cpp/.o/.a (C/C++/Rust via the C ABI) |
--target TRIPLE |
cross-compile (e.g. x86_64-pc-windows-gnu) |
--log-level debug|info|warn|error|off |
build-time log threshold (below it = zero instructions) |
--syntax FILE |
opt-in custom keyword spellings (also via an in-file //!syntax: FILE) |
-g / --backtrace |
DWARF debug info (.vr:line) / native backtrace on an uncaught throw |
FFI is source-level: extern "C" header "h.h" { … } auto-generates bindings from a C
header at compile time; native "c"/"c++"/"asm" """ … """ blocks are compiled and linked
in automatically (and proven memory-safe by the vendored verifier, the sound
replacement for unsafe).
A whole .vr program lowers to one relocatable C-ABI object (the runtime main
included), so Meson links it like any C/C++/Rust object. Using only the stock Meson DSL
(no plugin to install):
project('app', 'c', meson_version: '>=1.1.0')
vire = find_program('vire')
main_obj = custom_target('main.vr.o',
input: 'main.vr', output: 'main.vr.o',
command: [vire, 'build', '@INPUT@', '--emit=obj', '-o', '@OUTPUT@', '--deps', '@DEPFILE@'],
depfile: 'main.vr.o.d') # incremental: rebuilds on source/header change
executable('app', 'util.c', objects: main_obj, link_args: ['-lm'])$ meson setup builddir && ninja -C builddir && ./builddir/appThis links a Vire object with a C object (-lm because the runtime uses libm). Add
-pthread if the program uses spawn, and system libraries via --pkg. An optional
import('vire') module (vire.executable() / vire.static_library()) and a tested,
runnable example are in build-integration/meson/.
There is no tracing GC. Memory is reference-counted, and the whole-program solver eliminates as much of the runtime bookkeeping as it can prove away statically, so what's left for the runtime to "handle" (allocate + RC retain/release + cycle collection) is small — and, since the runtime work this project added, spike-free.
What the solver removes statically (verified against the code):
- Compute-bound code → 0% runtime handling. No heap allocation at all
(
FASTLLVM_HEAPSTATSshows no[heap]line). - Traversal / read-only paths → RC already fully elided. Borrow-slot analysis
(a field read from a stable base whose field the function and its transitive
callees never write is a borrow, not an owned ref), interprocedural
field-write analysis, and move-on-last-use together reduce RC on read/traverse code
to the
--no-rcceiling (normal == --no-rc). - Provably-local allocations → stack / region / arena, immortal. Escape analysis
promotes non-escaping objects to
alloca(StackNew) and arrays to a bump region; awhile-loop body whose allocations provably can't leave the iteration (interprocedural check) is bracketed in a per-iteration arena. All of these are immortal — no per-object RC, no collector, freed en bloc — and are not counted as runtime-handled at all.
What still reaches the runtime — measured, honest: allocation-heavy object
graphs (trees, lists, ASTs) whose nodes escape into the structure. Example:
build(18) for a binary tree = ~524k heap nodes, all RC-managed (and, being a
self-referential type, kept under the cycle collector). This is the residual mechanism.
Honest scope of that residual. On the one object-graph benchmark in the suite —
binary-trees — this mechanism costs nothing measurable: Vire runs it 0.91× Rust and
peaks under both Rust and C++ (the analyses below drive its RC to the --no-rc
ceiling). The graph benchmark above is not an instance of this — it has no objects at
all. So the demonstrated suite does not contain a workload where RC/collector traffic
is the measured bottleneck. Such workloads exist — heavily shared, topology-mutating
object graphs with genuinely dynamic lifetimes (mutable caches, general graph mutation) —
and they are the honest boundary of what these numbers show: the suite demonstrates that
the elision analyses hold on structured-lifetime object graphs, not that RC is free in
full generality. The lever below is aimed at that un-benchmarked class.
The lever, measured — and the corrected diagnosis. The lever is auto-inferred
arena inference: where the solver can prove a whole subgraph is built, used, and dies
within one scope (t = build(); use(t); drop), route its allocations into an arena
(immortal, bulk-freed, zero RC/collector). The mechanism already exists (the
capsule/loop arena). benchmarks/complex/sharedgraph.vr
now measures the un-benchmarked class directly — a shared, topology-mutating, cyclic
object graph, built and dropped per iteration. Rebuilding it three ways decomposes the
cost (400k trials, 8M nodes): --no-rc ceiling 273 ms, --no-cycles (RC on,
collector off) 394 ms, shipping (RC + collector) 729 ms — so the collector is
338 ms / 46 % and RC retain/release 121 ms / 17 % of runtime. Fair Rust
(Rc<RefCell<Node>>, cycle broken by hand) is 145 ms; Vire is 5.0× here. This
is the honest cost of the class, not an estimate.
An earlier attempt to auto-fire the arena at function scope was reverted, and this
benchmark corrected the diagnosis that reverted it. Recursion is not the blocker:
the loop-arena already fires on a recursive build/use/drop (a non-mutating variant of
this benchmark emits jrt_arena_push/pop around a recursive chain(20) and reports
zero heap allocations — all 8M nodes immortal in the arena). What suppressed it was one
thing: the ref-storing field mutation last.next = h, whose base the region check could
not prove was itself arena-local, so it conservatively bailed. This lever is now built
(while_arena_safe + loop_fresh_locals/expr_is_fresh in crates/vire/src/lower.rs):
a ref-storing field mutation obj.f = ref is admitted into the arena when obj and the
stored ref are provably iteration-fresh — every reaching definition comes from a
constructor, a call with fresh arguments, a field-read of a fresh object, null, or a
scalar (a greatest-fixpoint freshness dataflow over the loop body). Both fresh ⇒ both live
and die in the arena ⇒ the store creates no dangling pointer and captures no non-arena ref.
On sharedgraph the arena fires, and a second pass elides the reference counting the arena
makes redundant: the objects are arena-immortal, so the jrt_retain/jrt_release calls
the emitter left in the hot loops were pure no-ops (an rc<0 early return) costing 282 ms
of call traffic. crates/backend/src/lib.rs now proves which refs are arena-immortal —
New at arena depth>0, a user call there with immortal args, and (interprocedurally, via
arena_ctx_fns) recursive builders like chain/make whose every call site is in-arena —
and skips RC on them. sharedgraph: 729 → 75 ms = 0.51× Rust (the class from the original
5.0× to twice as fast as Rust); binary-trees: 205 → 46 ms (its make() is arena-context
too). Zero heap, zero collector, correct, 0-live, GUARD_FREE-clean. Soundness-critical (a
wrong "always in an arena" verdict would drop a real RC → use-after-free), so it is pinned:
dispatch-reachable and heap-escaping (capsule-export) functions are excluded, a builder
called both in and out of an arena is proven NOT arena-context (a vire_heap regression
case), and the Java 0-live oracle + GUARD_FREE + the differential fuzzer gate the rest.
The hard floor is unchanged and real: genuinely dynamic-lifetime graphs (mutable caches, long-lived graphs mutated past any single scope) cannot be proven away by any static analysis — they structurally need dynamic RC + the collector. The arena lever covers structured-lifetime graphs (compilers/ASTs, request handlers, per-frame scene graphs); it does not make RC free in full generality.
No latency spikes. The three synchronous runtime operations were made incremental/
budgeted (see DONE.md): the cycle collector runs in bounded steps
(continuous, buffer-bounded RAM), the release free-cascade of a large dead
subgraph is spread across operations, and a large collected garbage cycle's free is
deferred — all verified 0-live (Java oracle 67/67, a listdrop leak-catcher, a 2M-node
ring, flat RSS across 8–16× allocation churn).
Temporal safety of the RC residual — a second eye beyond 0-live. The 0-live oracle
checks the heap balance, not the timing: for the interesting configuration (a heap
object with the retain elided, so exactly one release), a premature free — dropping an
object still reachable through a second reference — leaves live_objects at 0 anyway.
So FASTLLVM_GUARD_FREE=1 adds a non-perturbing check (companion to
FASTLLVM_HEAPSTATS): each RC-managed object gets its own guard-paged mapping, and at
rc→0 the runtime mprotect(PROT_NONE)s it instead of recycling the memory, turning
any premature free into a deterministic SIGSEGV at the dereference. It is runtime-
only — codegen is byte-identical to the shipping binary, so unlike ASan it does not
disturb the RC-elision it checks (a modeled premature free is caught; the same read
without the guard silently returns recycled bytes). Every ownership program — build(16),
shared-child DAGs, escaping chains with a live alias, list traversal — runs clean
under it. (The GPU/@vulkan binaries are excluded: the guard-page layout perturbs the
GPU driver's own atexit teardown — a SEGV_MAPERR in its munmap cascade, i.e. an
instrument artifact at the external-driver boundary, not an RC premature free.)
- TODO.md — roadmap and remaining work (M0 risk gate, front-end pipeline, features 1–8, performance).
- DESIGN.md — backend architecture (solver, memory model, benchmarks). Describes the Java-bytecode path = the proof/bootstrap base.
- language/EVALUATION.md — honest feasibility: the three tensions (no runtime / all libraries / Python-light) and §7 residual risks (alias precision, compile time).
- language/LANGUAGE.md — syntax tour (quick start).
- language/REFERENCE.md — full syntax/feature reference.
- language/FEATURES-EVALUATION.md — assessment of the eight requested features (multithreading, templates, comptime reflection, own preprocessor, Meson, logger, Go-style error handling, debug crash paths).
- language/PARSER.md — parser/front-end build plan.
- language/GPU-KERNELS.md —
@gpudata-parallel device kernels (NVPTX → CUDA). - language/GPU-VULKAN.md —
@vulkan: Vire-authored shaders and the GPU-driven meshlet renderer. Ends with a shipped reference (every stage, builtin, host entry point, and the build→cull→draw pipeline). - language/examples/ — example programs across areas and features.
- vscode-vire/ — VS Code extension. Language
intelligence (diagnostics, hover, go-to-definition, outline) runs the frontend
compiled to WebAssembly (
crates/vire-wasm), so it works on Windows/macOS/ Linux with no toolchain installed. Plus syntax highlighting, snippets, and native debugging (breakpoints, stepping, call stack, local variables) via--debugDWARF + lldb-dap. - benchmarks/ — benchmark suite (Java/Rust/C++), runner, analysis.
- LICENSING.md — dual license: CSolver (
crates/csolver/) under Apache-2.0, everything else under GPL-3.0-or-later.
Dual-licensed by directory: CSolver (crates/csolver/**, the vendored
memory-safety verifier) under the Apache License 2.0
(crates/csolver/LICENSE); everything else under the
GNU GPL v3.0 or later (LICENSE). See LICENSING.md.
- Inference over annotation — types appear nowhere yet are all known.
- No
null—Option[T]; no exceptions — errors are values (Go spirit) with?propagation. typefor product and sum types (value types, no object header), traits + monomorphized generics.comptime— code that runs in the compiler: reflection, derivations, conditional compilation — zero-cost, no runtime metadata ballast.- Invisible memory — stack/heap/RC decided by the solver;
&optional. - Concurrency safe by construction — channels (CSP) +
Mutex/Atomic; the solver rejects shared bare mutable state. capsule— a fault-containment sandbox: body allocations go into a private arena freed en bloc (RC-/collector-free). Inputs are deep-copied in, results deep-copied out (arrays and arbitrary structs/graphs, cycles + sharing handled), so a bug in the body can't touch the caller's data.- GPU kernels — a
@gpu fn k(i: Int, …)runs data-parallel on the GPU (single-source: NVPTX → PTX → CUDA Driver-API launch), with the thread index injected like aparallel_forworker(i, …). Up to 16× vs CPU on an RTX 5070, bit-exact for integer kernels. See language/GPU-KERNELS.md. @vulkangraphics — write the shaders in Vire (@vertex/@fragment/@mesh/@task/@compute→ SPIR-V) and get a GPU-driven meshlet renderer from one program: a@computebuilder fills the scene on the GPU,@taskfrustum/backface- culls each meshlet,@meshdraws the survivors,@fragmentshades — normally GLSL/HLSL + C++ + a mesh toolchain. Plus textures, depth, a render graph (offscreen passes with automatic barriers), interactive rendering (per-frame loop- animated window), lifetime-safe RC-bound GPU handles (freed when the last Vire
reference drops — no GPU use-after-free), and
@gpuvkvendor-neutral compute (the Vulkan counterpart to the CUDA/ROCm@gputrack). Vendor-neutral. See language/GPU-VULKAN.md.
- animated window), lifetime-safe RC-bound GPU handles (freed when the last Vire
reference drops — no GPU use-after-free), and
- C native —
extern "C"/header bindings; C++/Rust via the C ABI. Meson first-class.
The name and details are provisional and easy to change; the design is the core.