Add Lua bytecode → HIR → RV64 → x86-64 JIT pipeline (Phase 2)

Lua scripts compiled by lua_mod.so can now be JIT-compiled through
the existing HIR/RV64/x86-64 pipeline in engine.so. The bytecode
deserializer reads lua_dump() output without requiring Lua headers.

New COM interface mux_IJITCompile on engine.so with CompileLuaBytecode,
RunCompiled, IsCompiled, and Invalidate methods. lua_mod.so acquires
this interface and transparently attempts JIT before falling back to
the Lua VM.

Opcode coverage: data movement (MOVE/LOADI/LOADK/LOADNIL/etc.),
integer arithmetic (ADD/SUB/MUL/IDIV/MOD/UNM + immediate/constant
variants), comparisons (EQ/LT/LE/EQI/LTI/LEI/GTI/GEI), control flow
(JMP/TEST/TESTSET/FORPREP/FORLOOP), returns, and mux.* bridge calls
pattern-matched from GETTABUP+GETFIELD+CALL to engine API ECALLs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stephen Dennis 2026-03-18 09:59:36 -06:00
parent 1fa5282d54
commit 21243e8312
14 changed files with 1596 additions and 11 deletions

View file

@ -299,6 +299,13 @@ void hir_dump(const hir_program &h);
// JIT compiler (jit_compiler.cpp).
void dbt_compile_cleanup(void);
bool run_cached_program(compiled_program *prog,
dbref executor, dbref caller_db,
dbref enactor,
UTF8 *out, size_t out_size,
const UTF8 *cargs[] = nullptr,
int ncargs = 0,
int eval = EV_FCHECK | EV_EVAL);
// Helper used by both hir_lower and hir_codegen.
static inline const UTF8 *u8(const std::string &s) {

View file

@ -31,6 +31,17 @@ static constexpr uint64_t ECALL_ARENA_RELEASE = 0x112; // a0=arena_id
static constexpr uint64_t ECALL_DMA_SUBMIT = 0x120; // a0=window, a1=length, a2=op
static constexpr uint64_t ECALL_DMA_ACK = 0x121; // -> a0=window (next free)
// Lua bridge ECALLs — reserved range for mux.* function dispatch.
static constexpr uint64_t ECALL_LUA_BRIDGE = 0x380; // base for Lua bridge calls
static constexpr uint64_t ECALL_LUA_NAME = 0x380; // mux.name(dbref)
static constexpr uint64_t ECALL_LUA_OWNER = 0x381; // mux.owner(dbref)
static constexpr uint64_t ECALL_LUA_LOCATION = 0x382; // mux.location(dbref)
static constexpr uint64_t ECALL_LUA_GET = 0x383; // mux.get(dbref, attr)
static constexpr uint64_t ECALL_LUA_SET = 0x384; // mux.set(dbref, attr, val)
static constexpr uint64_t ECALL_LUA_NOTIFY = 0x385; // mux.notify(dbref, msg)
static constexpr uint64_t ECALL_LUA_EVAL = 0x386; // mux.eval(expr)
static constexpr uint64_t ECALL_LUA_BRIDGE_MAX= 0x38F;
// Maximum number of indexed functions.
static constexpr int ENGINE_API_MAX_FUNCS = 512;

View file

@ -1180,6 +1180,38 @@ public:
UTF8 *buff, UTF8 **bufc) = 0;
};
// Lua JIT compilation — engine-side bytecode → native compilation.
//
const MUX_CID CID_JITCompile = UINT64_C(0x00000002A8C3D4E5);
const MUX_IID IID_IJITCompile = UINT64_C(0x00000002B9D4E5F6);
interface mux_IJITCompile : public mux_IUnknown
{
public:
// Compile a Lua 5.4 bytecode blob (output of lua_dump).
// On success, stores compiled program and returns key in *pKey.
//
virtual MUX_RESULT CompileLuaBytecode(const uint8_t *pData, size_t nData,
uint64_t *pKey) = 0;
// Run a previously compiled program.
// executor/caller/enactor provide the softcode context.
// pArgs/nArgs are the Lua mux.args[].
//
virtual MUX_RESULT RunCompiled(uint64_t key,
dbref executor, dbref caller, dbref enactor,
const UTF8 *pArgs[], int nArgs,
UTF8 *pResult, size_t nResultMax, size_t *pnResultLen) = 0;
// Check if a key has a compiled program.
//
virtual MUX_RESULT IsCompiled(uint64_t key, bool *pCompiled) = 0;
// Invalidate a compiled program (e.g., source changed).
//
virtual MUX_RESULT Invalidate(uint64_t key) = 0;
};
// Lua scripting module — server-side Lua 5.4 integration.
//
const MUX_CID CID_LuaMod = UINT64_C(0x00000002E1A3B5C7);

View file

@ -19,7 +19,8 @@ ENGINE_CXX_SRC = ast.cpp ast_scan.cpp attrcache.cpp boolexp.cpp \
move.cpp object.cpp predicates.cpp player.cpp player_c.cpp \
plusemail.cpp powers.cpp quota.cpp rob.cpp set.cpp \
dbt.cpp hir_lower.cpp hir_codegen.cpp jit_compiler.cpp dbt_interp.cpp dbt_elf64.cpp \
hir_ssa.cpp hir_opt.cpp session.cpp speech.cpp timer.cpp \
hir_ssa.cpp hir_opt.cpp lua_bytecode.cpp hir_lower_lua.cpp jit_lua.cpp \
session.cpp speech.cpp timer.cpp \
unparse.cpp vattr.cpp walkdb.cpp wild.cpp wiz.cpp
# Add reality levels if enabled

View file

@ -277,8 +277,9 @@ ENGINE_CXX_SRC = ast.cpp ast_scan.cpp attrcache.cpp boolexp.cpp \
move.cpp object.cpp predicates.cpp player.cpp player_c.cpp \
plusemail.cpp powers.cpp quota.cpp rob.cpp set.cpp dbt.cpp \
hir_lower.cpp hir_codegen.cpp jit_compiler.cpp dbt_interp.cpp \
dbt_elf64.cpp hir_ssa.cpp hir_opt.cpp session.cpp speech.cpp \
timer.cpp unparse.cpp vattr.cpp walkdb.cpp wild.cpp wiz.cpp \
dbt_elf64.cpp hir_ssa.cpp hir_opt.cpp lua_bytecode.cpp \
hir_lower_lua.cpp jit_lua.cpp session.cpp speech.cpp timer.cpp \
unparse.cpp vattr.cpp walkdb.cpp wild.cpp wiz.cpp \
$(am__append_1) sqlitedb.cpp sqlite_backend.cpp
ENGINE_CXX_OBJS = $(ENGINE_CXX_SRC:.cpp=.eo)
BUILT_SOURCES = ast_scan.cpp

View file

