A modern, high-performance Lua 5.5 to LuaJIT compiler written in Rust.
Documentation: https://github.com/leonardespi/valua
Source Code: https://github.com/leonardespi/valua
valua is a professional, fast transpiler engineered to resolve runtime fragmentation within the Lua ecosystem. It translates modern Lua 5.5 source code into compatible Lua 5.1 streams, designed for native high-velocity execution under LuaJIT.
-
Zero-Cost Performance Mapping: Translates target-agnostic Abstract Syntax Trees (AST) directly into LuaJIT-friendly execution patterns, ensuring maximum JIT-compiler throughput and zero runtime overhead.
-
Memory-Safe Rust Core: Uses Rust's strict memory safety guarantees and thread-safe parsing routines for a highly predictable and deterministic compilation pipeline.
-
Modern Syntax Bridging: Delivers complete baseline support for Lua 5.5 semantics—including bitwise operators, compact array literals, and attributes—to runtimes historically restricted to 5.1 constraints.
-
Fail-Fast Semantic Guarantees: Eliminates silent runtime compilation issues by implementing a strict ahead-of-time validation pass that rejects borderline lexical ambiguities with explicit error diagnostics.
-
Decoupled Static Analysis: Ships with
valua-lintas an autonomous public crate, enabling plug-and-play integration with continuous integration (CI) workflows and editor language servers without committing to full code emission.
valua maps modern syntax primitives into optimized, standard LuaJIT idioms:
| Source Construct (Lua 5.5) | Compilation Output Strategy | Performance Profile |
|---|---|---|
Bitwise Operators (&, |, ~, <<, >>) |
Emits explicit bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift calls |
Native — LuaJIT compiles bit.* functions directly into single-cycle machine instructions. |
Integer Division (//) |
Lowered to a JIT-optimized math.floor(a / b) pattern |
Native |
<const> Attribute |
Validated statically at compile time; emitted as a standard local variable |
Native |
<close> Attribute |
Scoped pcall wrapper invoking the __close metamethod on deterministic exit |
Negligible — Minor structural overhead optimized for I/O-bound resource lifecycles. |
| Compact Array Literals | Passthrough optimization — semantic translation is uniform between versions | Native |
<global> Declarations |
Compiled directly into explicit global context _G dictionary assignments |
Native |
-- Input: Lua 5.5
local function process(mask, value)
local result = value & mask -- bitwise AND
local half = result // 2 -- integer floor division
return half | 0xFF00 -- bitwise OR
end-- Output: Lua 5.1 / LuaJIT (valua build input.lua --target luajit)
local function process(mask, value)
local result = bit.band(value, mask)
local half = math.floor(result / 2)
return bit.bor(half, 65280)
endNo polyfill injection when targeting LuaJIT — bit.* functions are native intrinsics compiled to single-cycle machine instructions. For the --target lua51 target, a pure-Lua bit compatibility table is prepended automatically.
Validated by trilateral-perf — a process-level harness that runs the same bitwise-intensive compute kernel across three environments and verifies JIT trace parity.
N = 100,000,000 iterations · LuaJIT 2.1 · Lua 5.5.0 · 7 measured runs, 2 warmup
A Lua 5.1 native (LuaJIT) 99 ms ██
B Lua 5.5 → valua → LuaJIT 93 ms ██ ← write modern Lua, pay nothing extra
C Lua 5.5 reference interpreter 1,085 ms ██████████████████████
(1 block ≈ 50 ms)
valua-transpiled code runs within measurement noise of handwritten Lua 5.1 and is ≈ 11× faster than the Lua 5.5 reference interpreter on bitwise-heavy numeric paths. The transpiled source is byte-for-byte identical to what an expert would write by hand — because valua emits the same bit.* intrinsics that LuaJIT compiles to single-cycle machine instructions.
The guarantee that matters: transpiled code must not trigger NYI bailouts that silently fall off the JIT fast path.
| Target | Traces Compiled | NYI Aborts | JIT Status |
|---|---|---|---|
| A — Lua 5.1 native | 1 | 0 | ✓ Full JIT |
| B — valua transpiled | 1 | 0 | ✓ Full JIT |
| C — Lua 5.5 native | — | — | No JIT |
Zero NYI aborts. Identical trace count. The transpiled hot loop compiles to the exact same native machine code path as hand-written Lua 5.1.
# Quick smoke test (N = 10M, ~15 seconds total)
cargo run -p valua-bench --release --bin trilateral-perf -- --lua55 /path/to/lua5.5
# Full production run (stable ratios, ~3 minutes)
cargo run -p valua-bench --release --bin trilateral-perf -- \
-n 100000000 -r 10 -w 3 \
--lua55 /path/to/lua5.5 \
-o ./bench_reports
# Emits: bench_reports/trilateral_perf_report.md
# bench_reports/trilateral_perf_report.jsonPrograms relying on features that break target execution invariants are strictly blocked during the validation phase. valua guarantees compile-time panics over silent semantic deviations.
| Error Code | Rejection Trigger | Remediation / Context |
|---|---|---|
E0101 |
Invocations of math.type() |
Type reflection cannot differentiate integers from floats natively under LuaJIT's NaN-boxing strategy. |
E0102 |
Explicit 64-bit integer overflow dependencies | Strict mathematical wrapping semantics cannot be replicated without heavy boxing overhead. |
E0301 |
Mutation of a <const> binding |
Detected modification of a compile-time immutable reference. |
E04xx |
Unrecognized downstream syntax constructs | Token sequences belonging to experimental features or newer specifications. |
Every rejection emits a precise span-annotated error. No silent failures, no stack traces — just the line, the token, and the fix.
$ valua check input.lua
error[E0101]: math.type observes the integer/float distinction absent in LuaJIT
┌─ input.lua:1:11
│
1 │ local t = math.type(1)
│ ^^^^^^^^^ math.type observes the integer/float distinction absent in LuaJIT
│
= note: math.type() has no equivalent in LuaJIT; all numbers are IEEE 754 doubles
= help: Remove type discrimination or track numeric kind explicitly in user data
Run valua explain <code> (e.g. valua explain E0101) for full documentation, examples, and remediation guidance on any error code.
A recurring challenge when bridging modern Lua specifications down to LuaJIT is the underlying data structure layout: LuaJIT unifies all numbers under standard IEEE 754 double-precision floats, whereas Lua 5.3+ introduces an explicit, separate 64-bit integer type subtype.
Enforcing perfect runtime arithmetic mirroring across this barrier requires wrapping every mathematical operator in a heap-allocated emulation layer. Benchmarks indicate this degrades raw performance by factors between 20x and 100x, fundamentally defeating the purpose of targeting LuaJIT. Statically identifying whether a script genuinely depends on strict integer overflow properties reduces directly to the Halting Problem.
valua's architectural response is deterministic boundary definition: What you compile is exactly what executes. By shifting verification from a complex runtime emulation layer to a predictable static compiler barrier, you receive consistent execution speeds with zero performance penalties.
Note: valua is under active pre-production development. The stable binary installation steps below apply to versions 1.0.0 and above.
cargo install valua-cli
Pre-compiled binary distributions targeting standard platform triplets (linux-x86_64, linux-aarch64, macos-arm64, windows-x86_64) are distributed continuously via GitHub Releases.
# Transpile a source file to optimized LuaJIT-compatible Lua 5.1 code
valua build input.lua -o output.lua --target luajit
# Transpile to vanilla Lua 5.1 (includes standard bitwise polyfills)
valua build input.lua -o output.lua --target lua51
# Validate syntax structure across the pipeline without writing to disk
valua check input.lua
# Execute static analysis rules exclusively (zero generation overhead)
valua lint input.lua --target luajit
# Output current version descriptor
valua version
# Print full documentation for a diagnostic error code (case-insensitive)
valua explain E0101
Note:
valua buildoperates on single source files. Directory-wide compilation is not yet supported. For large codebases, invoke valua per-file via shell scripting:find src -name '*.lua' | xargs -I{} valua build {} --target luajit -o dist/{}.out
- Rust Toolchain (v1.75+)
justautomation runner
# Install development task runner
cargo install just
# Install the pre-commit hook into .git/hooks/pre-commit
just install-hooks
# The hook runs `just check-fast` on every commit:
# 1. cargo fmt --all -- --check (formatting, must be exact)
# 2. cargo clippy -- -D warnings (all lints are errors)
# 3. cargo test --workspace (full test suite)
# Run the complete check suite manually (fmt + clippy + test + release build)
just check
# Execute test suite targets exclusively
just test
# Target an individual verification test fixture
just test-one bitwise_and
Q: What happens to valua if LuaJIT Remake (LJR) reaches production with Lua 5.4 support?
A: valua remains useful. The design targets the structural reality of Lua's ecosystem fragmentation, not a single version gap:
valua-lintstays relevant regardless of runtime: Verifying cross-runtime portability becomes more important with more runtime options, not less. CI pipelines that catch LuaJIT-incompatible syntax at commit time are useful whether LJR exists or not.- Lua 5.5 is already out; fragmentation continues: LJR targeting Lua 5.4 doesn't unify the ecosystem. The parser is built to handle Lua 5.5+ source and future grammar extensions without core rewrites.
- Modular codegen enables new targets: Adding Luau, Teal, or an LJR-native target is a new backend pass, not an architectural change.
valuais not a bet on the 5.4↔5.1 gap specifically — it is infrastructure for Lua's permanently fragmented runtime landscape.
Distributed under the terms of the MIT License. Review LICENSE for absolute legal terms.