Conversation
The luavm seam gains a cgo facade over the LuaJIT 2.1 C API that implements the same surface the gopher-lua aliases re-export: registry ref handles for tables/functions/userdata with finalizer-queued release, a C trampoline for Go callbacks with panic-sentinel error propagation (no longjmp across Go frames), an asynchronously armed deadline hook that leaves the JIT enabled, and cgo.Handle-backed userdata payloads released by __gc. Two core scripts relied on gopher-lua quirks and are now correct 5.1: hooks.call preserves argument counts across embedded nils instead of unpacking through an implementation-defined table border, and rune.caller_source tolerates tail-call frame elimination when attributing registrations. The full engine suite passes on both builds; go test -tags luajit ./lua/ is the backend contract. A pathfinding-shaped benchmark runs about 36x faster under the LuaJIT build.
The lua package now drives scripting through the script package's designed contract instead of a gopher-lua-shaped API: host functions receive call scopes whose values die with the call, retention is an explicit pin, composite data crosses as Go trees in single passes, and errors are returned rather than raised through stack machinery. All seventeen API modules, the line type, and the engine dispatch paths are ported; the suite passes unchanged on both backends and remains the backend conformance contract. script/gopherlua adapts gopher-lua to the seam. script/luajit implements it natively over the C API and is simpler than the facade it replaces: scoped registry refs released deterministically instead of finalizer queues, one-pass tree conversion, and pins as plain registry slots. The LuaJIT build also fixes a probabilistic whole-JIT failure: trace mcode must sit within the arm64 +-128MB branch range of the VM, and LuaJIT's hardened allocator probes randomized addresses there. In a Go process that window is often crowded, and a losing process thrashes compile/fail/flush, running slower than gopher-lua. LuaJIT is now statically linked so the VM sits in our text segment, and a loader constructor reserves branch-range address space before the Go runtime can claim it, released at first state creation. A regression test reads LuaJIT's trace log and fails on any mcode allocation failure. Watchdog tests exercise a host-call-bearing runaway on LuaJIT, where compiled traces cannot be interrupted by hooks but C calls abort traces; the bare-loop caveat is documented.
The release stays goreleaser-driven for the pure-Go rune binaries; a native matrix (macOS arm64, Linux amd64/arm64) then builds rune-jit with -tags luajit, runs that platform's engine suite, verifies the binary does not dynamically link LuaJIT, and attaches the archives and checksums to the same release. Beta-style tags publish as prereleases. Linux gains an explicit static-archive link file so distributed binaries run without LuaJIT installed, matching the darwin/arm64 rationale. CI adds a luajit job running vet and the race-enabled engine suite on Linux and macOS. Windows rune-jit is deliberately deferred until a mingw static lane is qualified.
Windows has no system LuaJIT package, so a composite action builds a static libluajit.a from source with mingw-w64 and stages it at the deterministic vendor path link_windows.go expects, exposing the toolchain to the cgo build steps. The release matrix gains windows_amd64, packaged as a zip with the same static-linkage gate (objdump DLL imports), and CI's luajit job now covers Windows so the lane is validated on every push, not just at tag time. The mcode branch-range reservation is POSIX-gated and stubbed on Windows: x64's +-2GB range makes the allocation-failure mode unlikely there, and sys/mman.h does not exist under mingw.
staticcheck cannot see a method reached only through an anonymous interface assertion; PinReleaser is now a named, exported part of the contract. The mcode regression test splices its log path as a Lua long-bracket string so Windows backslashes are not parsed as escapes.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #83 +/- ##
==========================================
- Coverage 70.79% 70.06% -0.74%
==========================================
Files 60 65 +5
Lines 4726 4981 +255
==========================================
+ Hits 3346 3490 +144
- Misses 1197 1309 +112
+ Partials 183 182 -1
🚀 New features to boost your workflow:
|
rune.engine joins rune.version as a Go-set data field, and the startup banner, /version, and the -version flag all report it, so beta testers' reports identify which binary they are running.
Without the LuaJIT headers the tagged targets died inside cgo with a bare "lua.h: No such file or directory", which says nothing about what to install. Add a require-luajit prerequisite to the four targets that compile cgo; it probes the same header paths as script/luajit/link_*.go, falls back to pkg-config, and otherwise prints the per-platform install command. Document those install commands in docs/luajit.md, which previously described the platform archives without saying how to get them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rune's scripting layer now drives its Lua engine through an engine-neutral seam (
scriptpackage) with two backends: gopher-lua (pure Go, the default — unchanged behavior, no cgo) and LuaJIT 2.1 behind-tags luajit. A pathfinding-shaped benchmark runs ~39x faster on the LuaJIT build (563µs → 14.3µs per iteration through the real engine path).The seam
script.Engineandscript.Calldefine the whole contract: values obtained during a host call die with the call scope, retaining a script function requires an explicit pin, composite data crosses the boundary as Go trees in single passes, and host functions return errors instead of manipulating a stack. All seventeen API modules, the line type, and the engine dispatch paths are ported. The existing engine test suite passes unchanged on both backends and serves as the backend conformance contract (go test ./lua/andgo test -tags luajit ./lua/).The scoped-value contract is what keeps the LuaJIT backend simple: registry references are scope-owned and released deterministically — no finalizers, no GC coupling.
Notable fixes along the way
hooks.calltruncated argument lists at an embedded nil (GMCP messages with no body lost theirrawargument), andrune.caller_sourcebroke under real Lua 5.1 tail-call elimination. Both fixed engine-agnostically.TestJITMcodeAllocationreads LuaJIT's own trace log and fails on any mcode allocation failure.Known caveat
The watchdog cannot interrupt a JIT-compiled loop that never exits its trace (compiled code does not poll debug hooks). Loops touching any host function abort traces and stay interruptible — the realistic runaway class in a scripted client. A bare
while true do enddoes not; every shipping JIT-Lua MUD client has the same exposure. Documented indocs/luajit.md.CI/CD
luajitjob (Linux, macOS, Windows) running vet and the race-enabled engine suite, so the JIT backend is validated on every push.rune-jitarchives (macOS arm64, Linux amd64/arm64, Windows amd64) alongside the goreleaser-built purerunebinaries. Each jit build runs its platform's engine suite first and is gated on being statically linked, so testers need no LuaJIT installed. Beta-style tags publish as prereleases.Note for reviewers
The Windows lane is validated by construction but has not yet executed — this PR's CI run is its first real test.