@ -75,6 +75,10 @@ DEFINE_ENGINE_FACTORY(CPlayerSessionFactory)
DEFINE_ENGINE_FACTORY(CComsysStorageFactory)
DEFINE_ENGINE_FACTORY(CMailStorageFactory)
// JIT Compile factory — implementation delegates to jit_lua.cpp.
extern MUX_RESULT jit_compile_create_instance(MUX_IID iid, void **ppv);
DEFINE_ENGINE_FACTORY(CJITCompileFactory)
// CServerEventsSource component which is not directly accessible.
//
class CServerEventsSource : public mux_IServerEventsControl
@ -5071,6 +5075,53 @@ MUX_RESULT CMailStorageFactory::LockServer(bool bLock)
return MUX_S_OK;
}
// ---------------------------------------------------------------------------
// CJITCompileFactory — Lua bytecode → native JIT compilation.
// ---------------------------------------------------------------------------
CJITCompileFactory::CJITCompileFactory(void) : m_cRef(1) {}
CJITCompileFactory::~CJITCompileFactory() {}
MUX_RESULT CJITCompileFactory::QueryInterface(MUX_IID iid, void **ppv)
{
if (mux_IID_IUnknown == iid)
{
*ppv = static_cast<mux_IUnknown *>(static_cast<mux_IClassFactory *>(this));
}
else if (mux_IID_IClassFactory == iid)
{
*ppv = static_cast<mux_IClassFactory *>(this);
}
else
{
*ppv = nullptr;
return MUX_E_NOINTERFACE;
}
AddRef();
return MUX_S_OK;
}
uint32_t CJITCompileFactory::AddRef(void) { m_cRef++; return m_cRef; }
uint32_t CJITCompileFactory::Release(void)
{
m_cRef--;
if (0 == m_cRef) { delete this; return 0; }
return m_cRef;
}
MUX_RESULT CJITCompileFactory::CreateInstance(mux_IUnknown *pUnknownOuter,
MUX_IID iid, void **ppv)
{
if (nullptr != pUnknownOuter) return MUX_E_NOAGGREGATION;
return jit_compile_create_instance(iid, ppv);
}
MUX_RESULT CJITCompileFactory::LockServer(bool bLock)
{
UNUSED_PARAMETER(bLock);
return MUX_S_OK;
}
// ===========================================================================
// COM Front-Door — engine.so exports only these 4 functions.
// ===========================================================================
@ -5093,6 +5144,7 @@ static MUX_CLASS_INFO engine_classes[] =
{ CID_PlayerSession },
{ CID_ComsysStorage },
{ CID_MailStorage },
{ CID_JITCompile },
};
#define NUM_ENGINE_CLASSES (sizeof(engine_classes)/sizeof(engine_classes[0]))
@ -5127,6 +5179,7 @@ extern "C" MUX_RESULT DCL_EXPORT DCL_API mux_GetClassObject(MUX_CID cid_arg, MUX
MAKE_FACTORY(CPlayerSessionFactory, CID_PlayerSession)
MAKE_FACTORY(CComsysStorageFactory, CID_ComsysStorage)
MAKE_FACTORY(CMailStorageFactory, CID_MailStorage)
MAKE_FACTORY(CJITCompileFactory, CID_JITCompile)
return mr;
}

View file

