Magnesium
Fast without a JIT.
An embeddable scripting language written from scratch in C11. Register bytecode, a generational garbage collector, and a verifier that makes untrusted bytecode safe to load.
- v1.2.0
- MIT license
- C11
- Bindings for Rust, C++ and C#
- Needs only libc, libm, libdl and pthreads
// hello.mg
let name = input("Name: ")
print(_"Hello, {name}!")
$ ./magnesium hello.mg
Name: Ada
Hello, Ada!
$
Under the hood
One function, five stages.
Follow three lines of Magnesium from source text to a running register machine.
fn total(price, qty, tax)
return price * qty + tax
end
A table-free scanner turns text into tokens, matching keywords with a hand-coded trie.
- fn
- total
- (
- price
- ,
- qty
- ,
- tax
- )
- return
- price
- *
- qty
- +
- tax
- end
keywordnameoperatorpunctuation
A recursive-descent parser with Pratt-style precedence climbing builds the tree, so * binds tighter than +.
The compiler allocates registers and emits register bytecode. The parameters land in R1, R2 and R3.
- 0000MULA=4 B=1 C=2R4 = price * qty
- 0001ADDA=5 B=4 C=3R5 = R4 + tax
- 0002RETURNA=5 B=1return R5
- 0003LOADNILA=6implicit
- 0004RETURNA=6 B=1return null
The actual output of Magnesium’s disassembler for this function. The last two instructions are the return null every function ends with.
magnesium build saves the bytecode as a .mgc file. Loading one runs the verifier over every instruction before any of it executes:
- Every opcode is one of the 109
- Every register is inside the function’s frame
- Every constant index exists, and holds a name where one is expected
- Every jump lands on an instruction of the same function
- Register counts and arity stay within limits
verified ready to run
Running source directly skips the file: the compiler hands its bytecode straight to the VM.
A computed-goto loop dispatches each instruction. Calling total(4, 3, 1):
- MULR4 = R1 * R2
- ADDR5 = R4 + R3
- RETURNR5
- R1
- 4
- R2
- 3
- R3
- 1
- R4
- 12
- R5
- 13
→ 13
Call it from top-level code and total is never called at all: the compiler inlines it, so the caller runs the MUL and ADD itself, with no call and no new frame.
Benchmarks
16 races. 16 wins.
The same 16 programs in Magnesium, Lua 5.4 and CPython. Magnesium is the fastest on every one, from source and from bytecode.
- vs Lua 5.4
- 1.6×
- vs CPython 3
- 3.7×
-
fib35recursive Fibonacci
-
binary_treesdepth-18 GC pressure
-
sieve1M-element array
-
mandelbrot400 × 400
-
dict_bench250k insert and lookup
-
arith_looparithmetic loop
-
call_loop10M function calls
-
closure_loop5M upvalue mutations
-
array_looparray loop
-
object_fields1M struct-field dispatches
-
gc_alloc50k × 50 arrays
-
string_concatstring building
-
control_flowbranches and loops
-
fallible_lookup3M ? operator lookups
-
native_len5M native calls
-
coroutine_switch200k resume and yield
Every run: averages, medians and all five timings, from source and from bytecode
| Benchmark | Runtime | Average | Median | vs Mg | Runs |
|---|---|---|---|---|---|
| fib35 | Magnesium | 0.390 | 0.396 | 1.00× | 0.376 0.384 0.396 0.399 0.396 |
| Lua 5.4 | 0.625 | 0.617 | 1.56× | 0.617 0.638 0.656 0.614 0.600 | |
| CPython 3 | 0.998 | 1.001 | 2.53× | 1.003 1.005 1.001 0.991 0.990 | |
| binary_trees | Magnesium | 0.081 | 0.079 | 1.00× | 0.089 0.079 0.079 0.081 0.079 |
| Lua 5.4 | 0.199 | 0.193 | 2.43× | 0.187 0.193 0.234 0.193 0.189 | |
| CPython 3 | 0.527 | 0.514 | 6.49× | 0.585 0.563 0.487 0.486 0.514 | |
| sieve | Magnesium | 0.092 | 0.094 | 1.00× | 0.084 0.091 0.095 0.094 0.096 |
| Lua 5.4 | 0.112 | 0.111 | 1.19× | 0.119 0.111 0.104 0.122 0.106 | |
| CPython 3 | 0.196 | 0.192 | 2.05× | 0.202 0.192 0.187 0.213 0.188 | |
| mandelbrot | Magnesium | 0.081 | 0.082 | 1.00× | 0.082 0.083 0.082 0.079 0.080 |
| Lua 5.4 | 0.089 | 0.088 | 1.08× | 0.087 0.090 0.088 0.088 0.091 | |
| CPython 3 | 0.380 | 0.341 | 4.18× | 0.363 0.326 0.341 0.333 0.537 | |
| dict_bench | Magnesium | 0.121 | 0.120 | 1.00× | 0.108 0.120 0.130 0.118 0.130 |
| Lua 5.4 | 0.207 | 0.205 | 1.71× | 0.190 0.228 0.216 0.205 0.193 | |
| CPython 3 | 0.184 | 0.181 | 1.51× | 0.181 0.175 0.203 0.190 0.170 | |
| arith_loop | Magnesium | 0.089 | 0.089 | 1.00× | 0.087 0.088 0.092 0.089 0.089 |
| Lua 5.4 | 0.107 | 0.107 | 1.20× | 0.106 0.109 0.107 0.107 0.104 | |
| CPython 3 | 0.679 | 0.670 | 7.56× | 0.711 0.666 0.670 0.684 0.665 | |
| call_loop | Magnesium | 0.149 | 0.150 | 1.00× | 0.147 0.152 0.150 0.146 0.151 |
| Lua 5.4 | 0.237 | 0.233 | 1.55× | 0.251 0.234 0.232 0.233 0.233 | |
| CPython 3 | 1.147 | 1.139 | 7.57× | 1.194 1.131 1.139 1.139 1.130 | |
| closure_loop | Magnesium | 0.108 | 0.107 | 1.00× | 0.111 0.106 0.110 0.105 0.107 |
| Lua 5.4 | 0.128 | 0.128 | 1.20× | 0.128 0.129 0.128 0.125 0.129 | |
| CPython 3 | 0.464 | 0.459 | 4.28× | 0.484 0.468 0.459 0.456 0.452 | |
| array_loop | Magnesium | 0.075 | 0.075 | 1.00× | 0.075 0.076 0.075 0.076 0.074 |
| Lua 5.4 | 0.108 | 0.107 | 1.42× | 0.106 0.108 0.107 0.110 0.107 | |
| CPython 3 | 0.456 | 0.453 | 6.00× | 0.480 0.453 0.463 0.440 0.444 | |
| object_fields | Magnesium | 0.062 | 0.063 | 1.00× | 0.059 0.064 0.063 0.064 0.059 |
| Lua 5.4 | 0.073 | 0.072 | 1.14× | 0.072 0.071 0.072 0.074 0.075 | |
| CPython 3 | 0.154 | 0.151 | 2.39× | 0.151 0.151 0.151 0.154 0.161 | |
| gc_alloc | Magnesium | 0.065 | 0.066 | 1.00× | 0.066 0.067 0.061 0.066 0.066 |
| Lua 5.4 | 0.092 | 0.092 | 1.39× | 0.092 0.091 0.095 0.092 0.090 | |
| CPython 3 | 0.211 | 0.211 | 3.19× | 0.207 0.205 0.216 0.215 0.211 | |
| string_concat | Magnesium | 0.037 | 0.037 | 1.00× | 0.039 0.036 0.041 0.034 0.037 |
| Lua 5.4 | 0.101 | 0.099 | 2.68× | 0.097 0.107 0.105 0.099 0.098 | |
| CPython 3 | 0.077 | 0.076 | 2.08× | 0.081 0.074 0.076 0.075 0.078 | |
| control_flow | Magnesium | 0.058 | 0.058 | 1.00× | 0.060 0.058 0.063 0.055 0.055 |
| Lua 5.4 | 0.072 | 0.071 | 1.22× | 0.071 0.066 0.068 0.073 0.080 | |
| CPython 3 | 0.321 | 0.321 | 5.55× | 0.321 0.323 0.322 0.319 0.320 | |
| fallible_lookup | Magnesium | 0.051 | 0.051 | 1.00× | 0.052 0.049 0.053 0.051 0.051 |
| Lua 5.4 | 0.062 | 0.061 | 1.19× | 0.060 0.062 0.061 0.059 0.067 | |
| CPython 3 | 0.243 | 0.244 | 4.75× | 0.248 0.244 0.236 0.243 0.244 | |
| native_len | Magnesium | 0.073 | 0.074 | 1.00× | 0.074 0.072 0.071 0.075 0.074 |
| Lua 5.4 | 0.139 | 0.136 | 1.84× | 0.137 0.136 0.151 0.136 0.134 | |
| CPython 3 | 0.432 | 0.429 | 5.79× | 0.445 0.428 0.433 0.429 0.425 | |
| coroutine_switch | Magnesium | 0.052 | 0.052 | 1.00× | 0.052 0.052 0.052 0.052 0.053 |
| Lua 5.4 | 0.253 | 0.254 | 4.88× | 0.255 0.256 0.253 0.249 0.254 | |
| CPython 3 | 0.089 | 0.089 | 1.70× | 0.089 0.092 0.089 0.087 0.089 |
| Benchmark | Runtime | Average | Median | vs Mg | Runs |
|---|---|---|---|---|---|
| fib35 | Magnesium | 0.371 | 0.372 | 1.00× | 0.373 0.367 0.370 0.372 0.372 |
| Lua 5.4 | 0.626 | 0.615 | 1.65× | 0.662 0.613 0.615 0.597 0.641 | |
| CPython 3 | 1.052 | 1.058 | 2.85× | 1.020 1.044 1.058 1.061 1.079 | |
| binary_trees | Magnesium | 0.073 | 0.072 | 1.00× | 0.075 0.072 0.071 0.074 0.070 |
| Lua 5.4 | 0.187 | 0.182 | 2.51× | 0.172 0.179 0.182 0.214 0.187 | |
| CPython 3 | 0.540 | 0.544 | 7.52× | 0.557 0.570 0.534 0.496 0.544 | |
| sieve | Magnesium | 0.087 | 0.087 | 1.00× | 0.087 0.089 0.085 0.087 0.086 |
| Lua 5.4 | 0.092 | 0.092 | 1.05× | 0.088 0.096 0.090 0.092 0.094 | |
| CPython 3 | 0.210 | 0.207 | 2.37× | 0.192 0.215 0.204 0.207 0.231 | |
| mandelbrot | Magnesium | 0.082 | 0.080 | 1.00× | 0.080 0.079 0.080 0.083 0.086 |
| Lua 5.4 | 0.093 | 0.092 | 1.14× | 0.096 0.089 0.092 0.090 0.096 | |
| CPython 3 | 0.369 | 0.371 | 4.63× | 0.348 0.371 0.345 0.394 0.385 | |
| dict_bench | Magnesium | 0.119 | 0.117 | 1.00× | 0.109 0.123 0.117 0.116 0.129 |
| Lua 5.4 | 0.212 | 0.200 | 1.71× | 0.200 0.219 0.191 0.196 0.251 | |
| CPython 3 | 0.180 | 0.180 | 1.54× | 0.180 0.179 0.183 0.176 0.182 | |
| arith_loop | Magnesium | 0.086 | 0.086 | 1.00× | 0.087 0.086 0.085 0.086 0.086 |
| Lua 5.4 | 0.107 | 0.106 | 1.24× | 0.110 0.105 0.106 0.106 0.106 | |
| CPython 3 | 0.681 | 0.683 | 7.96× | 0.682 0.685 0.685 0.683 0.668 | |
| call_loop | Magnesium | 0.152 | 0.154 | 1.00× | 0.154 0.150 0.154 0.154 0.147 |
| Lua 5.4 | 0.234 | 0.234 | 1.52× | 0.231 0.235 0.235 0.234 0.233 | |
| CPython 3 | 1.153 | 1.150 | 7.48× | 1.143 1.155 1.144 1.172 1.150 | |
| closure_loop | Magnesium | 0.105 | 0.105 | 1.00× | 0.105 0.105 0.105 0.106 0.106 |
| Lua 5.4 | 0.129 | 0.128 | 1.21× | 0.135 0.128 0.128 0.125 0.127 | |
| CPython 3 | 0.470 | 0.471 | 4.48× | 0.470 0.472 0.474 0.462 0.471 | |
| array_loop | Magnesium | 0.074 | 0.074 | 1.00× | 0.075 0.072 0.074 0.074 0.077 |
| Lua 5.4 | 0.107 | 0.107 | 1.44× | 0.107 0.108 0.105 0.107 0.108 | |
| CPython 3 | 0.453 | 0.451 | 6.06× | 0.465 0.451 0.432 0.471 0.447 | |
| object_fields | Magnesium | 0.065 | 0.065 | 1.00× | 0.063 0.067 0.062 0.065 0.066 |
| Lua 5.4 | 0.073 | 0.072 | 1.11× | 0.072 0.071 0.070 0.077 0.072 | |
| CPython 3 | 0.152 | 0.152 | 2.34× | 0.149 0.158 0.152 0.152 0.151 | |
| gc_alloc | Magnesium | 0.066 | 0.067 | 1.00× | 0.067 0.065 0.069 0.061 0.068 |
| Lua 5.4 | 0.092 | 0.091 | 1.36× | 0.090 0.091 0.096 0.090 0.094 | |
| CPython 3 | 0.213 | 0.216 | 3.23× | 0.218 0.207 0.216 0.200 0.226 | |
| string_concat | Magnesium | 0.039 | 0.039 | 1.00× | 0.039 0.037 0.038 0.040 0.039 |
| Lua 5.4 | 0.117 | 0.113 | 2.89× | 0.111 0.111 0.138 0.114 0.113 | |
| CPython 3 | 0.077 | 0.077 | 1.98× | 0.076 0.076 0.079 0.078 0.077 | |
| control_flow | Magnesium | 0.060 | 0.061 | 1.00× | 0.060 0.056 0.062 0.062 0.061 |
| Lua 5.4 | 0.070 | 0.071 | 1.16× | 0.070 0.071 0.068 0.071 0.071 | |
| CPython 3 | 0.327 | 0.327 | 5.35× | 0.333 0.329 0.325 0.324 0.327 | |
| fallible_lookup | Magnesium | 0.048 | 0.047 | 1.00× | 0.047 0.048 0.047 0.047 0.049 |
| Lua 5.4 | 0.060 | 0.059 | 1.25× | 0.059 0.058 0.060 0.061 0.059 | |
| CPython 3 | 0.256 | 0.257 | 5.41× | 0.266 0.257 0.247 0.269 0.244 | |
| native_len | Magnesium | 0.071 | 0.072 | 1.00× | 0.067 0.072 0.074 0.073 0.070 |
| Lua 5.4 | 0.138 | 0.137 | 1.91× | 0.137 0.145 0.138 0.137 0.134 | |
| CPython 3 | 0.435 | 0.434 | 6.05× | 0.434 0.445 0.447 0.420 0.430 | |
| coroutine_switch | Magnesium | 0.050 | 0.051 | 1.00× | 0.053 0.051 0.046 0.051 0.050 |
| Lua 5.4 | 0.251 | 0.252 | 4.98× | 0.252 0.254 0.252 0.248 0.251 | |
| CPython 3 | 0.089 | 0.087 | 1.72× | 0.087 0.094 0.086 0.091 0.085 |
The release gate
Every release runs all 16 benchmarks from source and from bytecode, checks that the three languages print the same output, then times five runs after a warmup. If Magnesium’s median is not strictly faster than both Lua 5.4 and CPython on every benchmark, the build fails. No benchmark-only opcodes, no special cases.
Medians of five runs, in seconds, lower is better. Magnesium v1.2.0 built with Clang -O3, Lua 5.4.6 and CPython 3.12.10, on an Intel Core i7-11700KF running Windows 11, October 2026. Averages are geometric means. Run it yourself with make bench.
Bytecode verifier
Damage one byte. Watch it refuse.
Compiled .mgc files are checked before a single instruction runs, so a host can load bytecode it didn’t compile. The test suite damages valid files one field at a time, and the loader must reject every variant.
vm_set_deadlineA time limit for each scriptvm_request_cancelStop it from any threadvm_set_memory_limitA memory cap the collector enforcesvm_set_sandboxNo files, environment, spawning or stray imports
$ ./magnesium game.mgc
13
game.mgc, built with magnesium build from the function above plus print(total(4, 3, 1)). Each damage is one of the malformed-bytecode tests, and the message is the loader’s own.Implementation
Every part, written from scratch.
No parser generator, no JSON library, no runtime dependencies. Just C11.
- 109opcodes
- 4inline caches
- 64bits per value, NaN-boxed
- 14size classes in the slab allocator
vm.cVM
Computed-goto dispatch, 109 opcodes, four inline caches, the standard library, native and FFI calls, threads and coroutines.
compiler.cCompiler
AST to register bytecode: register allocation, peephole fusion, a leaf-function inliner and control-flow lowering.
gc.cGarbage collector
Generational tri-colour mark-and-sweep with write barriers and a per-VM memory limit.
parser.cParser
Recursive descent with Pratt precedence and panic-mode recovery.
typecheck.cType checker
Opt-in. Aliases, structural shapes, forward references.
serialize.cSerializer
The
.mgcformat and its verifier.lsp.cLanguage server
LSP over stdio, framing written by hand.
object.cObjects
Strings with ropes, arrays, dicts, functions.
lexer.cLexer
Table-free scanner, keyword trie.
mt.cmt
Installs the runtime, bindings and editor extension.
Embedding
Drop it into your engine.
A small C API, with safe bindings on top. Scripts declare what the host provides, so the checker and the language server know about it too.
- RustA safe crate over
magnesium-sys; vendored builds by default - C++17Header-only, built with CMake
- C#.NET 8 through P/Invoke
- CThe low-level boundary under all three
!strict
// Host-provided symbols are declared, not defined.
extern fn read_score(player: string): number
extern const MAX_SCORE: number
// Multi-return errors, with ? propagation.
fn get_score(scores, name)
return scores<name>?
end
let scores = &< Ada = 10 >
let value, err = get_score(scores, "Grace")
if err then print(err) end
use magnesium::Vm;
let mut vm = Vm::new();
vm.try_set_global_number("MAX_SCORE", 100.0)?;
vm.try_interpret_named(r#"
!strict
extern const MAX_SCORE: number
print(MAX_SCORE)
"#, "game.mg")?;
typedef Value (*NativeFn)(VM *vm, int arg_count, Value *args);
void vm_register_native(
VM *vm, const char *name, NativeFn function, int arity,
void *userdata, void (*userdata_finalizer)(void *)
);
fn best(scores)
let top = 0
for name, score in scores
if score > top then top = scroe end
end
return top
end
Tooling
A language server in the binary.
magnesium --lsp starts an LSP server over stdio, with no JSON library. The VS Code extension adds a grammar, auto-indent and the Magnesium Dark theme.
- completion
- hover
- signatureHelp
- definition
- rename
- formatting
- documentSymbol
- semanticTokens
- diagnostics
The language server and extension are still a work in progress and not yet at parity with the core language.
Build it in a minute.
You need git, make and GCC or Clang. On Windows, use LLVM-MinGW.
git clone https://github.com/EmeraldStudiosG/magnesium-showcase.git
cd magnesium-showcase
make
./magnesium --version
mt install # runtime, library, headers, VS Code extension
mt install rust # Rust bindings
mt install cpp # C++17 header-only bindings
mt install csharp # .NET 8 bindings
Meet Flow.
MIT licensed.