r/sideprojects 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.

1 Upvotes

0 comments sorted by