@ -0,0 +1,603 @@
/*! \file hir_lower_lua.cpp
* \brief Lua 5.4 bytecode HIR lowering.
*
* Two-pass approach:
* Pass 1: scan for basic block boundaries (branch targets).
* Pass 2: walk opcodes, emit HIR instructions.
*
* Lua register map: lua_reg[i] holds the current HIR value number
* for Lua register i. Updated on each register write.
*
* Unsupported opcodes return -1 (caller falls back to Lua VM).
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
#include "dbt_compile.h"
#include "engine_api.h"
#include "lua_bytecode.h"
#include "hir_lower_lua.h"
#include <cstring>
#include <cstdio>
#include <cctype>
#include <vector>
#include <string>
// Maximum Lua registers we track.
static constexpr int MAX_LUA_REGS = 256;
// ---------------------------------------------------------------
// Pass 1: find basic block boundaries
// ---------------------------------------------------------------
static void find_block_starts(const lua_bc_proto *proto,
std::vector<bool> &is_leader) {
int n = static_cast<int>(proto->code.size());
is_leader.assign(n, false);
if (n > 0) is_leader[0] = true;
for (int pc = 0; pc < n; pc++) {
const lua_bc_instruction &insn = proto->code[pc];
int op = insn.opcode();
switch (op) {
case OP_LUA_JMP: {
int target = pc + 1 + insn.sJ();
if (target >= 0 && target < n) is_leader[target] = true;
if (pc + 1 < n) is_leader[pc + 1] = true;
break;
}
case OP_LUA_FORLOOP: {
int target = pc + 1 + insn.sBx();
if (target >= 0 && target < n) is_leader[target] = true;
if (pc + 1 < n) is_leader[pc + 1] = true;
break;
}
case OP_LUA_FORPREP: {
int target = pc + 1 + insn.sBx();
if (target >= 0 && target < n) is_leader[target] = true;
if (pc + 1 < n) is_leader[pc + 1] = true;
break;
}
case OP_LUA_EQ:
case OP_LUA_LT:
case OP_LUA_LE:
case OP_LUA_EQI:
case OP_LUA_LTI:
case OP_LUA_LEI:
case OP_LUA_GTI:
case OP_LUA_GEI:
case OP_LUA_TEST:
case OP_LUA_TESTSET:
if (pc + 1 < n) is_leader[pc + 1] = true;
if (pc + 2 < n) is_leader[pc + 2] = true;
break;
case OP_LUA_RETURN:
case OP_LUA_RETURN0:
case OP_LUA_RETURN1:
if (pc + 1 < n) is_leader[pc + 1] = true;
break;
default:
break;
}
}
}
// ---------------------------------------------------------------
// Block mapping
// ---------------------------------------------------------------
static int assign_blocks(const std::vector<bool> &is_leader,
std::vector<int> &pc_to_block, int n) {
int block_count = 0;
pc_to_block.resize(n, -1);
for (int pc = 0; pc < n; pc++) {
if (is_leader[pc]) block_count++;
pc_to_block[pc] = block_count - 1;
}
return block_count;
}
// ---------------------------------------------------------------
// Helper: load a Lua constant into HIR.
// ---------------------------------------------------------------
static int emit_lua_constant(hir_program &h, rv_compiler &rc,
const lua_bc_constant &k) {
switch (k.type) {
case LUA_BC_TNIL:
return h.emit_sconst(rc.pool_str("", 0), "");
case LUA_BC_TFALSE:
return h.emit_iconst(0);
case LUA_BC_TTRUE:
return h.emit_iconst(1);
case LUA_BC_TINT:
return h.emit_iconst(k.ival);
case LUA_BC_TFLOAT: {
char buf[64];
snprintf(buf, sizeof(buf), "%g", k.fval);
uint64_t addr = rc.pool_str(buf, strlen(buf));
return h.emit_sconst(addr, std::string(buf));
}
case LUA_BC_TSHRSTR:
case LUA_BC_TLNGSTR: {
uint64_t addr = rc.pool_str(k.sval.c_str(), k.sval.size());
return h.emit_sconst(addr, k.sval);
}
default:
return -1;
}
}
// ---------------------------------------------------------------
// Helper: emit a comparison + branch pattern.
// Many Lua comparison opcodes share the same structure:
// compare → optional negate (k bit) → read JMP → emit BRC
// ---------------------------------------------------------------
static int emit_cmp_branch(hir_program &h, int cmp, int k_bit,
const lua_bc_proto *proto, int pc,
const std::vector<int> &pc_to_block,
int cur_hir_block, int n) {
if (cmp < 0) return -1;
if (k_bit) {
cmp = h.emit(HIR_NOT, TY_INT, cmp);
if (cmp < 0) return -1;
}
if (pc + 1 >= n) return -1;
const lua_bc_instruction &jmp_insn = proto->code[pc + 1];
if (jmp_insn.opcode() != OP_LUA_JMP) return -1;
int true_target = pc + 2 + jmp_insn.sJ();
int false_target = pc + 2;
int true_blk = (true_target >= 0 && true_target < n) ? pc_to_block[true_target] : -1;
int false_blk = (false_target >= 0 && false_target < n) ? pc_to_block[false_target] : -1;
if (true_blk < 0 || false_blk < 0) return -1;
h.emit(HIR_BRC, TY_VOID, cmp, false_blk, true_blk);
h.add_edge(cur_hir_block, true_blk);
h.add_edge(cur_hir_block, false_blk);
return 0; // success
}
// ---------------------------------------------------------------
// Pass 2: emit HIR
// ---------------------------------------------------------------
int hir_lower_lua_proto(hir_program &h, rv_compiler &rc,
const lua_bc_proto *proto) {
if (nullptr == proto) return -1;
int n = static_cast<int>(proto->code.size());
if (n == 0) return -1;
// Pass 1: find block boundaries.
std::vector<bool> is_leader;
find_block_starts(proto, is_leader);
std::vector<int> pc_to_block;
int num_blocks = assign_blocks(is_leader, pc_to_block, n);
bool multi_block = (num_blocks > 1);
// Allocate HIR blocks.
if (multi_block) {
for (int b = 1; b < num_blocks; b++) {
int nb = h.new_block();
if (nb < 0) return -1;
}
}
// Lua register → HIR value map.
int lua_reg[MAX_LUA_REGS];
memset(lua_reg, -1, sizeof(lua_reg));
int cur_hir_block = 0;
h.cur_block = 0;
int result_val = -1;
for (int pc = 0; pc < n; pc++) {
// Switch blocks if this PC is a leader.
if (is_leader[pc] && pc > 0) {
int new_block = pc_to_block[pc];
if (new_block != cur_hir_block) {
if (h.n_insns > 0) {
hir_kind last = h.kind[h.n_insns - 1];
if (last != HIR_BR && last != HIR_BRC && last != HIR_RET) {
h.emit(HIR_BR, TY_VOID, -1, -1, new_block);
h.add_edge(cur_hir_block, new_block);
}
}
cur_hir_block = new_block;
h.cur_block = new_block;
}
}
const lua_bc_instruction &insn = proto->code[pc];
int op = insn.opcode();
int A = insn.A();
switch (op) {
// ---- Data movement ----
case OP_LUA_MOVE:
if (lua_reg[insn.B()] < 0) return -1;
lua_reg[A] = lua_reg[insn.B()];
break;
case OP_LUA_LOADI:
lua_reg[A] = h.emit_iconst(insn.sBx());
if (lua_reg[A] < 0) return -1;
break;
case OP_LUA_LOADF:
lua_reg[A] = h.emit_iconst(insn.sBx());
if (lua_reg[A] < 0) return -1;
break;
case OP_LUA_LOADK: {
int kidx = insn.Bx();
if (kidx < 0 || kidx >= static_cast<int>(proto->constants.size()))
return -1;
lua_reg[A] = emit_lua_constant(h, rc, proto->constants[kidx]);
if (lua_reg[A] < 0) return -1;
break;
}
case OP_LUA_LOADFALSE:
case OP_LUA_LFALSESKIP:
lua_reg[A] = h.emit_iconst(0);
if (lua_reg[A] < 0) return -1;
if (op == OP_LUA_LFALSESKIP) pc++;
break;
case OP_LUA_LOADTRUE:
lua_reg[A] = h.emit_iconst(1);
if (lua_reg[A] < 0) return -1;
break;
case OP_LUA_LOADNIL:
for (int i = A; i <= A + insn.B(); i++) {
lua_reg[i] = h.emit_sconst(rc.pool_str("", 0), "");
if (lua_reg[i] < 0) return -1;
}
break;
// ---- Integer arithmetic ----
#define ARITH_RR(HIR_OP, MMOP) \
{ \
int rb = lua_reg[insn.B()]; \
int rc_val = lua_reg[insn.C()]; \
if (rb < 0 || rc_val < 0) return -1; \
lua_reg[A] = h.emit(HIR_OP, TY_INT, rb, rc_val); \
if (lua_reg[A] < 0) return -1; \
h.native_ops++; \
if (pc + 1 < n && proto->code[pc + 1].opcode() == MMOP) pc++; \
break; \
}
case OP_LUA_ADD: ARITH_RR(HIR_ADD, OP_LUA_MMBIN)
case OP_LUA_SUB: ARITH_RR(HIR_SUB, OP_LUA_MMBIN)
case OP_LUA_MUL: ARITH_RR(HIR_MUL, OP_LUA_MMBIN)
case OP_LUA_IDIV: ARITH_RR(HIR_DIV, OP_LUA_MMBIN)
case OP_LUA_MOD: ARITH_RR(HIR_REM, OP_LUA_MMBIN)
#undef ARITH_RR
case OP_LUA_UNM: {
int rb = lua_reg[insn.B()];
if (rb < 0) return -1;
lua_reg[A] = h.emit(HIR_NEG, TY_INT, rb);
if (lua_reg[A] < 0) return -1;
h.native_ops++;
if (pc + 1 < n && proto->code[pc + 1].opcode() == OP_LUA_MMBIN)
pc++;
break;
}
// ---- Immediate arithmetic ----
case OP_LUA_ADDI: {
int rb = lua_reg[insn.B()];
if (rb < 0) return -1;
int imm_val = h.emit_iconst(insn.sC());
if (imm_val < 0) return -1;
lua_reg[A] = h.emit(HIR_ADD, TY_INT, rb, imm_val);
if (lua_reg[A] < 0) return -1;
h.native_ops++;
if (pc + 1 < n && proto->code[pc + 1].opcode() == OP_LUA_MMBINI)
pc++;
break;
}
// ---- Constant arithmetic ----
#define ARITH_RK(HIR_OP) \
{ \
int rb = lua_reg[insn.B()]; \
if (rb < 0) return -1; \
int kidx = insn.C(); \
if (kidx < 0 || kidx >= static_cast<int>(proto->constants.size())) \
return -1; \
int kval = emit_lua_constant(h, rc, proto->constants[kidx]); \
if (kval < 0) return -1; \
lua_reg[A] = h.emit(HIR_OP, TY_INT, rb, kval); \
if (lua_reg[A] < 0) return -1; \
h.native_ops++; \
if (pc + 1 < n && proto->code[pc + 1].opcode() == OP_LUA_MMBINK) \
pc++; \
break; \
}
case OP_LUA_ADDK: ARITH_RK(HIR_ADD)
case OP_LUA_SUBK: ARITH_RK(HIR_SUB)
case OP_LUA_MULK: ARITH_RK(HIR_MUL)
#undef ARITH_RK
// ---- Comparisons ----
// All share: compare → optional negate → JMP → BRC
#define CMP_RR(HIR_OP) \
{ \
int rb = lua_reg[A]; \
int rc_val = lua_reg[insn.B()]; \
if (rb < 0 || rc_val < 0) return -1; \
int cmp = h.emit(HIR_OP, TY_INT, rb, rc_val); \
h.native_ops++; \
if (!multi_block) return -1; \
if (emit_cmp_branch(h, cmp, insn.k(), proto, pc, pc_to_block, \
cur_hir_block, n) < 0) return -1; \
pc++; \
break; \
}
#define CMP_RI(HIR_OP) \
{ \
int rb = lua_reg[A]; \
if (rb < 0) return -1; \
int imm_val = h.emit_iconst(insn.sB()); \
if (imm_val < 0) return -1; \
int cmp = h.emit(HIR_OP, TY_INT, rb, imm_val); \
h.native_ops++; \
if (!multi_block) return -1; \
if (emit_cmp_branch(h, cmp, insn.k(), proto, pc, pc_to_block, \
cur_hir_block, n) < 0) return -1; \
pc++; \
break; \
}
case OP_LUA_EQ: CMP_RR(HIR_EQ)
case OP_LUA_LT: CMP_RR(HIR_LT)
case OP_LUA_LE: CMP_RR(HIR_LE)
case OP_LUA_EQI: CMP_RI(HIR_EQ)
case OP_LUA_LTI: CMP_RI(HIR_LT)
case OP_LUA_LEI: CMP_RI(HIR_LE)
case OP_LUA_GTI: CMP_RI(HIR_GT)
case OP_LUA_GEI: CMP_RI(HIR_GE)
#undef CMP_RR
#undef CMP_RI
case OP_LUA_TEST: {
int rb = lua_reg[A];
if (rb < 0) return -1;
int cmp = h.emit(HIR_BOOL, TY_INT, rb);
if (!multi_block) return -1;
if (emit_cmp_branch(h, cmp, insn.k(), proto, pc, pc_to_block,
cur_hir_block, n) < 0) return -1;
pc++;
break;
}
case OP_LUA_TESTSET: {
int rb = lua_reg[insn.B()];
if (rb < 0) return -1;
int cmp = h.emit(HIR_BOOL, TY_INT, rb);
lua_reg[A] = rb; // Simplified: always copy.
if (!multi_block) return -1;
if (emit_cmp_branch(h, cmp, insn.k(), proto, pc, pc_to_block,
cur_hir_block, n) < 0) return -1;
pc++;
break;
}
// ---- Control flow ----
case OP_LUA_JMP: {
int target = pc + 1 + insn.sJ();
if (!multi_block) return -1;
int target_blk = (target >= 0 && target < n) ? pc_to_block[target] : -1;
if (target_blk < 0) return -1;
h.emit(HIR_BR, TY_VOID, -1, -1, target_blk);
h.add_edge(cur_hir_block, target_blk);
break;
}
// ---- Numeric for loop ----
case OP_LUA_FORPREP: {
if (!multi_block) return -1;
if (lua_reg[A] < 0 || lua_reg[A + 1] < 0 || lua_reg[A + 2] < 0)
return -1;
lua_reg[A + 3] = lua_reg[A];
int target = pc + 1 + insn.sBx();
int target_blk = (target >= 0 && target < n) ? pc_to_block[target] : -1;
if (target_blk < 0) return -1;
h.emit(HIR_BR, TY_VOID, -1, -1, target_blk);
h.add_edge(cur_hir_block, target_blk);
break;
}
case OP_LUA_FORLOOP: {
if (!multi_block) return -1;
int idx = lua_reg[A + 3];
int step = lua_reg[A + 2];
int limit = lua_reg[A + 1];
if (idx < 0 || step < 0 || limit < 0) return -1;
int new_idx = h.emit(HIR_ADD, TY_INT, idx, step);
if (new_idx < 0) return -1;
lua_reg[A + 3] = new_idx;
lua_reg[A] = new_idx;
h.native_ops++;
int cmp = h.emit(HIR_LE, TY_INT, new_idx, limit);
if (cmp < 0) return -1;
h.native_ops++;
int loop_target = pc + 1 + insn.sBx();
int exit_target = pc + 1;
int loop_blk = (loop_target >= 0 && loop_target < n) ? pc_to_block[loop_target] : -1;
int exit_blk = (exit_target >= 0 && exit_target < n) ? pc_to_block[exit_target] : -1;
if (loop_blk < 0 || exit_blk < 0) return -1;
h.emit(HIR_BRC, TY_VOID, cmp, exit_blk, loop_blk);
h.add_edge(cur_hir_block, loop_blk);
h.add_edge(cur_hir_block, exit_blk);
break;
}
// ---- Return ----
case OP_LUA_RETURN0:
result_val = h.emit_sconst(rc.pool_str("", 0), "");
if (result_val < 0) return -1;
h.emit(HIR_RET, TY_VOID, result_val);
break;
case OP_LUA_RETURN1: {
int rv = lua_reg[A];
if (rv < 0) return -1;
if (h.ty[rv] == TY_INT) {
rv = h.emit(HIR_ITOA, TY_STRING, rv);
if (rv < 0) return -1;
}
result_val = rv;
h.emit(HIR_RET, TY_VOID, result_val);
break;
}
case OP_LUA_RETURN: {
int nret = insn.B() - 1;
if (nret < 0) return -1;
if (nret == 0) {
result_val = h.emit_sconst(rc.pool_str("", 0), "");
if (result_val < 0) return -1;
} else {
int rv = lua_reg[A];
if (rv < 0) return -1;
if (h.ty[rv] == TY_INT) {
rv = h.emit(HIR_ITOA, TY_STRING, rv);
if (rv < 0) return -1;
}
result_val = rv;
}
h.emit(HIR_RET, TY_VOID, result_val);
break;
}
// ---- Table access: mux.* bridge pattern ----
case OP_LUA_GETTABUP: {
if (insn.B() != 0) return -1; // Only _ENV.
int kidx = insn.C();
if (kidx < 0 || kidx >= static_cast<int>(proto->constants.size()))
return -1;
const lua_bc_constant &k = proto->constants[kidx];
if (k.type != LUA_BC_TSHRSTR && k.type != LUA_BC_TLNGSTR)
return -1;
if (k.sval != "mux") return -1;
uint64_t addr = rc.pool_str("mux", 3);
lua_reg[A] = h.emit_sconst(addr, "mux");
if (lua_reg[A] < 0) return -1;
break;
}
case OP_LUA_GETFIELD: {
int table_reg = lua_reg[insn.B()];
if (table_reg < 0) return -1;
int kidx = insn.C();
if (kidx < 0 || kidx >= static_cast<int>(proto->constants.size()))
return -1;
const lua_bc_constant &k = proto->constants[kidx];
if (k.type != LUA_BC_TSHRSTR && k.type != LUA_BC_TLNGSTR)
return -1;
if (table_reg >= 0 && h.kind[table_reg] == HIR_SCONST
&& h.sval[table_reg] == "mux") {
std::string name = "mux." + k.sval;
uint64_t addr = rc.pool_str(name.c_str(), name.size());
lua_reg[A] = h.emit_sconst(addr, name);
if (lua_reg[A] < 0) return -1;
} else {
return -1;
}
break;
}
case OP_LUA_CALL: {
int func_reg = lua_reg[A];
if (func_reg < 0) return -1;
int nargs = insn.B() - 1;
int nresults = insn.C() - 1;
if (h.kind[func_reg] != HIR_SCONST) return -1;
const std::string &fname = h.sval[func_reg];
if (fname.substr(0, 4) != "mux.") return -1;
std::string bridge_name = fname.substr(4);
std::string upper_name;
for (char c : bridge_name) {
upper_name += static_cast<char>(toupper(static_cast<unsigned char>(c)));
}
std::vector<int> args;
for (int i = 0; i < nargs; i++) {
int areg = lua_reg[A + 1 + i];
if (areg < 0) return -1;
if (h.ty[areg] == TY_INT) {
areg = h.emit(HIR_ITOA, TY_STRING, areg);
if (areg < 0) return -1;
}
args.push_back(areg);
}
int fidx = engine_api_lookup(upper_name.c_str());
int call_val;
if (fidx > 0) {
call_val = h.emit_call(TY_STRING, fidx,
args.data(), static_cast<int>(args.size()));
} else {
call_val = h.emit_call(TY_STRING, 0,
args.data(), static_cast<int>(args.size()),
&upper_name);
}
if (call_val < 0) return -1;
h.ecalls++;
if (nresults >= 1) {
lua_reg[A] = call_val;
}
break;
}
// ---- No-op instructions ----
case OP_LUA_VARARGPREP:
case OP_LUA_MMBIN:
case OP_LUA_MMBINI:
case OP_LUA_MMBINK:
break;
// ---- Unsupported opcodes ----
default:
return -1;
}
}
if (result_val < 0) return -1;
h.result = result_val;
h.needs_jit = (h.ecalls > 0 || h.native_ops > 0);
return result_val;
}

View file

@ -0,0 +1,19 @@
/*! \file hir_lower_lua.h
* \brief Lua bytecode HIR lowering.
*/
#ifndef HIR_LOWER_LUA_H
#define HIR_LOWER_LUA_H
struct hir_program;
struct rv_compiler;
struct lua_bc_proto;
// Lower a Lua 5.4 function prototype to HIR instructions.
// Returns the HIR instruction index of the result, or -1 on failure
// (unsupported opcode → caller falls back to Lua VM).
//
int hir_lower_lua_proto(hir_program &h, rv_compiler &rc,
const lua_bc_proto *proto);
#endif // HIR_LOWER_LUA_H

