tinymux/mux/include/engine_api.h
Stephen Dennis b8ea2d80eb fix(lua/jit): compile CALL_VAL handle equality via HIR_LUA_EQ
Value-producing `return x == "0"` is EQK+JMP+LFALSESKIP+LOADTRUE, but
EQK was never in the bool fuse and always declined on TY_LUA_HANDLE.
Add ECALL_LUA_EQ / HIR_LUA_EQ, fuse EQK like EQ/EQI, and route handle
and non-handle EQK through emit_cmp_branch.  tostring(0)=="0" and
return nil=="" now execute; AGREE_DECLINE_BUDGET 13→11 on current master.
2026-07-29 15:38:19 -06:00

200 lines
9.4 KiB
C++

/*! \file engine_api.h
* \brief Engine API function index table for DBT/JIT dispatch.
*
* Provides O(1) function lookup by integer index, eliminating the
* string-based hash lookup in the ECALL hot path. The compiler
* resolves function names to indices at compile time; the JIT
* emits ECALL_CALL_INDEX (0x101) with a0 = function index.
*/
#ifndef ENGINE_API_H
#define ENGINE_API_H
#include <cstdint>
#include <cstddef>
#include <string>
#include <utility>
#include <vector>
// Forward declarations.
struct tagFun;
struct compiled_program;
typedef struct tagFun FUN;
// ECALL numbers.
static constexpr uint64_t ECALL_EXIT = 93;
static constexpr uint64_t ECALL_CALL_FUNC = 0x100; // string-based dispatch
static constexpr uint64_t ECALL_CALL_INDEX = 0x101; // index-based dispatch
static constexpr uint64_t ECALL_SETQ = 0x102; // q-register write-through
static constexpr uint64_t ECALL_SETQ_PACK = 0x130; // efficient register sync
static constexpr uint64_t ECALL_ARENA_ALLOC = 0x110; // a0=size -> a0=arena_id, a1=offset
static constexpr uint64_t ECALL_ARENA_REF = 0x111; // a0=arena_id
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)
// Float/double conversion ECALLs.
static constexpr uint64_t ECALL_FTOA = 0x140; // a0=double bits, a1=output addr
static constexpr uint64_t ECALL_ATOF = 0x141; // a0=string addr → fa0=double
static constexpr uint64_t ECALL_LUA_FTOA = 0x142; // a0=double bits, a1=output addr (Lua tostring rules)
// Database query ECALLs — leaf lookups, no softcode evaluation.
static constexpr uint64_t ECALL_GOOD_OBJ = 0x150; // a0=dbref → a0=0/1
// Unicode ECALLs — heavy-weight operations that use host tables.
static constexpr uint64_t ECALL_CHR = 0x160; // a0=input_addr, a1=output_addr → a0=0(ok)/-1(err)
static constexpr uint64_t ECALL_ORD = 0x161; // a0=input_addr, a1=output_addr → a0=0(ok)/-1(err)
static constexpr uint64_t ECALL_TRANSLATE = 0x162; // a0=input_addr, a1=type(0/1), a2=output_addr
static constexpr uint64_t ECALL_QUICK_WILD = 0x163; // a0=pattern_addr, a1=data_addr → a0=0/1
static constexpr uint64_t ECALL_SORT = 0x164; // a0=list_addr, a1=sort_type, a2=delim, a3=osep, a4=out_addr
// Persistent VM re-entrant call.
// Saves ctx, runs inner function via dbt_resume, restores ctx.
// a0 = entry_pc of compiled function
// a1 = guest addr of output buffer (for inner result)
// Returns: a0 = bytes written to output buffer (0 on failure)
//
static constexpr uint64_t ECALL_CALL_COMPILED = 0x200;
// Attribute resolution + compile.
// Resolves an attribute on an object, compiles the body into the
// persistent VM code heap, and returns the entry point.
// a0 = dbref of target object
// a1 = guest addr of attribute name string
// Returns: a0 = entry_pc (0 on failure)
// a1 = out_addr (where the compiled function writes its result)
// a2 = aflags (AF_NOEVAL etc.)
//
static constexpr uint64_t ECALL_COMPILE_ATTR = 0x201;
// Lua VM ECALLs — operations that call back into the Lua interpreter.
// Require eval_ctx.lua_state != nullptr.
//
// Convention: a0-a3 carry operand guest addresses or values.
// Results written to guest memory; a0 returns status or integer result.
//
static constexpr uint64_t ECALL_LUA_NEWTABLE = 0x301; // a0=narr, a1=nrec → a0=stack_idx
static constexpr uint64_t ECALL_LUA_GETI_INT = 0x308; // a0=tbl_idx, a1=key → a0=value, a1=ok
static constexpr uint64_t ECALL_LUA_SETI_INT = 0x309; // a0=tbl_idx, a1=key, a2=value
static constexpr uint64_t ECALL_LUA_LEN_INT = 0x30A; // a0=tbl_idx → a0=len, a1=ok
static constexpr uint64_t ECALL_LUA_GETFIELD_INT = 0x30B; // a0=tbl_idx, a1=key addr → a0=value, a1=ok
static constexpr uint64_t ECALL_LUA_SETFIELD_INT = 0x30C; // a0=tbl_idx, a1=key addr, a2=value
static constexpr uint64_t ECALL_LUA_GETGLOBAL = 0x30D; // a0=key addr → a0=stack_idx, a1=ok
static constexpr uint64_t ECALL_LUA_GETFIELD_REF = 0x30E; // a0=tbl_idx, a1=key addr → a0=stack_idx, a1=ok
static constexpr uint64_t ECALL_LUA_CALL_INT = 0x30F; // a0=fn_idx, a1=nargs, a2=args addr → a0=int result, a1=ok
static constexpr uint64_t ECALL_LUA_CALL_STR = 0x310; // a0=fn_idx, a1=nargs|argkinds, a2..a4=args, a5=out addr, a6=out size → a0=len, a1=ok
static constexpr uint64_t ECALL_LUA_GETFIELD_FLT = 0x311; // a0=tbl_idx, a1=key addr → a0=double bits, a1=ok
static constexpr uint64_t ECALL_LUA_CALL_VOID = 0x312; // a0=fn_idx, a1=nargs|argkinds, a2..a4=args; result discarded → a1=ok
static constexpr uint64_t ECALL_LUA_LIMITED = 0x313; // back-edge budget exhausted: aborts the run (declines to the interpreter)
static constexpr uint64_t ECALL_LUA_INSN_BUDGET = 0x314; // () -> a0 = current lua_instruction_limit (#1745 runtime rebinding)
// Typed call result: leave the first pcall result on the Lua stack and
// return its stack index (TY_LUA_HANDLE). Marshal only at the softcode
// boundary (#1764 shape 2).
static constexpr uint64_t ECALL_LUA_CALL_VAL = 0x315; // a0=fn, a1=nargs|kinds, a2..a4=args → a0=stack_idx, a1=ok
static constexpr uint64_t ECALL_LUA_MARSHAL = 0x316; // a0=stack_idx, a1=out addr, a2=out size → a0=len (fun_lua rules)
static constexpr uint64_t ECALL_LUA_TOBOOL = 0x317; // a0=stack_idx → a0=0/1 (Lua truthiness: only nil/false falsy)
// Lua == for a CALL_VAL handle vs a constant or another handle (#1764 residual).
// a0=lhs stack_idx, a1=rhs kind (0=int,1=string addr,2=handle,3=nil,4=bool),
// a2=rhs payload → a0=0/1.
static constexpr uint64_t ECALL_LUA_EQ = 0x318;
// 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_BRIDGE_MAX= 0x38F;
// Alarm check for JIT back-edge budgeting.
// Returns: a0 = 0 (ok, budget refilled) or 1 (alarmed, must exit).
static constexpr uint64_t ECALL_CHECK_ALARM = 0x400;
// Maximum number of indexed functions.
static constexpr int ENGINE_API_MAX_FUNCS = 512;
// The function index table: engine_api_table[i] → FUN*.
// Populated by engine_api_init() during init_functab().
// Index 0 is reserved (invalid).
//
extern FUN *engine_api_table[ENGINE_API_MAX_FUNCS];
extern int engine_api_count; // number of valid entries (1..count-1)
// Initialize the function index table from builtin_functions.
// Must be called after init_functab().
//
void engine_api_init();
// Sync function aliases (from netmux.conf / alias.conf) into the
// JIT lookup map. Must be called after cf_read().
//
void engine_api_sync_aliases();
// Look up a function index by uppercase name.
// Returns 0 if not found.
//
int engine_api_lookup(const char *name);
// Per-attribute modification counter for JIT cache invalidation.
// Incremented on every atr_add/atr_clr. The JIT records mod_count
// at compile time and checks for staleness at runtime.
//
void attr_mod_count_inc(dbref obj, int attrnum);
uint32_t attr_mod_count_get(dbref obj, int attrnum);
void attr_mod_count_invalidate_all();
// Two-phase bulk invalidation for transactional object deletion.
// Phase 1: collect attr list + current counters (before delete, no mutation).
// Phase 2: apply increments (after commit only).
//
std::vector<std::pair<uint64_t, uint32_t>>
attr_mod_count_collect_object(dbref obj);
void attr_mod_count_apply_increments(
const std::vector<std::pair<uint64_t, uint32_t>> &collected);
// Sort helper for ECALL_SORT — complete sort-and-format to buffer.
//
size_t sort_to_buffer(const UTF8 *list_in, char sort_type_char,
unsigned char delim, unsigned char osep,
UTF8 *out_buf, size_t out_max);
// SQLite code cache helpers (shared by softcode JIT and Lua JIT).
//
std::string jit_sha1_hex(const void *data, size_t len);
extern std::string s_blob_version;
void jit_store_to_sqlite(const std::string &key, const compiled_program &prog);
bool jit_load_from_sqlite(const std::string &key, compiled_program &out);
void jit_compact_program(compiled_program &prog);
// Lua JIT counters, owned by jit_lua.cpp. Reported by jitstats() so the
// Lua path is observable at all: without this the counters are incremented
// and never read, and a Lua JIT that compiles but never runs looks exactly
// like a healthy one, because every failure falls back to the interpreter
// and still produces correct results (#1316).
//
struct lua_jit_counters {
uint64_t compile_ok;
uint64_t compile_fail;
uint64_t run_ok;
uint64_t run_fail;
uint64_t cache_hits;
uint64_t invalidations;
uint64_t post_entry_decline; // #1751 Phase 0; long-term target 0
};
void jit_lua_get_stats(lua_jit_counters *out);
void jit_lua_reset_stats(void);
// Count a post-entry decline that was committed outside RunCompiled
// (e.g. effect_refused in TryJIT). #1751 Phase 0 / 0.5.
//
void jit_lua_note_post_entry_decline(void);
// Drop every in-process Lua compiled_program so a code_cache flush is not
// followed by runs that still hold native code from the previous build.
//
void jit_lua_clear_cache(void);
// Apply a jitstats(flush) that was deferred because a compiled program was
// still executing when it was requested. Call before taking a pointer into
// either program cache; it is a no-op unless a flush is pending and the JIT
// is quiet (#1316).
//
void jit_flush_pending_caches(void);
#endif // ENGINE_API_H