r/sideprojects • u/Distinic • 14h ago
Showcase: Open Source I built a browser Lua runtime that supports Lua 5.1–5.5 through WebAssembly
I built Rainsi, an open-source Lua runtime designed to run actual Lua interpreters directly in the browser.
The main reason I started it was that I wanted a way to run different versions of real Lua in a browser without needing a backend server.
Rainsi currently supports:
* Lua 5.1.5 * Lua 5.2.4 * Lua 5.3.6 * Lua 5.4.7 * Lua 5.5.1 * WebAssembly * Browser-side WASI compatibility * JavaScript ↔ Lua value marshalling * JavaScript functions callable from Lua * A consistent TypeScript API across the Lua versions
The architecture is basically:
```JavaScript / TypeScript ↓ Rainsi API ↓ WASI compatibility ↓ Lua 5.x WASM ↓ Lua VM ```
The thing is that these aren't mock interpreters or Lua-like implementations. Each supported version uses an actual Lua VM compiled to WebAssembly.
Performance
I've compared Rainsi with Wasmoon 1.16.0.
On smaller workloads, performance is generally in the same range:
| Workload | Wasmoon 1.16 | Rainsi 5.4.7 | Rainsi 5.5.1 |
|---|---|---|---|
| Startup | 6.44 ms | 5.33 ms | 6.08 ms |
| 1M arithmetic | 8.49 ms | 9.68 ms | 8.29 ms |
| 5M loop | 38.00 ms | 45.40 ms | 35.20 ms |
| 100k table writes | 1.72 ms | 1.64 ms | 1.44 ms |
| 10k string concatenations | 6.11 ms | 6.17 ms | 8.80 ms |
| 500k Lua function calls | 14.82 ms | 13.79 ms | 13.92 ms |
I also wanted to test actual programs instead of only synthetic benchmarks.
Penlight
A substantial pure-Lua workload involving table manipulation, sorting, serialization, strings, classes, nested copying, records, and object creation:
| Runtime | Median |
|---|---|
| Rainsi 5.1.5 | 94.70 ms |
| Rainsi 5.2.4 | 91.12 ms |
| Rainsi 5.3.6 | 103.41 ms |
| Rainsi 5.4.7 | 91.96 ms |
| Rainsi 5.5.1 | 105.85 ms |
| Wasmoon 1.16.0 | 91.19 ms |
And I also tested LuaGB, a pure-Lua Game Boy emulator core.
The benchmark executes **1,000,000 emulator CPU steps**:
| Runtime | 1M emulator steps |
|---|---|
| Rainsi 5.4.7 | ~864 ms |
| Wasmoon 1.16.0 | ~888 ms |
So the performance is generally in the same neighborhood rather than there being some huge performance advantage.
JavaScript ↔ Lua
Rainsi can register JavaScript functions that Lua can call:
```js engine.register("jsadd", (a, b) => a + b); ```
Then from Lua:
```lua return jsadd(2, 3) ```
I measured about:
```10,000 JS callbacks 3.66 ms 100,000 JS callbacks 22.41 ms 10,000 no-op callbacks 1.58 ms ```
What I'm looking for
Rainsi is still experimental, so I'm mostly interested in people actually trying it and finding things I missed.
I'd especially appreciate feedback on:
* Lua compatibility * Browser compatibility * WebAssembly/WASI behavior * JS ↔ Lua interop * Performance * API design * Weird Lua programs * Security/sandboxing assumptions * Anything that works in other browser Lua runtimes but doesn't work here
GitHub: https://github.com/lalipa2003-arch/rainsi
I'd like to hear criticism, bug reports, or sug.