View file

@ -926,13 +926,13 @@ static compiled_program *compile_cached(const UTF8 *expr, size_t nLen,
// Run a cached program. Uses dbt_rerun if the DBT already has
// translated blocks for this program, otherwise dbt_reset.
//
static bool run_cached_program(compiled_program *prog,
dbref executor, dbref caller_db,
dbref enactor,
UTF8 *out, size_t out_size,
const UTF8 *cargs[] = nullptr,
int ncargs = 0,
int eval = EV_FCHECK | EV_EVAL) {
bool run_cached_program(compiled_program *prog,
dbref executor, dbref caller_db,
dbref enactor,
UTF8 *out, size_t out_size,
const UTF8 *cargs[],
int ncargs,
int eval) {
if (!prog->needs_jit) {
const char *r = reinterpret_cast<const char *>(
prog->memory.data() + prog->out_addr);

View file

@ -0,0 +1,206 @@
/*! \file jit_lua.cpp
* \brief CJITCompile COM class Lua bytecode native JIT compilation.
*
* Implements mux_IJITCompile. Deserializes Lua 5.4 bytecode,
* lowers through HIR/RV64/x86-64 pipeline, caches compiled programs.
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
#include "dbt_compile.h"
#include "engine_api.h"
#include "lua_bytecode.h"
#include "hir_lower_lua.h"
#include <cstring>
#include <cstdio>
#include <unordered_map>
#include <vector>
#include <string>
// ---------------------------------------------------------------
// Compile cache
// ---------------------------------------------------------------
static std::unordered_map<uint64_t, compiled_program> s_lua_cache;
static uint64_t s_next_key = 1;
// ---------------------------------------------------------------
// Statistics
// ---------------------------------------------------------------
struct lua_jit_stats {
uint64_t compile_ok;
uint64_t compile_fail;
uint64_t run_ok;
uint64_t run_fail;
uint64_t cache_hits;
uint64_t invalidations;
};
static lua_jit_stats s_lua_jit_stats = {};
// ---------------------------------------------------------------
// Compile a Lua bytecode blob to a compiled_program.
// ---------------------------------------------------------------
static bool compile_lua_bytecode(const uint8_t *data, size_t len,
compiled_program *out) {
// Deserialize.
lua_bc_chunk chunk;
if (!lua_bc_load(data, len, &chunk)) {
return false;
}
// Create HIR program and RV64 compiler state.
hir_program *h = new hir_program;
h->init();
rv_compiler rc_state;
// Lower Lua bytecode to HIR.
int result = hir_lower_lua_proto(*h, rc_state, &chunk.main);
if (result < 0) {
delete h;
return false;
}
// For multi-block programs, run SSA construction and optimization.
if (h->n_blocks > 1) {
hir_build_cfg(*h);
hir_ssa_construct(*h);
hir_optimize(*h);
} else {
// Single block: just constant folding.
hir_const_fold(*h);
}
// Code generation: HIR → RV64.
hir_codegen(*h, rc_state);
// Build compiled_program output.
out->memory = std::move(rc_state.memory);
out->memory_size = rv_compiler::MEM_SIZE;
out->out_addr = rc_state.final_out;
out->out_used = rc_state.out_pool;
out->ok = true;
out->folds = h->folds;
out->ecalls = h->ecalls;
out->tier2_calls = 0;
out->native_ops = h->native_ops;
out->needs_jit = h->needs_jit;
delete h;
return true;
}
// ---------------------------------------------------------------
// CJITCompile COM class
// ---------------------------------------------------------------
class CJITCompile : public mux_IJITCompile
{
public:
CJITCompile(void) : m_cRef(1) {}
virtual ~CJITCompile() {}
// mux_IUnknown
MUX_RESULT QueryInterface(MUX_IID iid, void **ppv) override {
if (mux_IID_IUnknown == iid) {
*ppv = static_cast<mux_IUnknown *>(static_cast<mux_IJITCompile *>(this));
} else if (IID_IJITCompile == iid) {
*ppv = static_cast<mux_IJITCompile *>(this);
} else {
*ppv = nullptr;
return MUX_E_NOINTERFACE;
}
AddRef();
return MUX_S_OK;
}
uint32_t AddRef(void) override { return ++m_cRef; }
uint32_t Release(void) override {
uint32_t n = --m_cRef;
if (0 == n) delete this;
return n;
}
// mux_IJITCompile
MUX_RESULT CompileLuaBytecode(const uint8_t *pData, size_t nData,
uint64_t *pKey) override
{
if (nullptr == pData || nullptr == pKey) return MUX_E_INVALIDARG;
compiled_program prog;
if (!compile_lua_bytecode(pData, nData, &prog)) {
s_lua_jit_stats.compile_fail++;
*pKey = 0;
return MUX_E_FAIL;
}
uint64_t key = s_next_key++;
s_lua_cache[key] = std::move(prog);
*pKey = key;
s_lua_jit_stats.compile_ok++;
return MUX_S_OK;
}
MUX_RESULT RunCompiled(uint64_t key,
dbref executor, dbref caller, dbref enactor,
const UTF8 *pArgs[], int nArgs,
UTF8 *pResult, size_t nResultMax, size_t *pnResultLen) override
{
auto it = s_lua_cache.find(key);
if (it == s_lua_cache.end()) return MUX_E_NOTFOUND;
s_lua_jit_stats.cache_hits++;
bool ok = run_cached_program(&it->second, executor, caller, enactor,
pResult, nResultMax, pArgs, nArgs);
if (!ok) {
s_lua_jit_stats.run_fail++;
return MUX_E_FAIL;
}
if (pnResultLen) {
*pnResultLen = strlen(reinterpret_cast<const char *>(pResult));
}
s_lua_jit_stats.run_ok++;
return MUX_S_OK;
}
MUX_RESULT IsCompiled(uint64_t key, bool *pCompiled) override {
if (nullptr == pCompiled) return MUX_E_INVALIDARG;
*pCompiled = (s_lua_cache.find(key) != s_lua_cache.end());
return MUX_S_OK;
}
MUX_RESULT Invalidate(uint64_t key) override {
auto it = s_lua_cache.find(key);
if (it != s_lua_cache.end()) {
s_lua_cache.erase(it);
s_lua_jit_stats.invalidations++;
}
return MUX_S_OK;
}
private:
uint32_t m_cRef;
};
// ---------------------------------------------------------------
// Factory creation function — called from engine_com.cpp.
// ---------------------------------------------------------------
MUX_RESULT jit_compile_create_instance(MUX_IID iid, void **ppv) {
CJITCompile *pObj = nullptr;
try { pObj = new CJITCompile; } catch (...) { ; }
if (nullptr == pObj) return MUX_E_OUTOFMEMORY;
MUX_RESULT mr = pObj->QueryInterface(iid, ppv);
pObj->Release();
return mr;
}

View file

@ -0,0 +1,296 @@
/*! \file lua_bytecode.cpp
* \brief Lua 5.4 bytecode deserializer standalone, no Lua headers.
*
* Reads the binary format produced by lua_dump(). Reference:
* Lua 5.4 source: ldump.c / lundump.c
*/
#include "lua_bytecode.h"
#include <cstring>
// Lua 5.4 dump signature and magic values.
//
static const uint8_t LUA_SIGNATURE[] = { 0x1B, 'L', 'u', 'a' };
static const uint8_t LUAC_DATA[] = { 0x19, 0x93, '\r', '\n', 0x1A, '\n' };
static constexpr uint8_t LUAC_VERSION = 0x54; // Lua 5.4
static constexpr uint8_t LUAC_FORMAT = 0;
static constexpr int64_t LUAC_INT = 0x5678;
static constexpr double LUAC_NUM = 370.5;
// ---------------------------------------------------------------
// Reader helper — a simple cursor over a byte buffer.
// ---------------------------------------------------------------
struct bc_reader {
const uint8_t *data;
size_t len;
size_t pos;
bool ok;
bc_reader(const uint8_t *d, size_t l)
: data(d), len(l), pos(0), ok(true) {}
bool has(size_t n) const { return pos + n <= len; }
uint8_t read_byte() {
if (!has(1)) { ok = false; return 0; }
return data[pos++];
}
void read_bytes(void *dst, size_t n) {
if (!has(n)) { ok = false; return; }
memcpy(dst, data + pos, n);
pos += n;
}
bool match(const void *expected, size_t n) {
if (!has(n)) { ok = false; return false; }
bool m = (memcmp(data + pos, expected, n) == 0);
pos += n;
if (!m) ok = false;
return m;
}
// Lua uses a variable-length unsigned integer encoding for sizes.
// lundump.c LoadUnsigned: accumulate 7-bit groups, high bit = more.
//
size_t read_size() {
size_t x = 0;
int b = read_byte();
if (!ok) return 0;
while ((b & 0x80) == 0) {
x = (x << 7) | b;
b = read_byte();
if (!ok) return 0;
}
x = (x << 7) | (b & 0x7F);
return x;
}
int64_t read_integer() {
int64_t v = 0;
read_bytes(&v, 8);
return v;
}
double read_number() {
double v = 0;
read_bytes(&v, 8);
return v;
}
int read_int() {
int v = 0;
read_bytes(&v, 4);
return v;
}
std::string read_string() {
size_t sz = read_size();
if (!ok) return "";
if (sz == 0) return "";
// In Lua 5.4 dump format, sz is the length + 1.
// The actual string length is sz - 1.
sz -= 1;
if (!has(sz)) { ok = false; return ""; }
std::string s(reinterpret_cast<const char *>(data + pos), sz);
pos += sz;
return s;
}
};
// ---------------------------------------------------------------
// Proto loader (recursive).
// ---------------------------------------------------------------
static bool load_proto(bc_reader &r, lua_bc_proto *p, const std::string &parent_source);
static bool load_constants(bc_reader &r, lua_bc_proto *p) {
int n = static_cast<int>(r.read_size());
if (!r.ok) return false;
p->constants.resize(n);
for (int i = 0; i < n; i++) {
uint8_t t = r.read_byte();
if (!r.ok) return false;
lua_bc_constant &k = p->constants[i];
k.type = static_cast<lua_bc_const_type>(t);
k.ival = 0;
k.fval = 0.0;
switch (t) {
case LUA_BC_TNIL:
case LUA_BC_TFALSE:
case LUA_BC_TTRUE:
break;
case LUA_BC_TINT:
k.ival = r.read_integer();
break;
case LUA_BC_TFLOAT:
k.fval = r.read_number();
break;
case LUA_BC_TSHRSTR:
case LUA_BC_TLNGSTR:
k.sval = r.read_string();
break;
default:
r.ok = false;
return false;
}
}
return r.ok;
}
static bool load_upvalues(bc_reader &r, lua_bc_proto *p) {
int n = static_cast<int>(r.read_size());
if (!r.ok) return false;
p->upvalues.resize(n);
for (int i = 0; i < n; i++) {
p->upvalues[i].instack = r.read_byte();
p->upvalues[i].idx = r.read_byte();
p->upvalues[i].kind = r.read_byte();
if (!r.ok) return false;
}
return true;
}
static bool load_protos(bc_reader &r, lua_bc_proto *p,
const std::string &parent_source) {
int n = static_cast<int>(r.read_size());
if (!r.ok) return false;
p->protos.resize(n);
for (int i = 0; i < n; i++) {
if (!load_proto(r, &p->protos[i], parent_source))
return false;
}
return true;
}
static bool skip_debug(bc_reader &r, lua_bc_proto *p) {
// Line info.
int n = static_cast<int>(r.read_size());
if (!r.ok) return false;
for (int i = 0; i < n; i++) {
r.read_byte();
if (!r.ok) return false;
}
// Abs line info.
n = static_cast<int>(r.read_size());
if (!r.ok) return false;
for (int i = 0; i < n; i++) {
r.read_int(); // pc
r.read_int(); // line
if (!r.ok) return false;
}
// Local variables.
n = static_cast<int>(r.read_size());
if (!r.ok) return false;
for (int i = 0; i < n; i++) {
r.read_string(); // varname
r.read_int(); // startpc
r.read_int(); // endpc
if (!r.ok) return false;
}
// Upvalue names.
n = static_cast<int>(r.read_size());
if (!r.ok) return false;
for (int i = 0; i < n; i++) {
r.read_string();
if (!r.ok) return false;
}
(void)p;
return true;
}
static bool load_proto(bc_reader &r, lua_bc_proto *p,
const std::string &parent_source) {
// Source name.
p->source = r.read_string();
if (!r.ok) return false;
if (p->source.empty()) p->source = parent_source;
p->linedefined = r.read_int();
p->lastlinedefined = r.read_int();
p->numparams = r.read_byte();
p->is_vararg = r.read_byte();
p->maxstacksize = r.read_byte();
if (!r.ok) return false;
// Instructions.
int ncode = static_cast<int>(r.read_size());
if (!r.ok) return false;
p->code.resize(ncode);
for (int i = 0; i < ncode; i++) {
uint32_t w = 0;
r.read_bytes(&w, 4);
if (!r.ok) return false;
p->code[i].raw = w;
}
// Constants.
if (!load_constants(r, p)) return false;
// Upvalues.
if (!load_upvalues(r, p)) return false;
// Nested protos.
if (!load_protos(r, p, p->source)) return false;
// Debug info (skip).
if (!skip_debug(r, p)) return false;
return r.ok;
}
// ---------------------------------------------------------------
// Top-level entry point.
// ---------------------------------------------------------------
bool lua_bc_load(const uint8_t *data, size_t len, lua_bc_chunk *out) {
if (nullptr == data || nullptr == out) return false;
if (len < 4) return false;
bc_reader r(data, len);
// Header: signature.
if (!r.match(LUA_SIGNATURE, sizeof(LUA_SIGNATURE))) return false;
// Version.
out->version = r.read_byte();
if (!r.ok || out->version != LUAC_VERSION) return false;
// Format.
out->format = r.read_byte();
if (!r.ok || out->format != LUAC_FORMAT) return false;
// LUAC_DATA magic.
if (!r.match(LUAC_DATA, sizeof(LUAC_DATA))) return false;
// Type sizes.
out->insn_size = r.read_byte(); // sizeof(Instruction) = 4
out->lua_int_size = r.read_byte(); // sizeof(lua_Integer) = 8
out->lua_num_size = r.read_byte(); // sizeof(lua_Number) = 8
if (!r.ok) return false;
if (out->insn_size != 4 || out->lua_int_size != 8 || out->lua_num_size != 8)
return false;
// Check integer.
int64_t check_int = r.read_integer();
if (!r.ok || check_int != LUAC_INT) return false;
// Check number.
double check_num = r.read_number();
if (!r.ok || check_num != LUAC_NUM) return false;
// Number of upvalues for main proto.
out->num_upvalues = r.read_byte();
if (!r.ok) return false;
// Load main proto.
if (!load_proto(r, &out->main, "")) return false;
return r.ok;
}

View file

@ -0,0 +1,224 @@
/*! \file lua_bytecode.h
* \brief Lua 5.4 bytecode structures standalone deserializer.
*
* Our own Lua 5.4 bytecode structures. No Lua headers needed.
* engine.so reads the output of lua_dump() from lua_mod.so and
* lowers it through the HIR/RV64/x86-64 pipeline.
*/
#ifndef LUA_BYTECODE_H
#define LUA_BYTECODE_H
#include <cstdint>
#include <cstddef>
#include <string>
#include <vector>
// ---------------------------------------------------------------
// Lua 5.4 opcodes (all 83)
// ---------------------------------------------------------------
enum lua_bc_opcode {
OP_LUA_MOVE, // 0
OP_LUA_LOADI, // 1
OP_LUA_LOADF, // 2
OP_LUA_LOADK, // 3
OP_LUA_LOADKX, // 4
OP_LUA_LOADFALSE, // 5
OP_LUA_LFALSESKIP, // 6
OP_LUA_LOADTRUE, // 7
OP_LUA_LOADNIL, // 8
OP_LUA_GETUPVAL, // 9
OP_LUA_SETUPVAL, // 10
OP_LUA_GETTABUP, // 11
OP_LUA_GETTABLE, // 12
OP_LUA_GETTABI, // 13
OP_LUA_GETFIELD, // 14
OP_LUA_SETTABUP, // 15
OP_LUA_SETTABLE, // 16
OP_LUA_SETTABI, // 17
OP_LUA_SETFIELD, // 18
OP_LUA_NEWTABLE, // 19
OP_LUA_SELF, // 20
OP_LUA_ADDI, // 21
OP_LUA_ADDK, // 22
OP_LUA_SUBK, // 23
OP_LUA_MULK, // 24
OP_LUA_MODK, // 25
OP_LUA_POWK, // 26
OP_LUA_DIVK, // 27
OP_LUA_IDIVK, // 28
OP_LUA_BANDK, // 29
OP_LUA_BORK, // 30
OP_LUA_BXORK, // 31
OP_LUA_SHRI, // 32
OP_LUA_SHLI, // 33
OP_LUA_ADD, // 34
OP_LUA_SUB, // 35
OP_LUA_MUL, // 36
OP_LUA_MOD, // 37
OP_LUA_POW, // 38
OP_LUA_DIV, // 39
OP_LUA_IDIV, // 40
OP_LUA_BAND, // 41
OP_LUA_BOR, // 42
OP_LUA_BXOR, // 43
OP_LUA_SHL, // 44
OP_LUA_SHR, // 45
OP_LUA_MMBIN, // 46
OP_LUA_MMBINI, // 47
OP_LUA_MMBINK, // 48
OP_LUA_UNM, // 49
OP_LUA_BNOT, // 50
OP_LUA_NOT, // 51
OP_LUA_LEN, // 52
OP_LUA_CONCAT, // 53
OP_LUA_CLOSE, // 54
OP_LUA_TBC, // 55
OP_LUA_JMP, // 56
OP_LUA_EQ, // 57
OP_LUA_LT, // 58
OP_LUA_LE, // 59
OP_LUA_EQK, // 60
OP_LUA_EQI, // 61
OP_LUA_LTI, // 62
OP_LUA_LEI, // 63
OP_LUA_GTI, // 64
OP_LUA_GEI, // 65
OP_LUA_TEST, // 66
OP_LUA_TESTSET, // 67
OP_LUA_CALL, // 68
OP_LUA_TAILCALL, // 69
OP_LUA_RETURN, // 70
OP_LUA_RETURN0, // 71
OP_LUA_RETURN1, // 72
OP_LUA_FORLOOP, // 73
OP_LUA_FORPREP, // 74
OP_LUA_TFORPREP, // 75
OP_LUA_TFORCALL, // 76
OP_LUA_TFORLOOP, // 77
OP_LUA_SETLIST, // 78
OP_LUA_CLOSURE, // 79
OP_LUA_VARARG, // 80
OP_LUA_VARARGPREP, // 81
OP_LUA_EXTRAARG, // 82
OP_LUA_NUM_OPCODES
};
// ---------------------------------------------------------------
// Instruction field accessors
// ---------------------------------------------------------------
// Lua 5.4 instruction format: 32-bit word
// iABC: C:8 | B:8 | k:1 | A:8 | Op:7
// iABx: Bx:17 | A:8 | Op:7 (unsigned)
// iAsBx: sBx:17 | A:8 | Op:7 (signed = Bx - offset)
// iAx: Ax:25 | Op:7
// isJ: sJ:25 | Op:7
struct lua_bc_instruction {
uint32_t raw;
int opcode() const { return raw & 0x7F; }
int A() const { return (raw >> 7) & 0xFF; }
int k() const { return (raw >> 15) & 0x1; }
int B() const { return (raw >> 16) & 0xFF; }
int sB() const { return B() - 128; } // signed B field
int C() const { return (raw >> 24) & 0xFF; }
int sC() const { return C() - 128; } // signed C field
int Bx() const { return (raw >> 15) & 0x1FFFF; } // bits 15-31
int sBx() const { return Bx() - 65535; } // offset = (2^17 - 1) / 2
int Ax() const { return (raw >> 7) & 0x1FFFFFF; }
int sJ() const { return static_cast<int>((raw >> 7) & 0x1FFFFFF) - (1 << 24); }
};
// ---------------------------------------------------------------
// Constant types
// ---------------------------------------------------------------
enum lua_bc_const_type {
LUA_BC_TNIL = 0,
LUA_BC_TFALSE = 1,
LUA_BC_TTRUE = 17,
LUA_BC_TINT = 3,
LUA_BC_TFLOAT = 19,
LUA_BC_TSHRSTR = 4,
LUA_BC_TLNGSTR = 20,
};
struct lua_bc_constant {
lua_bc_const_type type;
int64_t ival;
double fval;
std::string sval;
};
// ---------------------------------------------------------------
// Upvalue descriptor
// ---------------------------------------------------------------
struct lua_bc_upvalue {
uint8_t instack;
uint8_t idx;
uint8_t kind;
};
// ---------------------------------------------------------------
// Function prototype
// ---------------------------------------------------------------
struct lua_bc_proto {
// Source info.
std::string source;
int linedefined;
int lastlinedefined;
uint8_t numparams;
uint8_t is_vararg;
uint8_t maxstacksize;
// Code.
std::vector<lua_bc_instruction> code;
// Constants.
std::vector<lua_bc_constant> constants;
// Upvalues.
std::vector<lua_bc_upvalue> upvalues;
// Nested protos.
std::vector<lua_bc_proto> protos;
// Debug info (line numbers, local names) — we skip these.
};
// ---------------------------------------------------------------
// Top-level chunk
// ---------------------------------------------------------------
struct lua_bc_chunk {
// Header fields.
uint8_t version;
uint8_t format;
uint8_t int_size;
uint8_t size_t_size;
uint8_t insn_size;
uint8_t lua_int_size;
uint8_t lua_num_size;
// Main function prototype.
lua_bc_proto main;
// Number of upvalues in main proto (from header).
uint8_t num_upvalues;
};
// ---------------------------------------------------------------
// Deserializer
// ---------------------------------------------------------------
// Load a Lua 5.4 bytecode dump into `out`.
// Returns true on success, false on malformed input.
//
bool lua_bc_load(const uint8_t *data, size_t len, lua_bc_chunk *out);
#endif // LUA_BYTECODE_H

View file

@ -18,6 +18,7 @@
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
// Module bookkeeping.
//
@ -800,6 +801,7 @@ CLuaMod::CLuaMod(void) : m_cRef(1),
m_pIAttributeAccess(nullptr),
m_pIEvaluator(nullptr),
m_pIPermissions(nullptr),
m_pIJITCompile(nullptr),
m_L(nullptr),
m_nInsnLimit(LUA_DEFAULT_INSN_LIMIT),
m_nMemLimit(LUA_DEFAULT_MEM_LIMIT),
@ -861,6 +863,10 @@ MUX_RESULT CLuaMod::FinalConstruct(void)
mux_CreateInstance(CID_Permissions, nullptr, UseSameProcess,
IID_IPermissions, reinterpret_cast<void **>(&m_pIPermissions));
// Acquire JIT compile interface (optional — graceful degradation).
mux_CreateInstance(CID_JITCompile, nullptr, UseSameProcess,
IID_IJITCompile, reinterpret_cast<void **>(&m_pIJITCompile));
// Create the Lua state.
//
if (!CreateLuaState())
@ -948,6 +954,12 @@ CLuaMod::~CLuaMod()
m_pIPermissions = nullptr;
}
if (nullptr != m_pIJITCompile)
{
m_pIJITCompile->Release();
m_pIJITCompile = nullptr;
}
if (g_pLuaMod == this)
{
g_pLuaMod = nullptr;
@ -1051,6 +1063,8 @@ bool CLuaMod::LoadCached(const char *source, size_t nSource,
cache_entry entry;
entry.lua_ref = ref;
entry.lru_it = m_cache_lru.begin();
entry.jit_key = 0;
entry.jit_eligible = false;
m_cache[key] = entry;
return true;
@ -1070,6 +1084,10 @@ void CLuaMod::CacheEvict(void)
if (it != m_cache.end())
{
luaL_unref(m_L, LUA_REGISTRYINDEX, it->second.lua_ref);
if (it->second.jit_key != 0 && nullptr != m_pIJITCompile)
{
m_pIJITCompile->Invalidate(it->second.jit_key);
}
m_cache.erase(it);
}
m_cache_lru.pop_back();
@ -1083,11 +1101,88 @@ void CLuaMod::CacheClear(void)
{
luaL_unref(m_L, LUA_REGISTRYINDEX, pair.second.lua_ref);
}
if (pair.second.jit_key != 0 && nullptr != m_pIJITCompile)
{
m_pIJITCompile->Invalidate(pair.second.jit_key);
}
}
m_cache.clear();
m_cache_lru.clear();
}
// =========================================================================
// lua_dump writer callback — accumulates bytecode into a vector.
// =========================================================================
struct dump_buffer {
std::vector<uint8_t> data;
};
static int dump_writer(lua_State *L, const void *p, size_t sz, void *ud) {
(void)L;
dump_buffer *buf = static_cast<dump_buffer *>(ud);
const uint8_t *bytes = static_cast<const uint8_t *>(p);
buf->data.insert(buf->data.end(), bytes, bytes + sz);
return 0;
}
// =========================================================================
// TryJIT: attempt to JIT-compile a cached chunk.
// The chunk must be on top of the Lua stack.
// Returns true if JIT succeeded and result is in pResult.
// Returns false on JIT failure (caller should fall through to lua_pcall).
// Does NOT pop the chunk from the stack.
// =========================================================================
bool CLuaMod::TryJIT(cache_entry &entry, dbref executor, dbref caller,
dbref enactor, const UTF8 *pArgs[], int nArgs,
UTF8 *pResult, size_t nResultMax, size_t *pnResultLen)
{
if (nullptr == m_pIJITCompile) return false;
// Already tried and failed?
if (entry.jit_eligible) {
// Already have a compiled key? Run it.
if (entry.jit_key != 0) {
MUX_RESULT mr = m_pIJITCompile->RunCompiled(entry.jit_key,
executor, caller, enactor, pArgs, nArgs,
pResult, nResultMax, pnResultLen);
return MUX_SUCCEEDED(mr);
}
return false; // Previously failed to compile.
}
// First attempt: dump the chunk to bytecode and try JIT compilation.
entry.jit_eligible = true;
// lua_dump expects the function on top of stack. We have it there
// from LoadCached. Push a copy so we don't consume it.
lua_pushvalue(m_L, -1);
dump_buffer buf;
int dump_status = lua_dump(m_L, dump_writer, &buf, 0);
lua_pop(m_L, 1); // pop the copy
if (dump_status != 0 || buf.data.empty()) {
return false;
}
// Try to compile.
uint64_t key = 0;
MUX_RESULT mr = m_pIJITCompile->CompileLuaBytecode(
buf.data.data(), buf.data.size(), &key);
if (MUX_FAILED(mr) || key == 0) {
return false; // JIT doesn't support this bytecode; fall through.
}
entry.jit_key = key;
// Run the compiled program.
mr = m_pIJITCompile->RunCompiled(key, executor, caller, enactor,
pArgs, nArgs, pResult, nResultMax, pnResultLen);
return MUX_SUCCEEDED(mr);
}
// =========================================================================
// mux_ILuaControl implementation.
// =========================================================================
@ -1156,7 +1251,22 @@ MUX_RESULT CLuaMod::CallAttr(dbref executor, dbref caller, dbref enactor,
return MUX_S_OK;
}
// Execute.
// Try JIT execution. The compiled chunk is on top of the Lua stack.
// If JIT succeeds, pop the chunk and return.
//
std::string cache_key(reinterpret_cast<const char *>(source), nSource);
auto cache_it = m_cache.find(cache_key);
if (cache_it != m_cache.end())
{
if (TryJIT(cache_it->second, executor, caller, enactor,
pArgs, nArgs, pResult, nResultMax, pnResultLen))
{
lua_pop(m_L, 1); // pop the chunk
return MUX_S_OK;
}
}
// Fall through to Lua VM execution.
//
ExecuteChunk(m_L, executor, caller, enactor, pArgs, nArgs,
pResult, nResultMax, pnResultLen);
@ -1208,6 +1318,22 @@ MUX_RESULT CLuaMod::Eval(dbref executor, dbref caller, dbref enactor,
return MUX_S_OK;
}
// Try JIT execution.
//
std::string cache_key(reinterpret_cast<const char *>(pSource), nSource);
auto cache_it = m_cache.find(cache_key);
if (cache_it != m_cache.end())
{
if (TryJIT(cache_it->second, executor, caller, enactor,
nullptr, 0, pResult, nResultMax, pnResultLen))
{
lua_pop(m_L, 1); // pop the chunk
return MUX_S_OK;
}
}
// Fall through to Lua VM execution.
//
ExecuteChunk(m_L, executor, caller, enactor, nullptr, 0,
pResult, nResultMax, pnResultLen);
return MUX_S_OK;

View file

@ -56,6 +56,7 @@ private:
mux_IAttributeAccess *m_pIAttributeAccess;
mux_IEvaluator *m_pIEvaluator;
mux_IPermissions *m_pIPermissions;
mux_IJITCompile *m_pIJITCompile;
// Lua state - global, shared across all executions.
//
@ -83,6 +84,8 @@ private:
{
int lua_ref; // LUA_REGISTRYINDEX ref
std::list<std::string>::iterator lru_it; // Position in LRU list
uint64_t jit_key; // JIT compiled program key (0 = not compiled)
bool jit_eligible; // true if JIT compilation was attempted
};
std::unordered_map<std::string, cache_entry> m_cache;
std::list<std::string> m_cache_lru; // Front = most recent
@ -97,6 +100,9 @@ private:
bool LoadCached(const char *source, size_t nSource, const char *chunkname);
void CacheEvict(void);
void CacheClear(void);
bool TryJIT(cache_entry &entry, dbref executor, dbref caller,
dbref enactor, const UTF8 *pArgs[], int nArgs,
UTF8 *pResult, size_t nResultMax, size_t *pnResultLen);
bool ExecuteChunk(lua_State *L, dbref executor, dbref caller,
dbref enactor, const UTF8 *pArgs[], int nArgs,
UTF8 *pResult, size_t nResultMax, size_t *pnResultLen);