diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a263f920..4fa377a9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -201,15 +201,21 @@ jobs: printf 'package "main"\nfunction init() return 0 end\nfunction fini() return 0 end\n' > test.lua ./bin/flua test.lua --once cd "$GITHUB_WORKSPACE" - # Only this repo's sources. Uploading Boost/CPM/usr headers makes Codecov - # drop file-level reports because paths cannot be matched to GitHub. + # Host sources + optional GCC JIT temps. --no-external drops /usr and CPM. + # Fl* runtime arcs are #line-mapped onto src/compile/c_runtime_cov.h + # (checked-in view header); extract src/* covers that. build/jit_cov/* + # kept as a safety net for any residual temp-.c hits (preamble). + # #line→.lua remaps stay excluded. inconsistent: Lua #line end-line + # checks + some host TU gcov noise. lcov --directory build --capture --output-file coverage.info \ --base-directory "$GITHUB_WORKSPACE" --no-external \ - --ignore-errors source,mismatch + --ignore-errors source,mismatch,inconsistent lcov --extract coverage.info \ "$GITHUB_WORKSPACE/src/*" \ "$GITHUB_WORKSPACE/include/*" \ - --output-file coverage.info + "$GITHUB_WORKSPACE/build/jit_cov/*" \ + --output-file coverage.info \ + --ignore-errors unused,inconsistent ls -l coverage.info lcov --list coverage.info diff --git a/.github/workflows/cppcheck.yml b/.github/workflows/cppcheck.yml index 3c9f60b2..f3613a10 100644 --- a/.github/workflows/cppcheck.yml +++ b/.github/workflows/cppcheck.yml @@ -30,7 +30,7 @@ jobs: cd .. cd .. cppcheck --version - FILES=`find src/ cmd/ -type f -name "*.cpp" -o -name "*.h"|grep -v parser|grep -v scanner|grep -v third_party` + FILES=`find src/ cmd/ -type f -name "*.cpp" -o -name "*.h"|grep -v parser|grep -v scanner|grep -v third_party|grep -v c_runtime_cov.h` cppcheck --language=c++ --check-level=exhaustive --max-ctu-depth=64 -j8 $FILES 2> err.txt cat err.txt if [[ -s err.txt ]] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7a96621b..a5275577 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -113,6 +113,14 @@ if (WIN32) target_link_options(fakelua PRIVATE "-Wl,--export-all-symbols" "-Wl,--exclude-libs,ALL") endif () +# When USE_COV is on, expose it to C++ so the GCC JIT path can instrument emitted C +# (host CXXFLAGS alone do not affect the forked `gcc` argv for temp .c → .so). +# FAKELUA_JIT_COV_DIR places JIT temps + .gcno/.gcda under the build tree for lcov. +if (USE_COV) + target_compile_definitions(fakelua PRIVATE USE_COV=1) + target_compile_definitions(fakelua PRIVATE "FAKELUA_JIT_COV_DIR=\"${CMAKE_BINARY_DIR}/jit_cov\"") +endif () + find_package(Threads REQUIRED) if (WIN32) diff --git a/src/compile/c_gen.cpp b/src/compile/c_gen.cpp index b5f09607..075bc173 100644 --- a/src/compile/c_gen.cpp +++ b/src/compile/c_gen.cpp @@ -461,7 +461,17 @@ void CGen::GenerateHeader() { // C 运行时类型定义、宏 and 函数(从 c_runtime_header.h 提取,便于独立维护) Out() << "static const char * const fl_lua_file = \"" << EscapeCStringLiteral(file_name_) << "\";\n"; + + // Attribute the pasted Fl* runtime to the checked-in view header so USE_COV + // gcov → lcov → Codecov can display hits on a real repo path (not + // build/jit_cov/fakelua_jit_*.c). Preamble above (_S, heaps, fl_lua_file) + // stays on the temp .c. kCRuntimeHeader begins with '\n', so do not write a + // newline after #line — logical line 1 becomes "#include ", matching + // src/compile/c_runtime_cov.h. Reset afterward so per-TU spec boilerplate is + // not falsely mapped onto that header; Lua body #line directives still apply. + Out() << "#line 1 \"" << kCRuntimeCovPath << "\""; Out() << kCRuntimeHeader; + Out() << "#line 1 \"\"\n"; // 前置发射所有 spec 类型的 typedef + 特化 get/set 函数(读取 TypeInferencer 预计算的 // ir.spec_type_metadata)。CGen 不再在 CompileTableconstructor 里懒发射这些样板代码。 diff --git a/src/compile/c_runtime_cov.h b/src/compile/c_runtime_cov.h new file mode 100644 index 00000000..82756f3f --- /dev/null +++ b/src/compile/c_runtime_cov.h @@ -0,0 +1,2310 @@ +#include + +// 运行期报错定位槽。字段顺序与类型必须和 state/state.h 的 fakelua::LuaLoc 一致: +// 生成的代码直接按这个布局读写 State 上的实例。 +typedef struct FlLuaLoc { + const char *file; + int line; +} FlLuaLoc; + +typedef struct VarString { + int size_; + uint32_t hash_; + char data_[0]; +} VarString; + +typedef struct VarTable VarTable; +typedef struct VarMulti VarMulti; +typedef struct VarClosure VarClosure; + +typedef struct CVar { + int type_; + int flag_; + union { + bool b; + int64_t i; + double f; + VarString *s; + VarTable *t; + VarMulti *m; + VarClosure *cl; + } data_; +} CVar; + +typedef struct VarClosure { + void *func_ptr; + int upvalue_count; + int expected_arg_count; + bool is_vararg; + const char *code_str; + CVar *upvalues[0]; +} VarClosure; + +// 编译期断言:确保 CVar 大小为 16 字节 +typedef char check_cvar_size[sizeof(CVar) == 16 ? 1 : -1]; + +typedef struct VarEntry { + CVar key; + CVar val; + uint32_t hash; +} VarEntry; + +typedef struct TableNode { + VarEntry entry; + uint32_t next; + uint32_t active_pos; +} TableNode; + +struct VarTable; + +typedef CVar (*SpecGetFn)(VarTable *tbl, CVar k, bool *finish); +typedef void (*SpecSetFn)(VarTable *tbl, CVar k, CVar v, bool *finish); + +struct VarTable { + uint32_t count_; + uint32_t bucket_count_; + TableNode *nodes_; + uint32_t *active_list_; + VarEntry quick_data_[8]; + uint32_t free_list_idx_; + void *spec; + SpecGetFn spec_get; + SpecSetFn spec_set; + CVar *spec_keys; + CVar *spec_vals; + uint32_t spec_count; + /* 连续整数键前缀长度(# 运算符结果)的缓存。seq_len_valid_ == 0 代表缓存无效, + 需要重算——因此把 VarTable 整体清零的分配路径天然落在安全的重算分支上。 */ + uint32_t seq_len_valid_; + int64_t seq_len_; + /* 数组部分:整数键 1..arr_size_ 只存放在 arr_ 中(允许 nil 空洞),哈希部分不含 [1, arr_size_+1] + 范围内的整数键。arr_size_ 只增不减,遍历时偏移保持稳定。spec 表不使用数组部分。 + 哈希部分写满时按 Lua 的规则(整数键占用率 > 50%)把整数键迁入数组,见 FlArrTryResize。 */ + CVar *arr_; + uint32_t arr_size_; + uint32_t arr_cap_; +}; + +typedef struct State State; + +#define LIKELY(x) __builtin_expect(!!(x), 1) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) + +#define STR_SIZE(s) ((s)->size_) +#define STR_DATA(s) ((s)->data_) + +enum { + VAR_NIL = 0, + VAR_BOOL = 1, + VAR_INT = 2, + VAR_FLOAT = 3, + VAR_STRING = 4, + VAR_STRINGID = 5, + VAR_TABLE = 6, + VAR_MULTI = 7, + VAR_CLOSURE = 8, +}; + +// CVar flag bits: 第 0 位表示该变量为只读(const),不可修改其值或表字段。 +// __fakelua_init_flag__:false=仍在 __fakelua_init(可填充 const 表);true=init 已结束。 +// init 期间 NEWTABLE/SET_TABLE 创建的表直接打 CONST_FLAG(整棵树,含嵌套); +// SETTABLE 仅在 init 结束后才因 CONST_FLAG 抛错,以便构造器写字段。 +#define CONST_FLAG 0x1 +#define FL_CHECK_CONST_TABLE(t) do { \ + if (UNLIKELY(((t).flag_ & CONST_FLAG) && __fakelua_init_flag__)) { \ + FakeluaThrowError(_S, "attempt to modify a const table"); \ + } \ +} while(0) + +/* 探测整数键 k 是否存在且值非 nil。查找逻辑与 FlGetTableInt 一致,但直接作用于 + VarTable*,供序列长度计算与缓存维护复用。必须查 spec:特化表的 1..n 只在 + spec 里,不在 quick/nodes。以前从 spec_count 起扫,等于把字段个数当成 #。 */ +static inline bool FlTableHasIntKey(VarTable *t, int64_t k) { + if (t->spec_get) { + CVar key_cvar; key_cvar.type_ = VAR_INT; key_cvar.flag_ = 0; key_cvar.data_.i = k; + bool __finish = false; + CVar __r = t->spec_get(t, key_cvar, &__finish); + if (__finish) return __r.type_ != VAR_NIL; + } + if ((uint64_t)k - 1 < (uint64_t)t->arr_size_) { return t->arr_[k - 1].type_ != VAR_NIL; } + if (t->spec_count > 0 && t->spec_keys && t->spec_vals) { + uint32_t i; + for (i = 0; i < t->spec_count; ++i) { + if (t->spec_keys[i].type_ == VAR_INT && t->spec_keys[i].data_.i == k && t->spec_vals[i].type_ != VAR_NIL) { + return true; + } + } + } + if (LIKELY(t->bucket_count_ == 0)) { + uint32_t i; + for (i = 0; i < t->count_; ++i) { + if (t->quick_data_[i].key.type_ == VAR_INT && t->quick_data_[i].key.data_.i == k && t->quick_data_[i].val.type_ != VAR_NIL) { + return true; + } + } + return false; + } else { + uint32_t h = (uint32_t)(k ^ (k >> 32)); + uint32_t mask = t->bucket_count_ - 1; + TableNode *curr = &t->nodes_[h & mask]; + while (1) { + if (curr->entry.key.type_ == VAR_INT && curr->entry.key.data_.i == k && curr->entry.val.type_ != VAR_NIL) { + return true; + } + if (curr->next == 0xFFFFFFFF) return false; + curr = &t->nodes_[curr->next]; + } + } +} + +/* 从 base 起向上数连续存在的整数键,返回前缀长度。*/ +static inline int64_t FlSeqScanFrom(VarTable *t, int64_t base) { + while (base < INT64_MAX && FlTableHasIntKey(t, base + 1)) { base++; } + return base; +} + +/* 带 spec 的表字段布局在构造期固定且规模很小,不参与缓存,每次直接重算。*/ +static inline bool FlSeqCacheable(VarTable *t) { + return t->spec == NULL && t->spec_count == 0; +} + +static inline int64_t FlGetTableSeqLen(VarTable *t) { + if (!t) return 0; + if (UNLIKELY(!FlSeqCacheable(t))) { + /* spec_count 是特化字段总数(含字符串键),不是数组前缀。从 0 起扫。 */ + return FlSeqScanFrom(t, 0); + } + if (LIKELY(t->seq_len_valid_ != 0)) { + return t->seq_len_; + } + t->seq_len_ = FlSeqScanFrom(t, 0); + t->seq_len_valid_ = 1; + return t->seq_len_; +} + +/* 在整数键写入完成后同步序列长度缓存。缓存无效时什么都不做,交由下一次 + FlGetTableSeqLen 懒重算——这保证漏挂钩的写入路径只会退化性能而不会算错长度。 + 本函数对同一次写入重复调用是幂等的。 */ +static inline void FlSeqNoteIntSet(VarTable *t, int64_t k, bool is_nil) { + if (!t || t->seq_len_valid_ == 0) return; + if (UNLIKELY(!FlSeqCacheable(t))) { t->seq_len_valid_ = 0; return; } + if (is_nil) { + /* t[k] 变为 nil,前缀在 k-1 处截断;1..k-1 都还在,故新长度恰为 k-1。*/ + if (k >= 1 && k <= t->seq_len_) { t->seq_len_ = k - 1; } + return; + } + /* 只有正好补上 t[len+1] 才会延长前缀,随后继续吸收先前写入的更高键。*/ + if (t->seq_len_ < INT64_MAX && k == t->seq_len_ + 1) { t->seq_len_ = FlSeqScanFrom(t, k); } +} + +/* NativeObject Wrap 惰性快照:pairs/GET_TABLE_ENTRY 读 spec_count 前调用。非 Native 表 no-op。 */ +extern void FlEnsureNativeSpecSnapshot(VarTable *t); +static inline void FlEnsureNativeSpecSnapshotInline(VarTable *t) { + if (UNLIKELY(t != NULL && t->spec != NULL)) { + FlEnsureNativeSpecSnapshot(t); + } +} + +#define TABLE_SIZE(t) ((t)->count_ + (t)->spec_count + (t)->arr_size_) + +typedef struct VarMulti { + uint32_t count; + CVar vars[0]; +} VarMulti; + +typedef struct FlHeapSlot { + char *curr; + char *end; +} FlHeapSlot; + +extern void* FakeluaAlloc(State *s, size_t size, bool is_const); + +static inline void* FlFastAlloc(State *state, size_t size, bool is_const) { + FlHeapSlot *hs = (is_const || !__fakelua_init_flag__) ? (FlHeapSlot *)fl_const_heap : (FlHeapSlot *)fl_temp_heap; + if (LIKELY(hs != NULL)) { + size_t aligned_size = (size + 15) & ~(size_t)15; + char *c = hs->curr; + if (LIKELY(c != NULL && aligned_size >= size && c + aligned_size > c && c + aligned_size <= hs->end)) { + hs->curr = c + aligned_size; + return (void *)c; + } + } + return FakeluaAlloc(state, size, is_const); +} + +static inline CVar FlAllocMulti(State *state, uint32_t count) { + VarMulti *m = (VarMulti *)FlFastAlloc(state, sizeof(VarMulti) + count * sizeof(CVar), !__fakelua_init_flag__); + m->count = count; + CVar res; + res.type_ = VAR_MULTI; + res.flag_ = 0; + res.data_.m = m; + return res; +} + +// 设置 multi-value 的元素,避免链接 inter::SetMultiCVarElement(Windows TCC 找不到符号) +static inline void FlSetMultiCVarElement(CVar multi, int idx, CVar val) { + VarMulti *m = multi.data_.m; + if (idx >= 0 && idx < (int)m->count) { + m->vars[idx] = val; + } +} + +static inline CVar FlMakeMulti(State *state, uint32_t count, ...) { + VarMulti *m = (VarMulti *)FlFastAlloc(state, sizeof(VarMulti) + count * sizeof(CVar), !__fakelua_init_flag__); + m->count = count; + va_list args_list; + va_start(args_list, count); + for (uint32_t i = 0; i < count; ++i) { + m->vars[i] = va_arg(args_list, CVar); + } + va_end(args_list); + CVar res; + res.type_ = VAR_MULTI; + res.flag_ = 0; + res.data_.m = m; + return res; +} + +static inline CVar FlCombineMulti(State *state, uint32_t prefix_count, const CVar *prefix_vars, CVar last) { + uint32_t last_count = 1; + const CVar *last_vars = &last; + if (last.type_ == VAR_MULTI) { + VarMulti *m = last.data_.m; + last_count = m->count; + last_vars = m->vars; + } + + uint32_t total_count = prefix_count + last_count; + VarMulti *m = (VarMulti *)FlFastAlloc(state, sizeof(VarMulti) + total_count * sizeof(CVar), !__fakelua_init_flag__); + m->count = total_count; + + for (uint32_t i = 0; i < prefix_count; ++i) { + if (prefix_vars[i].type_ == VAR_MULTI) { + m->vars[i] = prefix_vars[i].data_.m->count > 0 ? prefix_vars[i].data_.m->vars[0] : (CVar){VAR_NIL}; + } else { + m->vars[i] = prefix_vars[i]; + } + } + for (uint32_t i = 0; i < last_count; ++i) { + m->vars[prefix_count + i] = last_vars[i]; + } + + CVar res; + res.type_ = VAR_MULTI; + res.flag_ = 0; + res.data_.m = m; + return res; +} + + +static inline CVar FlUnboxMulti(CVar v, uint32_t idx) { + if (LIKELY(v.type_ != VAR_MULTI)) { + return (idx == 0) ? v : (CVar){VAR_NIL}; + } + VarMulti *m = v.data_.m; + return (idx < m->count) ? m->vars[idx] : (CVar){VAR_NIL}; +} + +static inline CVar FlSliceMulti(State *state, CVar v, uint32_t start_idx) { + if (v.type_ != VAR_MULTI) { + return (start_idx == 0) ? v : (CVar){VAR_NIL}; + } + VarMulti *m = v.data_.m; + if (start_idx >= m->count) { + return (CVar){VAR_NIL}; + } + uint32_t count = m->count - start_idx; + VarMulti *nm = (VarMulti *)FlFastAlloc(state, sizeof(VarMulti) + count * sizeof(CVar), !__fakelua_init_flag__); + nm->count = count; + for (uint32_t i = 0; i < count; ++i) { + nm->vars[i] = m->vars[start_idx + i]; + } + CVar res; + res.type_ = VAR_MULTI; + res.flag_ = 0; + res.data_.m = nm; + return res; +} + +#define SET_NIL(v) do { (v).type_ = VAR_NIL; } while(0) +#define SET_BOOL(v, val) do { (v).type_ = VAR_BOOL; (v).data_.i = 0; (v).data_.b = (val); } while(0) +#define SET_INT(v, val) do { (v).type_ = VAR_INT; (v).data_.i = (val); } while(0) +#define SET_FLOAT(v, val) do { \ + double __f = (val); \ + (v).type_ = VAR_FLOAT; \ + (v).data_.f = __f; \ +} while(0) + +/* 2^63:精确 double,也是第一个无法放入 int64 的整数。 + * (double)INT64_MAX 会向上舍入成这个值,因此不能用 <= (double)INT64_MAX。 */ +#define FL_INT64_FLOAT_EXCL 9223372036854775808.0 + +static inline int FlDoubleFitsInt64(double d, int64_t *out) { + double ip; + if (!isfinite(d)) return 0; + if (modf(d, &ip) != 0.0) return 0; + if (ip < (double)INT64_MIN || ip >= FL_INT64_FLOAT_EXCL) return 0; + *out = (int64_t)ip; + return 1; +} + +#define NORMALIZE_TABLE_KEY(key) ({ \ + CVar __k = (key); \ + if (UNLIKELY(__k.type_ == VAR_FLOAT)) { \ + int64_t __i; \ + if (FlDoubleFitsInt64(__k.data_.f, &__i)) { \ + __k.type_ = VAR_INT; \ + __k.data_.i = __i; \ + } \ + } \ + __k; \ +}) + +#ifdef __cplusplus +extern "C" { +#endif +extern void* FakeluaAlloc(State *s, size_t size, bool is_const); +extern void FakeluaThrowError(State *s, const char *msg); +extern CVar FakeluaCallByName(State *s, int jit_type, const char *name, int arg_num, ...); +extern CVar FakeluaAllocMultiCVar(State *s, int count); +extern void FakeluaSetMultiCVarElement(CVar *multi, int idx, CVar val); +extern CVar FlEvalLoadClosure(State *state, VarClosure *cl, int arg_num, const CVar *args); +extern CVar FakeluaInterpCall(State *state, VarClosure *cl, int arg_num, const CVar *args); +// 当前执行引擎上下文(供 JIT C 代码调用闭包前后维护):push 切到 jit 并返回上一个引擎, +// pop 恢复。native 对象方法等在闭包内执行时据此登记派回本引擎的异步回调。 +extern int FakeluaJitContextPush(State *s, int jit); +extern void FakeluaJitContextPop(State *s, int prev); +extern void FakeluaRegisterLuaLoc(State *s, int jit_type, FlLuaLoc *loc); +#ifdef __cplusplus +} +#endif + +// 记录当前执行的 Lua 语句位置(文件级全局变量 fl_lua_loc 在 __fakelua_init 时把地址传给宿主 State)。 +// C++ 侧的报错拿不到 Lua 栈,直接通过注册的变量地址读取;#line 则负责把编译告警映射回 Lua 源码。 +static FlLuaLoc fl_lua_loc = { 0, 0 }; +#define FL_LOC_SET(f, l) do { \ + (void)(f); \ + fl_lua_loc.line = (l); \ +} while (0) + +#define kMaxFunctionInputParams 32 + +// 注意:is_vararg 必须声明为 int 而非 bool。C17 7.16.1.4 规定 va_start 的最后一个 +// 具名形参若类型会受默认实参提升影响(bool/_Bool 会被提升为 int),行为未定义。 +// 调用处传入的 true/false 会隐式转换为 int。 +static inline CVar FlMakeClosure(State *state, void *func_ptr, int upvalue_count, int expected_arg_count, int is_vararg, ...) { + VarClosure *cl = (VarClosure *)FlFastAlloc(state, sizeof(VarClosure) + upvalue_count * sizeof(CVar *), !__fakelua_init_flag__); + cl->func_ptr = func_ptr; + cl->upvalue_count = upvalue_count; + cl->expected_arg_count = expected_arg_count; + cl->is_vararg = is_vararg; + cl->code_str = NULL; + va_list args_list; + va_start(args_list, is_vararg); + for (int i = 0; i < upvalue_count; ++i) { + cl->upvalues[i] = va_arg(args_list, CVar *); + } + va_end(args_list); + CVar res; + res.type_ = VAR_CLOSURE; + res.flag_ = 0; + res.data_.cl = cl; + return res; +} + +static inline CVar FlMakeClosure0(State *state, void *func_ptr, int expected_arg_count, int is_vararg) { + VarClosure *cl = (VarClosure *)FlFastAlloc(state, sizeof(VarClosure), !__fakelua_init_flag__); + cl->func_ptr = func_ptr; + cl->upvalue_count = 0; + cl->expected_arg_count = expected_arg_count; + cl->is_vararg = is_vararg; + cl->code_str = NULL; + CVar res; + res.type_ = VAR_CLOSURE; + res.flag_ = 0; + res.data_.cl = cl; + return res; +} + +static inline CVar FlMakeClosure1(State *state, void *func_ptr, int expected_arg_count, int is_vararg, CVar *up0) { + VarClosure *cl = (VarClosure *)FlFastAlloc(state, sizeof(VarClosure) + sizeof(CVar *), !__fakelua_init_flag__); + cl->func_ptr = func_ptr; + cl->upvalue_count = 1; + cl->expected_arg_count = expected_arg_count; + cl->is_vararg = is_vararg; + cl->code_str = NULL; + cl->upvalues[0] = up0; + CVar res; + res.type_ = VAR_CLOSURE; + res.flag_ = 0; + res.data_.cl = cl; + return res; +} + +static inline CVar FlCallClosure(State *state, CVar cl_var, int arg_num, ...) { + if (UNLIKELY(cl_var.type_ != VAR_CLOSURE)) { + FakeluaThrowError(state, "attempt to call a non-function value"); + } + // 与 FakeluaCallByName 一致:先拦超限,再读 varargs。 + // raw_arg_arr 只有 32 槽,arg_num>32 会栈缓冲溢出。 + if (UNLIKELY(arg_num < 0 || arg_num > (int)kMaxFunctionInputParams)) { + FakeluaThrowError(state, "too many arguments"); + } + VarClosure *cl = cl_var.data_.cl; + void *addr = cl->func_ptr; + if (UNLIKELY(addr == NULL)) { + return FlEvalLoadClosure(state, cl, arg_num, cl_var.type_ == VAR_CLOSURE ? (const CVar *)&cl_var : NULL); + } + const bool is_vararg = cl->is_vararg; + const int expected_arg_count = cl->expected_arg_count; + const int fixed_arg_count = is_vararg ? expected_arg_count - 1 : expected_arg_count; + // 形参个数也要限:下面按 expected_arg_count 往 temp_arg_arr(定长 32)里写, + // 声明了超过上限的形参会栈缓冲溢出(与 vm.cpp 的 CallByNameImpl 一致)。 + if (UNLIKELY(expected_arg_count < 0 || expected_arg_count > (int)kMaxFunctionInputParams)) { + FakeluaThrowError(state, "too many arguments"); + } + + CVar raw_arg_arr[kMaxFunctionInputParams]; + va_list args_list; + va_start(args_list, arg_num); + for (int i = 0; i < arg_num; ++i) { + raw_arg_arr[i] = va_arg(args_list, CVar); + } + va_end(args_list); + + const CVar *arg_arr = NULL; + const bool last_is_multi = (arg_num > 0 && raw_arg_arr[arg_num - 1].type_ == VAR_MULTI); + CVar temp_arg_arr[kMaxFunctionInputParams]; + if (LIKELY(!is_vararg && arg_num == expected_arg_count && !last_is_multi)) { + arg_arr = raw_arg_arr; + } else { + CVar flat_args_buf[kMaxFunctionInputParams]; + int flat_count = 0; + for (int i = 0; i < arg_num; ++i) { + if (i == arg_num - 1 && raw_arg_arr[i].type_ == VAR_MULTI) { + VarMulti *m = raw_arg_arr[i].data_.m; + const int extra = m ? (int)m->count : 0; + if (flat_count + extra > (int)kMaxFunctionInputParams) { + FakeluaThrowError(state, "too many arguments"); + } + if (m) { + for (uint32_t j = 0; j < m->count; ++j) { + flat_args_buf[flat_count++] = m->vars[j]; + } + } + } else if (raw_arg_arr[i].type_ == VAR_MULTI) { + VarMulti *m = raw_arg_arr[i].data_.m; + if (flat_count >= (int)kMaxFunctionInputParams) { + FakeluaThrowError(state, "too many arguments"); + } + flat_args_buf[flat_count++] = (m && m->count > 0) ? m->vars[0] : (CVar){VAR_NIL}; + } else { + if (flat_count >= (int)kMaxFunctionInputParams) { + FakeluaThrowError(state, "too many arguments"); + } + flat_args_buf[flat_count++] = raw_arg_arr[i]; + } + } + + if (UNLIKELY(is_vararg)) { + for (int i = 0; i < fixed_arg_count; ++i) { + temp_arg_arr[i] = i < flat_count ? flat_args_buf[i] : (CVar){VAR_NIL}; + } + const int vararg_count = flat_count - fixed_arg_count; + VarMulti *m = (VarMulti *)FlFastAlloc(state, sizeof(VarMulti) + (vararg_count > 0 ? vararg_count : 0) * sizeof(CVar), !__fakelua_init_flag__); + m->count = vararg_count > 0 ? vararg_count : 0; + for (int i = 0; i < vararg_count; ++i) { + m->vars[i] = flat_args_buf[fixed_arg_count + i]; + } + CVar vararg_cvar; + vararg_cvar.type_ = VAR_MULTI; + vararg_cvar.flag_ = 0; + vararg_cvar.data_.m = m; + temp_arg_arr[fixed_arg_count] = vararg_cvar; + } else { + for (int i = 0; i < expected_arg_count; ++i) { + temp_arg_arr[i] = i < flat_count ? flat_args_buf[i] : (CVar){VAR_NIL}; + } + } + arg_arr = temp_arg_arr; + } + + if (cl->code_str == (const char *)1) { + int npass = expected_arg_count; + if (is_vararg) { + npass = fixed_arg_count + 1; + if (npass < 1) npass = 1; + } + return FakeluaInterpCall(state, cl, npass, arg_arr); + } + +#define FCCVAR_0 +#define FCCVAR_1 , CVar +#define FCCVAR_2 FCCVAR_1, CVar +#define FCCVAR_3 FCCVAR_2, CVar +#define FCCVAR_4 FCCVAR_3, CVar +#define FCCVAR_5 FCCVAR_4, CVar +#define FCCVAR_6 FCCVAR_5, CVar +#define FCCVAR_7 FCCVAR_6, CVar +#define FCCVAR_8 FCCVAR_7, CVar +#define FCCVAR_9 FCCVAR_8, CVar +#define FCCVAR_10 FCCVAR_9, CVar +#define FCCVAR_11 FCCVAR_10, CVar +#define FCCVAR_12 FCCVAR_11, CVar +#define FCCVAR_13 FCCVAR_12, CVar +#define FCCVAR_14 FCCVAR_13, CVar +#define FCCVAR_15 FCCVAR_14, CVar +#define FCCVAR_16 FCCVAR_15, CVar +#define FCCVAR_17 FCCVAR_16, CVar +#define FCCVAR_18 FCCVAR_17, CVar +#define FCCVAR_19 FCCVAR_18, CVar +#define FCCVAR_20 FCCVAR_19, CVar +#define FCCVAR_21 FCCVAR_20, CVar +#define FCCVAR_22 FCCVAR_21, CVar +#define FCCVAR_23 FCCVAR_22, CVar +#define FCCVAR_24 FCCVAR_23, CVar +#define FCCVAR_25 FCCVAR_24, CVar +#define FCCVAR_26 FCCVAR_25, CVar +#define FCCVAR_27 FCCVAR_26, CVar +#define FCCVAR_28 FCCVAR_27, CVar +#define FCCVAR_29 FCCVAR_28, CVar +#define FCCVAR_30 FCCVAR_29, CVar +#define FCCVAR_31 FCCVAR_30, CVar +#define FCCVAR_32 FCCVAR_31, CVar + +#define FCARG_0 +#define FCARG_1 , arg_arr[0] +#define FCARG_2 FCARG_1, arg_arr[1] +#define FCARG_3 FCARG_2, arg_arr[2] +#define FCARG_4 FCARG_3, arg_arr[3] +#define FCARG_5 FCARG_4, arg_arr[4] +#define FCARG_6 FCARG_5, arg_arr[5] +#define FCARG_7 FCARG_6, arg_arr[6] +#define FCARG_8 FCARG_7, arg_arr[7] +#define FCARG_9 FCARG_8, arg_arr[8] +#define FCARG_10 FCARG_9, arg_arr[9] +#define FCARG_11 FCARG_10, arg_arr[10] +#define FCARG_12 FCARG_11, arg_arr[11] +#define FCARG_13 FCARG_12, arg_arr[12] +#define FCARG_14 FCARG_13, arg_arr[13] +#define FCARG_15 FCARG_14, arg_arr[14] +#define FCARG_16 FCARG_15, arg_arr[15] +#define FCARG_17 FCARG_16, arg_arr[16] +#define FCARG_18 FCARG_17, arg_arr[17] +#define FCARG_19 FCARG_18, arg_arr[18] +#define FCARG_20 FCARG_19, arg_arr[19] +#define FCARG_21 FCARG_20, arg_arr[20] +#define FCARG_22 FCARG_21, arg_arr[21] +#define FCARG_23 FCARG_22, arg_arr[22] +#define FCARG_24 FCARG_23, arg_arr[23] +#define FCARG_25 FCARG_24, arg_arr[24] +#define FCARG_26 FCARG_25, arg_arr[25] +#define FCARG_27 FCARG_26, arg_arr[26] +#define FCARG_28 FCARG_27, arg_arr[27] +#define FCARG_29 FCARG_28, arg_arr[28] +#define FCARG_30 FCARG_29, arg_arr[29] +#define FCARG_31 FCARG_30, arg_arr[30] +#define FCARG_32 FCARG_31, arg_arr[31] + +#define FCCASE(N) \ + case N: { \ + int __fl_saved_line = fl_lua_loc.line; \ + fl_lua_loc.line = 0; \ + int __fl_prev_jit = FakeluaJitContextPush(state, FAKELUA_JIT_TYPE); \ + CVar __fl_ret = ((CVar (*)(VarClosure * FCCVAR_##N))(addr))(cl FCARG_##N); \ + FakeluaJitContextPop(state, __fl_prev_jit); \ + fl_lua_loc.line = __fl_saved_line; \ + return __fl_ret; \ + } + + switch (expected_arg_count) { + FCCASE(0) FCCASE(1) FCCASE(2) FCCASE(3) FCCASE(4) FCCASE(5) + FCCASE(6) FCCASE(7) FCCASE(8) FCCASE(9) FCCASE(10) FCCASE(11) + FCCASE(12) FCCASE(13) FCCASE(14) FCCASE(15) FCCASE(16) FCCASE(17) + FCCASE(18) FCCASE(19) FCCASE(20) FCCASE(21) FCCASE(22) FCCASE(23) + FCCASE(24) FCCASE(25) FCCASE(26) FCCASE(27) FCCASE(28) FCCASE(29) + FCCASE(30) FCCASE(31) FCCASE(32) + default: FakeluaThrowError(state, "FlCallClosure: expected_arg_count out of range"); return (CVar){VAR_NIL}; + } + +#undef FCCASE +#undef FCCVAR_0 +#undef FCCVAR_1 +#undef FCCVAR_2 +#undef FCCVAR_3 +#undef FCCVAR_4 +#undef FCCVAR_5 +#undef FCCVAR_6 +#undef FCCVAR_7 +#undef FCCVAR_8 +#undef FCCVAR_9 +#undef FCCVAR_10 +#undef FCCVAR_11 +#undef FCCVAR_12 +#undef FCCVAR_13 +#undef FCCVAR_14 +#undef FCCVAR_15 +#undef FCCVAR_16 +#undef FCCVAR_17 +#undef FCCVAR_18 +#undef FCCVAR_19 +#undef FCCVAR_20 +#undef FCCVAR_21 +#undef FCCVAR_22 +#undef FCCVAR_23 +#undef FCCVAR_24 +#undef FCCVAR_25 +#undef FCCVAR_26 +#undef FCCVAR_27 +#undef FCCVAR_28 +#undef FCCVAR_29 +#undef FCCVAR_30 +#undef FCCVAR_31 +#undef FCCVAR_32 +#undef FCARG_0 +#undef FCARG_1 +#undef FCARG_2 +#undef FCARG_3 +#undef FCARG_4 +#undef FCARG_5 +#undef FCARG_6 +#undef FCARG_7 +#undef FCARG_8 +#undef FCARG_9 +#undef FCARG_10 +#undef FCARG_11 +#undef FCARG_12 +#undef FCARG_13 +#undef FCARG_14 +#undef FCARG_15 +#undef FCARG_16 +#undef FCARG_17 +#undef FCARG_18 +#undef FCARG_19 +#undef FCARG_20 +#undef FCARG_21 +#undef FCARG_22 +#undef FCARG_23 +#undef FCARG_24 +#undef FCARG_25 +#undef FCARG_26 +#undef FCARG_27 +#undef FCARG_28 +#undef FCARG_29 +#undef FCARG_30 +#undef FCARG_31 +#undef FCARG_32 +} + +static inline CVar FlCallClosure0(State *state, CVar cl_var) { + if (LIKELY(cl_var.type_ == VAR_CLOSURE)) { + VarClosure *cl = cl_var.data_.cl; + void *addr = cl->func_ptr; + if (LIKELY(addr != NULL && !cl->is_vararg && cl->expected_arg_count == 0 && cl->code_str != (const char *)1)) { + int __fl_saved_line = fl_lua_loc.line; + fl_lua_loc.line = 0; + CVar __fl_ret = ((CVar (*)(VarClosure *))(addr))(cl); + fl_lua_loc.line = __fl_saved_line; + return __fl_ret; + } + } + return FlCallClosure(state, cl_var, 0); +} + +static inline CVar FlCallClosure1(State *state, CVar cl_var, CVar a0) { + if (LIKELY(cl_var.type_ == VAR_CLOSURE)) { + VarClosure *cl = cl_var.data_.cl; + void *addr = cl->func_ptr; + if (LIKELY(addr != NULL && !cl->is_vararg && cl->expected_arg_count == 1 && a0.type_ != VAR_MULTI && cl->code_str != (const char *)1)) { + int __fl_saved_line = fl_lua_loc.line; + fl_lua_loc.line = 0; + CVar __fl_ret = ((CVar (*)(VarClosure *, CVar))(addr))(cl, a0); + fl_lua_loc.line = __fl_saved_line; + return __fl_ret; + } + } + return FlCallClosure(state, cl_var, 1, a0); +} + +static inline CVar FlCallClosure2(State *state, CVar cl_var, CVar a0, CVar a1) { + if (LIKELY(cl_var.type_ == VAR_CLOSURE)) { + VarClosure *cl = cl_var.data_.cl; + void *addr = cl->func_ptr; + if (LIKELY(addr != NULL && !cl->is_vararg && cl->expected_arg_count == 2 && a0.type_ != VAR_MULTI && a1.type_ != VAR_MULTI && cl->code_str != (const char *)1)) { + int __fl_saved_line = fl_lua_loc.line; + fl_lua_loc.line = 0; + CVar __fl_ret = ((CVar (*)(VarClosure *, CVar, CVar))(addr))(cl, a0, a1); + fl_lua_loc.line = __fl_saved_line; + return __fl_ret; + } + } + return FlCallClosure(state, cl_var, 2, a0, a1); +} + +static inline CVar FlCallClosure3(State *state, CVar cl_var, CVar a0, CVar a1, CVar a2) { + if (LIKELY(cl_var.type_ == VAR_CLOSURE)) { + VarClosure *cl = cl_var.data_.cl; + void *addr = cl->func_ptr; + if (LIKELY(addr != NULL && !cl->is_vararg && cl->expected_arg_count == 3 && a0.type_ != VAR_MULTI && a1.type_ != VAR_MULTI && a2.type_ != VAR_MULTI && cl->code_str != (const char *)1)) { + int __fl_saved_line = fl_lua_loc.line; + fl_lua_loc.line = 0; + CVar __fl_ret = ((CVar (*)(VarClosure *, CVar, CVar, CVar))(addr))(cl, a0, a1, a2); + fl_lua_loc.line = __fl_saved_line; + return __fl_ret; + } + } + return FlCallClosure(state, cl_var, 3, a0, a1, a2); +} + +static inline bool FlIsTrue(CVar v) { + return LIKELY(v.type_ != VAR_NIL) && (v.type_ != VAR_BOOL || v.data_.b); +} + +#ifndef FAKELUA_JIT_TYPE +#define FAKELUA_JIT_TYPE 0 +#endif + +static inline uint32_t FlHashString(const char *str, int len) { + uint32_t hash = 5381; + int i; + for (i = 0; i < len; ++i) { + hash = ((hash << 5) + hash) + (uint8_t)str[i]; + } + if (hash == 0) { + hash = 1; + } + return hash; +} + +#define SET_STRING(v, str, len) do { \ + int __len = (len); \ + const char *__s_ptr = (str); \ + VarString *__vs = (VarString *)FlFastAlloc(_S, sizeof(VarString) + __len, !__fakelua_init_flag__); \ + __vs->size_ = __len; \ + __vs->hash_ = 0; \ + memcpy(__vs->data_, __s_ptr, __len); \ + (v).type_ = VAR_STRING; \ + (v).data_.s = __vs; \ +} while(0) + +#define SET_TABLE(v) do { \ + VarTable *__t = (VarTable *)FlFastAlloc(_S, sizeof(VarTable), !__fakelua_init_flag__); \ + __t->count_ = 0; \ + __t->bucket_count_ = 0; \ + __t->nodes_ = NULL; \ + __t->active_list_ = NULL; \ + __t->free_list_idx_ = 0xFFFFFFFF; \ + __t->spec = NULL; \ + __t->spec_get = NULL; \ + __t->spec_set = NULL; \ + __t->spec_keys = NULL; \ + __t->spec_vals = NULL; \ + __t->spec_count = 0; \ + __t->seq_len_valid_ = 1; \ + __t->seq_len_ = 0; \ + __t->arr_ = NULL; \ + __t->arr_size_ = 0; \ + __t->arr_cap_ = 0; \ + assert(sizeof(__t->quick_data_) == 8 * sizeof(VarEntry)); \ + { int __i; for (__i = 0; __i < 8; ++__i) { \ + __t->quick_data_[__i].key.type_ = VAR_NIL; \ + __t->quick_data_[__i].val.type_ = VAR_NIL; \ + __t->quick_data_[__i].hash = 0; \ + } } \ + (v).type_ = VAR_TABLE; \ + /* init 中创建 → CONST_FLAG(嵌套表同样走 SET_TABLE,整树只读);init 后创建 → 可变 */ \ + (v).flag_ = __fakelua_init_flag__ ? 0 : CONST_FLAG; \ + (v).data_.t = __t; \ +} while(0) + +#define SET_TABLE_SPEC(v, SpecType, get_fn, set_fn, field_count) do { \ + SET_TABLE(v); \ + SpecType *__sp = (SpecType *)FlFastAlloc(_S, sizeof(SpecType), !__fakelua_init_flag__); \ + (v).data_.t->spec = __sp; \ + (v).data_.t->spec_get = (get_fn); \ + (v).data_.t->spec_set = (set_fn); \ + (v).data_.t->spec_keys = (CVar *)FlFastAlloc(_S, sizeof(CVar) * (field_count), !__fakelua_init_flag__); \ + (v).data_.t->spec_vals = (CVar *)FlFastAlloc(_S, sizeof(CVar) * (field_count), !__fakelua_init_flag__); \ + (v).data_.t->spec_count = (field_count); \ + assert(sizeof(SpecType) == (field_count) * sizeof(CVar)); \ +} while(0) + +#define FL_SPEC(SpecType, v, field) (((SpecType *)(v).data_.t->spec)->field) +#define FL_SET_SPEC(SpecType, v, field, index, val) do { \ + CVar __flsv = (val); \ + FL_CHECK_CONST_TABLE(v); \ + FL_SPEC(SpecType, (v), field) = __flsv; \ + (v).data_.t->spec_vals[(index)] = __flsv; \ +} while(0) + +#define IsTrue(v, result) do { \ + CVar __tv = (v); \ + (result) = (LIKELY(__tv.type_ != VAR_NIL) && (__tv.type_ != VAR_BOOL || __tv.data_.b)); \ +} while(0) + +static inline int FlCompareIntDouble(int64_t integer, double floating) { + if (isnan(floating)) return 0; + if (floating >= 9223372036854775808.0) return -1; + if (floating < -9223372036854775808.0) return 1; + int64_t truncated = (int64_t)floating; + if (integer < truncated) return -1; + if (integer > truncated) return 1; + double truncated_as_double = (double)truncated; + if (floating > truncated_as_double) return -1; + if (floating < truncated_as_double) return 1; + return 0; +} + +#define FL_INT_FLOAT_EQUAL(i, f) (!isnan((f)) && FlCompareIntDouble((i), (f)) == 0) + +#define VarEqual(_fa, _fb, result) do { \ + CVar __a = (_fa), __b = (_fb); \ + if (__a.type_ != __b.type_) { \ + if ((__a.type_ == VAR_STRING || __a.type_ == VAR_STRINGID) && \ + (__b.type_ == VAR_STRING || __b.type_ == VAR_STRINGID)) { \ + VarString *__sa = (__a.type_ == VAR_STRING) ? __a.data_.s : (VarString *)__a.data_.i; \ + VarString *__sb = (__b.type_ == VAR_STRING) ? __b.data_.s : (VarString *)__b.data_.i; \ + (result) = (__sa == __sb) || (__sa->size_ == __sb->size_ && memcmp(__sa->data_, __sb->data_, __sa->size_) == 0); \ + } else if (__a.type_ == VAR_INT && __b.type_ == VAR_FLOAT) { \ + (result) = FL_INT_FLOAT_EQUAL(__a.data_.i, __b.data_.f); \ + } else if (__a.type_ == VAR_FLOAT && __b.type_ == VAR_INT) { \ + (result) = FL_INT_FLOAT_EQUAL(__b.data_.i, __a.data_.f); \ + } else { \ + (result) = false; \ + } \ + break; \ + } \ + switch (__a.type_) { \ + case VAR_NIL: (result) = true; break; \ + case VAR_BOOL: (result) = (__a.data_.b == __b.data_.b); break; \ + case VAR_INT: (result) = (__a.data_.i == __b.data_.i); break; \ + case VAR_FLOAT: (result) = (__a.data_.f == __b.data_.f); break; \ + case VAR_STRING: { \ + if (__a.data_.s == __b.data_.s) { (result) = true; break; } \ + (result) = (__a.data_.s->size_ == __b.data_.s->size_ && memcmp(__a.data_.s->data_, __b.data_.s->data_, __a.data_.s->size_) == 0); \ + break; \ + } \ + case VAR_STRINGID: { \ + if (__a.data_.i == __b.data_.i) { (result) = true; break; } \ + VarString *__sa = (VarString *)__a.data_.i; \ + VarString *__sb = (VarString *)__b.data_.i; \ + (result) = (__sa->size_ == __sb->size_ && memcmp(__sa->data_, __sb->data_, __sa->size_) == 0); \ + break; \ + } \ + case VAR_TABLE: (result) = (__a.data_.t == __b.data_.t); break; \ + case VAR_CLOSURE: (result) = (__a.data_.cl == __b.data_.cl); break; \ + default: (result) = false; break; \ + } \ +} while(0) + +#define VarHash(v, hash) do { \ + CVar __v = (v); \ + switch (__v.type_) { \ + case VAR_NIL: (hash) = 0; break; \ + case VAR_BOOL: (hash) = (__v.data_.b ? 1 : 0); break; \ + case VAR_INT: (hash) = (uint32_t)(__v.data_.i ^ (__v.data_.i >> 32)); break; \ + case VAR_FLOAT: { union { double __d; uint64_t __u; } __fu; __fu.__d = __v.data_.f; (hash) = (uint32_t)(__fu.__u ^ (__fu.__u >> 32)); break; } \ + case VAR_STRING: { if (__v.data_.s->hash_ == 0) { __v.data_.s->hash_ = FlHashString(__v.data_.s->data_, __v.data_.s->size_); } (hash) = __v.data_.s->hash_; break; } \ + case VAR_STRINGID: { VarString *__vs = (VarString *)__v.data_.i; if (__vs->hash_ == 0) { __vs->hash_ = FlHashString(__vs->data_, __vs->size_); } (hash) = __vs->hash_; break; } \ + case VAR_TABLE: (hash) = (uint32_t)((uintptr_t)__v.data_.t ^ ((uintptr_t)__v.data_.t >> 32)); break; \ + case VAR_CLOSURE: (hash) = (uint32_t)((uintptr_t)__v.data_.cl ^ ((uintptr_t)__v.data_.cl >> 32)); break; \ + default: (hash) = 0; break; \ + } \ +} while(0) + +static inline CVar FlGetTable(CVar t, CVar k) { + k = NORMALIZE_TABLE_KEY(k); + if (UNLIKELY(t.type_ != VAR_TABLE)) { FakeluaThrowError(_S, "attempt to index a non-table value"); } + if (UNLIKELY(k.type_ == VAR_NIL)) { FakeluaThrowError(_S, "table index is nil"); } + if (UNLIKELY(k.type_ == VAR_FLOAT && isnan(k.data_.f))) { FakeluaThrowError(_S, "table index is NaN"); } + VarTable *tbl = t.data_.t; + if (tbl->spec_get) { + bool __finish = false; + CVar __r = tbl->spec_get(tbl, k, &__finish); + if (__finish) return __r; + } + if (k.type_ == VAR_INT && (uint64_t)k.data_.i - 1 < (uint64_t)tbl->arr_size_) { return tbl->arr_[k.data_.i - 1]; } + if (UNLIKELY(tbl->count_ == 0)) { return (CVar){VAR_NIL}; } + uint32_t h; VarHash(k, h); + if (LIKELY(tbl->bucket_count_ == 0)) { + bool __eq; + if (tbl->quick_data_[0].hash == h) { VarEqual(tbl->quick_data_[0].key, k, __eq); if (__eq) { return tbl->quick_data_[0].val; } } + if (tbl->count_ > 1 && tbl->quick_data_[1].hash == h) { VarEqual(tbl->quick_data_[1].key, k, __eq); if (__eq) { return tbl->quick_data_[1].val; } } + if (tbl->count_ > 2 && tbl->quick_data_[2].hash == h) { VarEqual(tbl->quick_data_[2].key, k, __eq); if (__eq) { return tbl->quick_data_[2].val; } } + if (tbl->count_ > 3 && tbl->quick_data_[3].hash == h) { VarEqual(tbl->quick_data_[3].key, k, __eq); if (__eq) { return tbl->quick_data_[3].val; } } + if (tbl->count_ > 4 && tbl->quick_data_[4].hash == h) { VarEqual(tbl->quick_data_[4].key, k, __eq); if (__eq) { return tbl->quick_data_[4].val; } } + if (tbl->count_ > 5 && tbl->quick_data_[5].hash == h) { VarEqual(tbl->quick_data_[5].key, k, __eq); if (__eq) { return tbl->quick_data_[5].val; } } + if (tbl->count_ > 6 && tbl->quick_data_[6].hash == h) { VarEqual(tbl->quick_data_[6].key, k, __eq); if (__eq) { return tbl->quick_data_[6].val; } } + if (tbl->count_ > 7 && tbl->quick_data_[7].hash == h) { VarEqual(tbl->quick_data_[7].key, k, __eq); if (__eq) { return tbl->quick_data_[7].val; } } + } else { + uint32_t mask = tbl->bucket_count_ - 1; + uint32_t idx = h & mask; + TableNode *curr = &tbl->nodes_[idx]; + if (UNLIKELY(curr->entry.key.type_ == VAR_NIL)) { return (CVar){VAR_NIL}; } + while (1) { + if (curr->entry.hash == h) { bool __eq; VarEqual(curr->entry.key, k, __eq); if (__eq) { return curr->entry.val; } } + uint32_t next = curr->next; + if (next == 0xFFFFFFFF) { break; } + curr = &tbl->nodes_[next]; + } + } + return (CVar){VAR_NIL}; +} + +static inline uint32_t NextPowerOfTwo(uint32_t v) { + v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; + return v; +} + +static inline bool FlTableInsertRaw(VarTable *tbl, CVar key, CVar val, uint32_t hash) { + uint32_t mask = tbl->bucket_count_ - 1; + uint32_t total_nodes = tbl->bucket_count_ + tbl->bucket_count_ / 2; + assert(tbl->count_ < total_nodes); + uint32_t idx = hash & mask; + TableNode *main_node = &tbl->nodes_[idx]; + if (LIKELY(main_node->entry.key.type_ == VAR_NIL)) { + main_node->entry.key = key; main_node->entry.val = val; main_node->entry.hash = hash; main_node->next = 0xFFFFFFFF; + main_node->active_pos = tbl->count_; tbl->active_list_[tbl->count_] = idx; + tbl->count_++; return true; + } + uint32_t curr_idx = idx; + while (1) { + TableNode *curr = &tbl->nodes_[curr_idx]; + if (curr->entry.hash == hash) { bool __eq; VarEqual(curr->entry.key, key, __eq); if (__eq) { curr->entry.val = val; return true; } } + if (curr->next == 0xFFFFFFFF) { break; } + curr_idx = curr->next; + } + if (UNLIKELY(tbl->free_list_idx_ == 0xFFFFFFFF)) { return false; } + uint32_t new_node_idx = tbl->free_list_idx_; + TableNode *new_node = &tbl->nodes_[new_node_idx]; + tbl->free_list_idx_ = new_node->next; + new_node->entry.key = key; new_node->entry.val = val; new_node->entry.hash = hash; + new_node->next = main_node->next; main_node->next = new_node_idx; + new_node->active_pos = tbl->count_; tbl->active_list_[tbl->count_] = new_node_idx; + tbl->count_++; return true; +} + +static inline void FlTableRehash(VarTable *tbl) { + uint32_t old_count = tbl->count_; + uint32_t old_bucket_count = tbl->bucket_count_; + TableNode *old_nodes = tbl->nodes_; + uint32_t new_bucket_count = NextPowerOfTwo(old_count + 1); + if (new_bucket_count <= old_bucket_count) { new_bucket_count = old_bucket_count * 2; } + while (1) { + uint32_t overflow_count = new_bucket_count / 2; + uint32_t total_nodes = new_bucket_count + overflow_count; + size_t nodes_size = total_nodes * sizeof(TableNode); + size_t active_list_size = total_nodes * sizeof(uint32_t); + char *buffer = (char *)FlFastAlloc(_S, nodes_size + active_list_size, !__fakelua_init_flag__); + TableNode *new_nodes = (TableNode *)buffer; + uint32_t *new_active_list = (uint32_t *)(buffer + nodes_size); + { uint32_t i; for (i = 0; i < total_nodes; ++i) { new_nodes[i].entry.key.type_ = VAR_NIL; new_nodes[i].next = 0xFFFFFFFF; new_nodes[i].active_pos = 0xFFFFFFFF; } } + TableNode *prev_nodes = tbl->nodes_; uint32_t *prev_active_list = tbl->active_list_; uint32_t prev_bucket_count = tbl->bucket_count_; uint32_t prev_count = tbl->count_; uint32_t prev_free_list_idx = tbl->free_list_idx_; + tbl->nodes_ = new_nodes; tbl->active_list_ = new_active_list; tbl->bucket_count_ = new_bucket_count; tbl->count_ = 0; + if (LIKELY(overflow_count > 0)) { + { uint32_t i; for (i = 0; i < overflow_count - 1; ++i) { tbl->nodes_[new_bucket_count + i].next = new_bucket_count + i + 1; } } + tbl->nodes_[new_bucket_count + overflow_count - 1].next = 0xFFFFFFFF; + tbl->free_list_idx_ = new_bucket_count; + } else { + tbl->free_list_idx_ = 0xFFFFFFFF; + } + bool success = true; + if (LIKELY(old_bucket_count == 0)) { + { uint32_t i; for (i = 0; i < old_count; ++i) { if (!FlTableInsertRaw(tbl, tbl->quick_data_[i].key, tbl->quick_data_[i].val, tbl->quick_data_[i].hash)) { success = false; break; } } } + } else { + { uint32_t i; for (i = 0; i < old_bucket_count; ++i) { + uint32_t curr_idx = i; + while (curr_idx != 0xFFFFFFFF) { + TableNode *old_node = &old_nodes[curr_idx]; + if (LIKELY(old_node->entry.key.type_ != VAR_NIL)) { if (!FlTableInsertRaw(tbl, old_node->entry.key, old_node->entry.val, old_node->entry.hash)) { success = false; break; } } + curr_idx = old_node->next; + } + if (!success) { break; } + } } + } + if (LIKELY(success)) { break; } else { tbl->nodes_ = prev_nodes; tbl->active_list_ = prev_active_list; tbl->bucket_count_ = prev_bucket_count; tbl->count_ = prev_count; tbl->free_list_idx_ = prev_free_list_idx; new_bucket_count *= 2; } + } +} + +static inline bool FlArrResizeInsert(VarTable *tbl, CVar k, CVar v); + +static inline void FlHashSet(VarTable *tbl, CVar k, CVar v) { + uint32_t h; VarHash(k, h); + if (UNLIKELY(v.type_ == VAR_NIL)) { + if (UNLIKELY(tbl->count_ == 0)) { return; } + if (LIKELY(tbl->bucket_count_ == 0)) { + bool __eq; + if (tbl->quick_data_[0].hash == h) { VarEqual(tbl->quick_data_[0].key, k, __eq); if (__eq) { if (tbl->count_ > 1) { tbl->quick_data_[0] = tbl->quick_data_[tbl->count_ - 1]; } tbl->count_--; return; } } + if (tbl->count_ > 1 && tbl->quick_data_[1].hash == h) { VarEqual(tbl->quick_data_[1].key, k, __eq); if (__eq) { if (tbl->count_ > 2) { tbl->quick_data_[1] = tbl->quick_data_[tbl->count_ - 1]; } tbl->count_--; return; } } + if (tbl->count_ > 2 && tbl->quick_data_[2].hash == h) { VarEqual(tbl->quick_data_[2].key, k, __eq); if (__eq) { if (tbl->count_ > 3) { tbl->quick_data_[2] = tbl->quick_data_[tbl->count_ - 1]; } tbl->count_--; return; } } + if (tbl->count_ > 3 && tbl->quick_data_[3].hash == h) { VarEqual(tbl->quick_data_[3].key, k, __eq); if (__eq) { if (tbl->count_ > 4) { tbl->quick_data_[3] = tbl->quick_data_[tbl->count_ - 1]; } tbl->count_--; return; } } + if (tbl->count_ > 4 && tbl->quick_data_[4].hash == h) { VarEqual(tbl->quick_data_[4].key, k, __eq); if (__eq) { if (tbl->count_ > 5) { tbl->quick_data_[4] = tbl->quick_data_[tbl->count_ - 1]; } tbl->count_--; return; } } + if (tbl->count_ > 5 && tbl->quick_data_[5].hash == h) { VarEqual(tbl->quick_data_[5].key, k, __eq); if (__eq) { if (tbl->count_ > 6) { tbl->quick_data_[5] = tbl->quick_data_[tbl->count_ - 1]; } tbl->count_--; return; } } + if (tbl->count_ > 6 && tbl->quick_data_[6].hash == h) { VarEqual(tbl->quick_data_[6].key, k, __eq); if (__eq) { if (tbl->count_ > 7) { tbl->quick_data_[6] = tbl->quick_data_[tbl->count_ - 1]; } tbl->count_--; return; } } + if (tbl->count_ > 7 && tbl->quick_data_[7].hash == h) { VarEqual(tbl->quick_data_[7].key, k, __eq); if (__eq) { tbl->count_--; return; } } + } else { + uint32_t mask = tbl->bucket_count_ - 1; uint32_t idx = h & mask; TableNode *curr = &tbl->nodes_[idx]; + if (UNLIKELY(curr->entry.key.type_ == VAR_NIL)) { return; } + { bool __eq; VarEqual(curr->entry.key, k, __eq); if (curr->entry.hash == h && __eq) { + if (curr->next != 0xFFFFFFFF) { + uint32_t next_idx = curr->next; TableNode *next_node = &tbl->nodes_[next_idx]; + uint32_t pos = next_node->active_pos; uint32_t last_node_idx = tbl->active_list_[tbl->count_ - 1]; + if (next_idx != last_node_idx) { tbl->active_list_[pos] = last_node_idx; tbl->nodes_[last_node_idx].active_pos = pos; } + next_node->active_pos = 0xFFFFFFFF; curr->entry = next_node->entry; curr->next = next_node->next; next_node->next = tbl->free_list_idx_; tbl->free_list_idx_ = next_idx; + } else { + uint32_t pos = curr->active_pos; uint32_t last_node_idx = tbl->active_list_[tbl->count_ - 1]; + if (idx != last_node_idx) { tbl->active_list_[pos] = last_node_idx; tbl->nodes_[last_node_idx].active_pos = pos; } + curr->active_pos = 0xFFFFFFFF; curr->entry.key.type_ = VAR_NIL; + } + tbl->count_--; return; + } } + uint32_t prev_idx = idx; uint32_t curr_idx = curr->next; + while (curr_idx != 0xFFFFFFFF) { + TableNode *node = &tbl->nodes_[curr_idx]; + { bool __eq; VarEqual(node->entry.key, k, __eq); if (node->entry.hash == h && __eq) { + uint32_t pos = node->active_pos; uint32_t last_node_idx = tbl->active_list_[tbl->count_ - 1]; + if (curr_idx != last_node_idx) { tbl->active_list_[pos] = last_node_idx; tbl->nodes_[last_node_idx].active_pos = pos; } + node->active_pos = 0xFFFFFFFF; tbl->nodes_[prev_idx].next = node->next; node->next = tbl->free_list_idx_; tbl->free_list_idx_ = curr_idx; tbl->count_--; return; + } } + prev_idx = curr_idx; curr_idx = node->next; + } + } + return; + } + if (LIKELY(tbl->bucket_count_ == 0)) { + bool __eq; + if (tbl->count_ > 0 && tbl->quick_data_[0].hash == h) { VarEqual(tbl->quick_data_[0].key, k, __eq); if (__eq) { tbl->quick_data_[0].val = v; return; } } + if (tbl->count_ > 1 && tbl->quick_data_[1].hash == h) { VarEqual(tbl->quick_data_[1].key, k, __eq); if (__eq) { tbl->quick_data_[1].val = v; return; } } + if (tbl->count_ > 2 && tbl->quick_data_[2].hash == h) { VarEqual(tbl->quick_data_[2].key, k, __eq); if (__eq) { tbl->quick_data_[2].val = v; return; } } + if (tbl->count_ > 3 && tbl->quick_data_[3].hash == h) { VarEqual(tbl->quick_data_[3].key, k, __eq); if (__eq) { tbl->quick_data_[3].val = v; return; } } + if (tbl->count_ > 4 && tbl->quick_data_[4].hash == h) { VarEqual(tbl->quick_data_[4].key, k, __eq); if (__eq) { tbl->quick_data_[4].val = v; return; } } + if (tbl->count_ > 5 && tbl->quick_data_[5].hash == h) { VarEqual(tbl->quick_data_[5].key, k, __eq); if (__eq) { tbl->quick_data_[5].val = v; return; } } + if (tbl->count_ > 6 && tbl->quick_data_[6].hash == h) { VarEqual(tbl->quick_data_[6].key, k, __eq); if (__eq) { tbl->quick_data_[6].val = v; return; } } + if (tbl->count_ > 7 && tbl->quick_data_[7].hash == h) { VarEqual(tbl->quick_data_[7].key, k, __eq); if (__eq) { tbl->quick_data_[7].val = v; return; } } + if (tbl->count_ < 8) { tbl->quick_data_[tbl->count_].key = k; tbl->quick_data_[tbl->count_].val = v; tbl->quick_data_[tbl->count_].hash = h; tbl->count_++; return; } + if (k.type_ == VAR_INT && FlArrResizeInsert(tbl, k, v)) { return; } + FlTableRehash(tbl); + } + if (UNLIKELY(tbl->count_ >= tbl->bucket_count_ || tbl->free_list_idx_ == 0xFFFFFFFF)) { + if (k.type_ == VAR_INT && FlArrResizeInsert(tbl, k, v)) { return; } + FlTableRehash(tbl); + } + FlTableInsertRaw(tbl, k, v, h); +} + +static inline CVar FlHashGetInt(VarTable *tbl, int64_t k) { + if (UNLIKELY(tbl->count_ == 0)) { return (CVar){VAR_NIL}; } + uint32_t h = (uint32_t)(k ^ (k >> 32)); + if (LIKELY(tbl->bucket_count_ == 0)) { + for (uint32_t i = 0; i < tbl->count_; ++i) { + if (tbl->quick_data_[i].hash == h && tbl->quick_data_[i].key.type_ == VAR_INT && tbl->quick_data_[i].key.data_.i == k) { + return tbl->quick_data_[i].val; + } + } + } else { + uint32_t mask = tbl->bucket_count_ - 1; + uint32_t idx = h & mask; + TableNode *curr = &tbl->nodes_[idx]; + if (UNLIKELY(curr->entry.key.type_ == VAR_NIL)) { return (CVar){VAR_NIL}; } + while (1) { + if (curr->entry.hash == h && curr->entry.key.type_ == VAR_INT && curr->entry.key.data_.i == k) { + return curr->entry.val; + } + uint32_t next = curr->next; + if (next == 0xFFFFFFFF) { break; } + curr = &tbl->nodes_[next]; + } + } + return (CVar){VAR_NIL}; +} + +__attribute__((noinline)) static void FlArrGrow(VarTable *tbl) { + uint32_t cap = tbl->arr_cap_ ? tbl->arr_cap_ * 2 : 4; + CVar *na = (CVar *)FlFastAlloc(_S, (size_t)cap * sizeof(CVar), !__fakelua_init_flag__); + if (tbl->arr_size_ > 0) { memcpy(na, tbl->arr_, (size_t)tbl->arr_size_ * sizeof(CVar)); } + tbl->arr_ = na; + tbl->arr_cap_ = cap; +} + +/* 数组末尾追加后,把哈希部分中紧随其后的整数键迁入数组,维持「哈希不含 [1, arr_size_+1]」。*/ +__attribute__((noinline)) static void FlArrMigrate(VarTable *tbl) { + while (tbl->count_ != 0) { + int64_t nk = (int64_t)tbl->arr_size_ + 1; + CVar hv = FlHashGetInt(tbl, nk); + if (hv.type_ == VAR_NIL) { break; } + CVar key; key.type_ = VAR_INT; key.flag_ = 0; key.data_.i = nk; + FlHashSet(tbl, key, (CVar){VAR_NIL}); + if (UNLIKELY(tbl->arr_size_ == tbl->arr_cap_)) { FlArrGrow(tbl); } + tbl->arr_[tbl->arr_size_++] = hv; + } +} + +/* 整数键写入的数组部分快路径。返回 true 表示已处理。*/ +static inline bool FlArrSetInt(VarTable *tbl, int64_t k, CVar v) { + uint64_t idx = (uint64_t)k - 1; + if (LIKELY(idx < (uint64_t)tbl->arr_size_)) { tbl->arr_[idx] = v; return true; } + if (idx == (uint64_t)tbl->arr_size_ && v.type_ != VAR_NIL && !tbl->spec_set) { + if (UNLIKELY(tbl->arr_size_ == tbl->arr_cap_)) { FlArrGrow(tbl); } + tbl->arr_[tbl->arr_size_++] = v; + if (UNLIKELY(tbl->count_ != 0)) { FlArrMigrate(tbl); } + return true; + } + return false; +} + +/* 哈希部分写满、即将为整数键 nk 扩容前,按 Lua 的 computesizes 规则判断是否应把整数键迁入数组: + 选取最大的 2^i,使 1..2^i 内的整数键数量超过 2^i 的一半。返回 true 表示数组已扩大。 */ +__attribute__((noinline)) static bool FlArrTryResize(VarTable *tbl, int64_t nk) { + uint32_t nums[32]; + uint64_t total = 0; + uint32_t i; + memset(nums, 0, sizeof(nums)); +#define FL_COUNT_INT_KEY(kk) do { \ + int64_t __ck = (kk); \ + if (__ck >= 1 && __ck <= (1LL << 30)) { nums[__ck == 1 ? 0 : 64 - __builtin_clzll((uint64_t)__ck - 1)]++; total++; } \ +} while(0) + for (i = 0; i < tbl->arr_size_; ++i) { if (tbl->arr_[i].type_ != VAR_NIL) { FL_COUNT_INT_KEY((int64_t)i + 1); } } + if (tbl->bucket_count_ == 0) { + for (i = 0; i < tbl->count_; ++i) { + if (tbl->quick_data_[i].key.type_ == VAR_INT) { FL_COUNT_INT_KEY(tbl->quick_data_[i].key.data_.i); } + } + } else { + for (i = 0; i < tbl->count_; ++i) { + VarEntry *e = &tbl->nodes_[tbl->active_list_[i]].entry; + if (e->key.type_ == VAR_INT) { FL_COUNT_INT_KEY(e->key.data_.i); } + } + } + FL_COUNT_INT_KEY(nk); +#undef FL_COUNT_INT_KEY + uint64_t a = 0, optimal = 0, two = 1; + for (i = 0; i < 31 && total > two / 2; ++i, two <<= 1) { + a += nums[i]; + if (a > two / 2) { optimal = two; } + } + if (optimal <= (uint64_t)tbl->arr_size_) { return false; } + { + uint32_t old_size = tbl->arr_size_; + CVar *na = (CVar *)FlFastAlloc(_S, (size_t)optimal * sizeof(CVar), !__fakelua_init_flag__); + if (old_size > 0) { memcpy(na, tbl->arr_, (size_t)old_size * sizeof(CVar)); } + memset(na + old_size, 0, (size_t)(optimal - old_size) * sizeof(CVar)); + tbl->arr_ = na; + tbl->arr_cap_ = (uint32_t)optimal; + tbl->arr_size_ = (uint32_t)optimal; + if (tbl->count_ != 0) { + uint64_t j; + for (j = old_size + 1; j <= optimal && tbl->count_ != 0; ++j) { + CVar hv = FlHashGetInt(tbl, (int64_t)j); + if (hv.type_ == VAR_NIL) { continue; } + CVar key; key.type_ = VAR_INT; key.flag_ = 0; key.data_.i = (int64_t)j; + FlHashSet(tbl, key, (CVar){VAR_NIL}); + tbl->arr_[j - 1] = hv; + } + FlArrMigrate(tbl); + } + } + return true; +} + +/* 整数键 k 要插入已写满的哈希部分前调用;返回 true 表示 k 已写入数组或哈希空位,调用方直接返回。*/ +static inline bool FlArrResizeInsert(VarTable *tbl, CVar k, CVar v) { + if (k.type_ != VAR_INT || tbl->spec_set) { return false; } + if (!FlArrTryResize(tbl, k.data_.i)) { return false; } + if (FlArrSetInt(tbl, k.data_.i, v)) { return true; } + uint32_t h = (uint32_t)(k.data_.i ^ (k.data_.i >> 32)); + if (tbl->bucket_count_ == 0) { + if (tbl->count_ < 8) { + tbl->quick_data_[tbl->count_].key = k; + tbl->quick_data_[tbl->count_].val = v; + tbl->quick_data_[tbl->count_].hash = h; + tbl->count_++; + return true; + } + } else { + if (tbl->count_ < tbl->bucket_count_ && tbl->free_list_idx_ != 0xFFFFFFFF) { + FlTableInsertRaw(tbl, k, v, h); + return true; + } + } + return false; +} + +static inline void FlSetTableImpl(CVar t, CVar k, CVar v) { + k = NORMALIZE_TABLE_KEY(k); + if (UNLIKELY(t.type_ != VAR_TABLE)) { FakeluaThrowError(_S, "attempt to index a non-table value"); } + FL_CHECK_CONST_TABLE(t); + if (UNLIKELY(k.type_ == VAR_NIL)) { FakeluaThrowError(_S, "table index is nil"); } + if (UNLIKELY(k.type_ == VAR_FLOAT && isnan(k.data_.f))) { FakeluaThrowError(_S, "table index is NaN"); } + VarTable *tbl = t.data_.t; + if (UNLIKELY(tbl->spec_set)) { + bool __finish = false; + tbl->spec_set(tbl, k, v, &__finish); + if (__finish) return; + } + if (k.type_ == VAR_INT && FlArrSetInt(tbl, k.data_.i, v)) { return; } + FlHashSet(tbl, k, v); +} + +static inline void FlSetTable(CVar t, CVar k, CVar v) { + k = NORMALIZE_TABLE_KEY(k); + FlSetTableImpl(t, k, v); + if (LIKELY(t.type_ == VAR_TABLE)) { + if (k.type_ == VAR_INT) { FlSeqNoteIntSet(t.data_.t, k.data_.i, v.type_ == VAR_NIL); } + } +} + +static inline CVar FlGetTableInt(CVar t, int64_t k) { + if (UNLIKELY(t.type_ != VAR_TABLE)) { FakeluaThrowError(_S, "attempt to index a non-table value"); } + VarTable *tbl = t.data_.t; + if (LIKELY((uint64_t)k - 1 < (uint64_t)tbl->arr_size_)) { return tbl->arr_[k - 1]; } + if (UNLIKELY(tbl->spec_get)) { + CVar key_cvar; key_cvar.type_ = VAR_INT; key_cvar.flag_ = 0; key_cvar.data_.i = k; + bool __finish = false; + CVar __r = tbl->spec_get(tbl, key_cvar, &__finish); + if (__finish) return __r; + } + return FlHashGetInt(tbl, k); +} + +static inline void FlSetTableIntImpl(CVar t, int64_t k, CVar v) { + if (UNLIKELY(t.type_ != VAR_TABLE)) { FakeluaThrowError(_S, "attempt to index a non-table value"); } + FL_CHECK_CONST_TABLE(t); + VarTable *tbl = t.data_.t; + if (UNLIKELY(tbl->spec_set)) { + CVar key_cvar; key_cvar.type_ = VAR_INT; key_cvar.flag_ = 0; key_cvar.data_.i = k; + bool __finish = false; + tbl->spec_set(tbl, key_cvar, v, &__finish); + if (__finish) return; + } + if (FlArrSetInt(tbl, k, v)) { return; } + uint32_t h = (uint32_t)(k ^ (k >> 32)); + CVar key_cvar; key_cvar.type_ = VAR_INT; key_cvar.flag_ = 0; key_cvar.data_.i = k; + if (UNLIKELY(v.type_ == VAR_NIL)) { + FlHashSet(tbl, key_cvar, v); + return; + } + if (LIKELY(tbl->bucket_count_ == 0)) { + for (uint32_t i = 0; i < tbl->count_; ++i) { + if (tbl->quick_data_[i].hash == h && tbl->quick_data_[i].key.type_ == VAR_INT && tbl->quick_data_[i].key.data_.i == k) { + tbl->quick_data_[i].val = v; return; + } + } + if (tbl->count_ < 8) { + tbl->quick_data_[tbl->count_].key = key_cvar; + tbl->quick_data_[tbl->count_].val = v; + tbl->quick_data_[tbl->count_].hash = h; + tbl->count_++; return; + } + if (FlArrResizeInsert(tbl, key_cvar, v)) { return; } + FlTableRehash(tbl); + } + if (UNLIKELY(tbl->count_ >= tbl->bucket_count_ || tbl->free_list_idx_ == 0xFFFFFFFF)) { + if (FlArrResizeInsert(tbl, key_cvar, v)) { return; } + FlTableRehash(tbl); + } + FlTableInsertRaw(tbl, key_cvar, v, h); +} + +static inline void FlSetTableInt(CVar t, int64_t k, CVar v) { + if (LIKELY(t.type_ == VAR_TABLE && (!(t.flag_ & CONST_FLAG) || !__fakelua_init_flag__))) { + VarTable *tbl = t.data_.t; + uint64_t idx = (uint64_t)k - 1; + /* 新旧值都非 nil 时序列长度不变,可跳过缓存维护。*/ + if (LIKELY(idx < (uint64_t)tbl->arr_size_ && v.type_ != VAR_NIL && tbl->arr_[idx].type_ != VAR_NIL)) { + tbl->arr_[idx] = v; + return; + } + } + FlSetTableIntImpl(t, k, v); + if (LIKELY(t.type_ == VAR_TABLE)) { FlSeqNoteIntSet(t.data_.t, k, v.type_ == VAR_NIL); } +} + +static inline void FlSetTableInt_Int(CVar t, int64_t k, int64_t v) { + if (LIKELY(t.type_ == VAR_TABLE && (!(t.flag_ & CONST_FLAG) || !__fakelua_init_flag__))) { + VarTable *tbl = t.data_.t; + uint64_t idx = (uint64_t)k - 1; + if (LIKELY(idx < (uint64_t)tbl->arr_size_ && tbl->arr_[idx].type_ != VAR_NIL)) { + tbl->arr_[idx].type_ = VAR_INT; + tbl->arr_[idx].flag_ = 0; + tbl->arr_[idx].data_.i = v; + return; + } + } + CVar cv; cv.type_ = VAR_INT; cv.flag_ = 0; cv.data_.i = v; + FlSetTableIntImpl(t, k, cv); + if (LIKELY(t.type_ == VAR_TABLE)) { FlSeqNoteIntSet(t.data_.t, k, false); } +} + +static inline void FlSetTableInt_Float(CVar t, int64_t k, double v) { + if (LIKELY(t.type_ == VAR_TABLE && (!(t.flag_ & CONST_FLAG) || !__fakelua_init_flag__))) { + VarTable *tbl = t.data_.t; + uint64_t idx = (uint64_t)k - 1; + if (LIKELY(idx < (uint64_t)tbl->arr_size_ && tbl->arr_[idx].type_ != VAR_NIL)) { + tbl->arr_[idx].type_ = VAR_FLOAT; + tbl->arr_[idx].flag_ = 0; + tbl->arr_[idx].data_.f = v; + return; + } + } + CVar cv; cv.type_ = VAR_FLOAT; cv.flag_ = 0; cv.data_.f = v; + FlSetTableIntImpl(t, k, cv); + if (LIKELY(t.type_ == VAR_TABLE)) { FlSeqNoteIntSet(t.data_.t, k, false); } +} + +static inline void FlSetTableInt_Bool(CVar t, int64_t k, bool b) { + if (LIKELY(t.type_ == VAR_TABLE && (!(t.flag_ & CONST_FLAG) || !__fakelua_init_flag__))) { + VarTable *tbl = t.data_.t; + uint64_t idx = (uint64_t)k - 1; + if (LIKELY(idx < (uint64_t)tbl->arr_size_ && tbl->arr_[idx].type_ != VAR_NIL)) { + tbl->arr_[idx].type_ = VAR_BOOL; + tbl->arr_[idx].flag_ = 0; + tbl->arr_[idx].data_.b = b; + return; + } + } + CVar cv; cv.type_ = VAR_BOOL; cv.flag_ = 0; cv.data_.b = b; + FlSetTableIntImpl(t, k, cv); + if (LIKELY(t.type_ == VAR_TABLE)) { FlSeqNoteIntSet(t.data_.t, k, false); } +} + +static inline CVar FlGetTableStrId(CVar t, int64_t str_id) { + if (UNLIKELY(t.type_ != VAR_TABLE)) { FakeluaThrowError(_S, "attempt to index a non-table value"); } + VarTable *tbl = t.data_.t; + if (UNLIKELY(tbl->spec_get)) { + CVar k; k.type_ = VAR_STRINGID; k.flag_ = 0; k.data_.i = str_id; + bool __finish = false; + CVar __r = tbl->spec_get(tbl, k, &__finish); + if (__finish) return __r; + } + // hash fallback + if (UNLIKELY(tbl->count_ == 0)) { return (CVar){VAR_NIL}; } + VarString *vs = (VarString *)str_id; + uint32_t h = LIKELY(vs->hash_ != 0) ? vs->hash_ : (vs->hash_ = FlHashString(vs->data_, vs->size_)); + if (LIKELY(tbl->bucket_count_ == 0)) { + for (uint32_t i = 0; i < tbl->count_; ++i) { + if (tbl->quick_data_[i].hash == h) { + CVar ek = tbl->quick_data_[i].key; + if (LIKELY(ek.type_ == VAR_STRINGID)) { + if (ek.data_.i == str_id) { return tbl->quick_data_[i].val; } + VarString *evs = (VarString *)ek.data_.i; + if (evs->size_ == vs->size_ && memcmp(evs->data_, vs->data_, vs->size_) == 0) { return tbl->quick_data_[i].val; } + } else if (UNLIKELY(ek.type_ == VAR_STRING)) { + if (ek.data_.s->size_ == vs->size_ && memcmp(ek.data_.s->data_, vs->data_, vs->size_) == 0) { return tbl->quick_data_[i].val; } + } + } + } + } else { + uint32_t mask = tbl->bucket_count_ - 1; + uint32_t idx = h & mask; + TableNode *curr = &tbl->nodes_[idx]; + if (UNLIKELY(curr->entry.key.type_ == VAR_NIL)) { return (CVar){VAR_NIL}; } + while (1) { + if (curr->entry.hash == h) { + CVar ek = curr->entry.key; + if (LIKELY(ek.type_ == VAR_STRINGID)) { + if (ek.data_.i == str_id) { return curr->entry.val; } + VarString *evs = (VarString *)ek.data_.i; + if (evs->size_ == vs->size_ && memcmp(evs->data_, vs->data_, vs->size_) == 0) { return curr->entry.val; } + } else if (UNLIKELY(ek.type_ == VAR_STRING)) { + if (ek.data_.s->size_ == vs->size_ && memcmp(ek.data_.s->data_, vs->data_, vs->size_) == 0) { return curr->entry.val; } + } + } + uint32_t next = curr->next; + if (next == 0xFFFFFFFF) { break; } + curr = &tbl->nodes_[next]; + } + } + return (CVar){VAR_NIL}; +} + +static inline void FlSetTableStrId(CVar t, int64_t str_id, CVar v) { + if (UNLIKELY(t.type_ != VAR_TABLE)) { FakeluaThrowError(_S, "attempt to index a non-table value"); } + FL_CHECK_CONST_TABLE(t); + VarTable *tbl = t.data_.t; + if (UNLIKELY(tbl->spec_set)) { + CVar k; k.type_ = VAR_STRINGID; k.flag_ = 0; k.data_.i = str_id; + bool __finish = false; + tbl->spec_set(tbl, k, v, &__finish); + if (__finish) return; + } + // hash fallback + VarString *vs = (VarString *)str_id; + uint32_t h = LIKELY(vs->hash_ != 0) ? vs->hash_ : (vs->hash_ = FlHashString(vs->data_, vs->size_)); + CVar key_cvar; key_cvar.type_ = VAR_STRINGID; key_cvar.flag_ = 0; key_cvar.data_.i = str_id; + if (UNLIKELY(v.type_ == VAR_NIL)) { + FlSetTable((CVar){.type_ = VAR_TABLE, .data_.t = tbl}, key_cvar, v); + return; + } + if (LIKELY(tbl->bucket_count_ == 0)) { + for (uint32_t i = 0; i < tbl->count_; ++i) { + if (tbl->quick_data_[i].hash == h) { + CVar ek = tbl->quick_data_[i].key; + if (LIKELY(ek.type_ == VAR_STRINGID)) { + if (ek.data_.i == str_id) { tbl->quick_data_[i].val = v; return; } + VarString *evs = (VarString *)ek.data_.i; + if (evs->size_ == vs->size_ && memcmp(evs->data_, vs->data_, vs->size_) == 0) { tbl->quick_data_[i].val = v; return; } + } else if (UNLIKELY(ek.type_ == VAR_STRING)) { + if (ek.data_.s->size_ == vs->size_ && memcmp(ek.data_.s->data_, vs->data_, vs->size_) == 0) { tbl->quick_data_[i].val = v; return; } + } + } + } + if (tbl->count_ < 8) { + tbl->quick_data_[tbl->count_].key = key_cvar; + tbl->quick_data_[tbl->count_].val = v; + tbl->quick_data_[tbl->count_].hash = h; + tbl->count_++; return; + } + FlTableRehash(tbl); + } + if (UNLIKELY(tbl->count_ >= tbl->bucket_count_ || tbl->free_list_idx_ == 0xFFFFFFFF)) { FlTableRehash(tbl); } + FlTableInsertRaw(tbl, key_cvar, v, h); +} + +static inline void FlTableExpandMulti(CVar t, int64_t start_idx, CVar v) { + if (LIKELY(v.type_ != VAR_MULTI)) { + FlSetTableInt(t, start_idx, v); + } else { + VarMulti *m = v.data_.m; + for (uint32_t i = 0; i < m->count; ++i) { + FlSetTableInt(t, start_idx + i, m->vars[i]); + } + } +} + + +#define CheckNum(v) do { \ + if (UNLIKELY((v).type_ != VAR_INT && (v).type_ != VAR_FLOAT)) { \ + FakeluaThrowError(_S, "attempt to perform arithmetic on non-numeric value"); \ + } \ +} while(0) + +#define CheckInt(v, result) do { \ + if (LIKELY((v).type_ == VAR_INT)) { (result) = (v).data_.i; } \ + else if (UNLIKELY((v).type_ == VAR_FLOAT)) { \ + if (!FlDoubleFitsInt64((v).data_.f, &(result))) { \ + FakeluaThrowError(_S, "number has no integer representation"); \ + } \ + } else { \ + FakeluaThrowError(_S, "attempt to perform bitwise operation on non-numeric value"); \ + (result) = 0; \ + } \ +} while(0) + +#define CVAR_TO_DOUBLE(v) ((v).type_ == VAR_INT ? (double)(v).data_.i : (v).data_.f) + +#define FL_INT_ADD(a, b) ((int64_t)((uint64_t)(int64_t)(a) + (uint64_t)(int64_t)(b))) +#define FL_INT_SUB(a, b) ((int64_t)((uint64_t)(int64_t)(a) - (uint64_t)(int64_t)(b))) +#define FL_INT_MUL(a, b) ((int64_t)((uint64_t)(int64_t)(a) * (uint64_t)(int64_t)(b))) + +#define OP_ARITH_IMPL(a, b, res, op) do { \ + CVar _ra = (a); CVar _rb = (b); \ + if (LIKELY(_ra.type_ == VAR_INT && _rb.type_ == VAR_INT)) { SET_INT(res, _ra.data_.i op _rb.data_.i); } \ + else { CheckNum(_ra); CheckNum(_rb); SET_FLOAT(res, CVAR_TO_DOUBLE(_ra) op CVAR_TO_DOUBLE(_rb)); } \ +} while(0) + +#define OpAdd(a, b, res) do { \ + CVar _ra = (a); CVar _rb = (b); \ + if (LIKELY(_ra.type_ == VAR_INT && _rb.type_ == VAR_INT)) { SET_INT(res, FL_INT_ADD(_ra.data_.i, _rb.data_.i)); } \ + else { CheckNum(_ra); CheckNum(_rb); SET_FLOAT(res, CVAR_TO_DOUBLE(_ra) + CVAR_TO_DOUBLE(_rb)); } \ +} while(0) +#define OpSub(a, b, res) do { \ + CVar _ra = (a); CVar _rb = (b); \ + if (LIKELY(_ra.type_ == VAR_INT && _rb.type_ == VAR_INT)) { SET_INT(res, FL_INT_SUB(_ra.data_.i, _rb.data_.i)); } \ + else { CheckNum(_ra); CheckNum(_rb); SET_FLOAT(res, CVAR_TO_DOUBLE(_ra) - CVAR_TO_DOUBLE(_rb)); } \ +} while(0) +#define OpMul(a, b, res) do { \ + CVar _ra = (a); CVar _rb = (b); \ + if (LIKELY(_ra.type_ == VAR_INT && _rb.type_ == VAR_INT)) { SET_INT(res, FL_INT_MUL(_ra.data_.i, _rb.data_.i)); } \ + else { CheckNum(_ra); CheckNum(_rb); SET_FLOAT(res, CVAR_TO_DOUBLE(_ra) * CVAR_TO_DOUBLE(_rb)); } \ +} while(0) + +#define OpDiv(a, b, res) do { \ + CVar _ra = (a); CVar _rb = (b); CheckNum(_ra); CheckNum(_rb); \ + SET_FLOAT(res, CVAR_TO_DOUBLE(_ra) / CVAR_TO_DOUBLE(_rb)); \ +} while(0) + +#define OpFloorDiv(a, b, res) do { \ + CVar _ra = (a); CVar _rb = (b); \ + if (LIKELY(_ra.type_ == VAR_INT && _rb.type_ == VAR_INT)) { \ + int64_t _q; \ + FlFloorDivInt(_ra.data_.i, _rb.data_.i, _q); \ + SET_INT(res, _q); \ + } else { \ + CheckNum(_ra); CheckNum(_rb); \ + SET_FLOAT(res, floor(CVAR_TO_DOUBLE(_ra) / CVAR_TO_DOUBLE(_rb))); \ + } \ +} while(0) + +#define OpPow(a, b, res) do { \ + CVar _ra = (a); CVar _rb = (b); CheckNum(_ra); CheckNum(_rb); \ + SET_FLOAT(res, pow(CVAR_TO_DOUBLE(_ra), CVAR_TO_DOUBLE(_rb))); \ +} while(0) + +#define OpMod(a, b, res) do { \ + CVar _ra = (a); CVar _rb = (b); \ + if (LIKELY(_ra.type_ == VAR_INT && _rb.type_ == VAR_INT)) { \ + int64_t _r; \ + FlModInt(_ra.data_.i, _rb.data_.i, _r); \ + SET_INT(res, _r); \ + } else { \ + CheckNum(_ra); CheckNum(_rb); \ + double _fa = CVAR_TO_DOUBLE(_ra); \ + double _fb = CVAR_TO_DOUBLE(_rb); \ + SET_FLOAT(res, _fa - _fb * floor(_fa / _fb)); \ + } \ +} while(0) + +#define OP_BIT_IMPL(a, b, res, op) do { int64_t _ai; int64_t _bi; CheckInt(a, _ai); CheckInt(b, _bi); SET_INT(res, _ai op _bi); } while(0) + +#define OpBitAnd(a, b, res) OP_BIT_IMPL(a, b, res, &) +#define OpBitXor(a, b, res) OP_BIT_IMPL(a, b, res, ^) +#define OpBitOr(a, b, res) OP_BIT_IMPL(a, b, res, |) + +#define OP_SHIFT_IMPL(a, b, res, _right) do { \ + int64_t _ai; int64_t _bi; CheckInt(a, _ai); CheckInt(b, _bi); \ + if (_bi >= 64 || _bi <= -64) { SET_INT(res, 0); } \ + else if (_bi >= 0) { SET_INT(res, (int64_t)((_right) ? ((uint64_t)_ai >> _bi) : ((uint64_t)_ai << _bi))); } \ + else { SET_INT(res, (int64_t)((_right) ? ((uint64_t)_ai << (-_bi)) : ((uint64_t)_ai >> (-_bi)))); } \ +} while(0) + +#define OpRightShift(a, b, res) OP_SHIFT_IMPL(a, b, res, 1) +#define OpLeftShift(a, b, res) OP_SHIFT_IMPL(a, b, res, 0) + +static inline bool FlCmpLt(CVar a, CVar b) { + if (LIKELY(a.type_ == VAR_INT && b.type_ == VAR_INT)) { return a.data_.i < b.data_.i; } + CheckNum(a); CheckNum(b); + if (a.type_ == VAR_INT) { return !isnan(b.data_.f) && FlCompareIntDouble(a.data_.i, b.data_.f) < 0; } + else if (b.type_ == VAR_INT) { return !isnan(a.data_.f) && 0 < FlCompareIntDouble(b.data_.i, a.data_.f); } + else { return a.data_.f < b.data_.f; } +} + +static inline bool FlCmpGt(CVar a, CVar b) { + if (LIKELY(a.type_ == VAR_INT && b.type_ == VAR_INT)) { return a.data_.i > b.data_.i; } + CheckNum(a); CheckNum(b); + if (a.type_ == VAR_INT) { return !isnan(b.data_.f) && FlCompareIntDouble(a.data_.i, b.data_.f) > 0; } + else if (b.type_ == VAR_INT) { return !isnan(a.data_.f) && 0 > FlCompareIntDouble(b.data_.i, a.data_.f); } + else { return a.data_.f > b.data_.f; } +} + +static inline bool FlCmpLe(CVar a, CVar b) { + if (LIKELY(a.type_ == VAR_INT && b.type_ == VAR_INT)) { return a.data_.i <= b.data_.i; } + CheckNum(a); CheckNum(b); + if (a.type_ == VAR_INT) { return !isnan(b.data_.f) && FlCompareIntDouble(a.data_.i, b.data_.f) <= 0; } + else if (b.type_ == VAR_INT) { return !isnan(a.data_.f) && 0 <= FlCompareIntDouble(b.data_.i, a.data_.f); } + else { return a.data_.f <= b.data_.f; } +} + +static inline bool FlCmpGe(CVar a, CVar b) { + if (LIKELY(a.type_ == VAR_INT && b.type_ == VAR_INT)) { return a.data_.i >= b.data_.i; } + CheckNum(a); CheckNum(b); + if (a.type_ == VAR_INT) { return !isnan(b.data_.f) && FlCompareIntDouble(a.data_.i, b.data_.f) >= 0; } + else if (b.type_ == VAR_INT) { return !isnan(a.data_.f) && 0 >= FlCompareIntDouble(b.data_.i, a.data_.f); } + else { return a.data_.f >= b.data_.f; } +} + +static inline bool FlCmpEq(CVar a, CVar b) { + bool _eq; + VarEqual(a, b, _eq); + return _eq; +} + +static inline bool FlCmpNe(CVar a, CVar b) { + bool _eq; + VarEqual(a, b, _eq); + return !_eq; +} + +#define OpLt(a, b, res) SET_BOOL(res, FlCmpLt(a, b)) +#define OpGt(a, b, res) SET_BOOL(res, FlCmpGt(a, b)) +#define OpLe(a, b, res) SET_BOOL(res, FlCmpLe(a, b)) +#define OpGe(a, b, res) SET_BOOL(res, FlCmpGe(a, b)) + +#define OpEq(a, b, res) SET_BOOL(res, FlCmpEq(a, b)) + +#define OpNe(a, b, res) SET_BOOL(res, FlCmpNe(a, b)) + +#define OpNot(a, res) do { bool _bt; IsTrue(a, _bt); SET_BOOL(res, !_bt); } while(0) + +#define OpUnaryMinus(a, res) do { \ + CVar _ra = (a); \ + if (LIKELY(_ra.type_ == VAR_INT)) { SET_INT(res, FL_INT_SUB(0, _ra.data_.i)); } \ + else { CheckNum(_ra); SET_FLOAT(res, -_ra.data_.f); } \ +} while(0) + +#define OpBitNot(a, res) do { int64_t _ai; CheckInt(a, _ai); SET_INT(res, ~_ai); } while(0) + +#define OpLen(a, res) do { \ + CVar _lv = (a); \ + if (LIKELY(_lv.type_ == VAR_STRING)) { SET_INT(res, STR_SIZE(_lv.data_.s)); } \ + else if (_lv.type_ == VAR_STRINGID) { SET_INT(res, STR_SIZE((VarString *)_lv.data_.i)); } \ + else if (_lv.type_ == VAR_TABLE) { SET_INT(res, FlGetTableSeqLen(_lv.data_.t)); } \ + else { FakeluaThrowError(_S, "attempt to get length of a non-string/table value"); } \ +} while(0) + +#define OpConcat(a, b, res) do { res = FlConcat(a, b); } while(0) + +static inline int FlFastItoA(int64_t v, char *buf) { + if (v == 0) { + buf[0] = '0'; + return 1; + } + char tmp[24]; + int i = 0; + bool neg = false; + uint64_t u; + if (v < 0) { + neg = true; + u = 0ULL - (uint64_t)v; + } else { + u = (uint64_t)v; + } + while (u > 0) { + tmp[i++] = (char)('0' + (u % 10)); + u /= 10; + } + int len = 0; + if (neg) { + buf[len++] = '-'; + } + while (i > 0) { + buf[len++] = tmp[--i]; + } + return len; +} + +static inline int FlVarToStr(CVar v, char *buf, int buf_size) { + switch (v.type_) { + case VAR_NIL: memcpy(buf, "nil", 3); buf[3] = '\0'; return 3; + case VAR_BOOL: if (v.data_.b) { memcpy(buf, "true", 4); buf[4] = '\0'; return 4; } else { memcpy(buf, "false", 5); buf[5] = '\0'; return 5; } + case VAR_INT: return FlFastItoA(v.data_.i, buf); + case VAR_FLOAT: { + /* Lua 5.4 tostringbuff: %.14g,结果只含数字和符号时补 .0 */ + int flen = snprintf(buf, buf_size, "%.14g", v.data_.f); + int fi = 0; + if (flen > 0 && flen < buf_size) { + if (buf[fi] == '-') fi++; + for (; fi < flen; fi++) { + if (buf[fi] < '0' || buf[fi] > '9') break; + } + if (fi == flen && flen + 2 < buf_size) { + buf[flen] = '.'; + buf[flen + 1] = '0'; + flen += 2; + } + } + return flen; + } + case VAR_STRING: + case VAR_STRINGID: FakeluaThrowError(_S, "FlVarToStr: string type should be handled by caller"); return 0; + case VAR_TABLE: { int __n = snprintf(buf, buf_size - 1, "table(0x%llx", (unsigned long long)(uintptr_t)v.data_.t); if (__n > 0 && __n < buf_size - 1) { buf[__n] = ')'; buf[__n + 1] = '\0'; return __n + 1; } return __n; } + case VAR_CLOSURE: { int __n = snprintf(buf, buf_size - 1, "function(0x%llx", (unsigned long long)(uintptr_t)v.data_.cl); if (__n > 0 && __n < buf_size - 1) { buf[__n] = ')'; buf[__n + 1] = '\0'; return __n + 1; } return __n; } + default: return 0; + } +} + +// Lua 的 .. 只接受字符串和数字,nil / bool / table / function 一律报错。 +// 检查放在非字符串分支里,字符串拼接的快路径不受影响。 +#define CONCAT_CHECK_NUMERIC(v) \ + if (UNLIKELY((v).type_ != VAR_INT && (v).type_ != VAR_FLOAT)) { FakeluaThrowError(_S, "attempt to concatenate a non-string value"); } + +static inline CVar FlConcat(CVar a, CVar b) { + char buf_a[64], buf_b[64]; + const char *sa = buf_a; int la; + const char *sb = buf_b; int lb; + if (LIKELY(a.type_ == VAR_STRING)) { sa = STR_DATA(a.data_.s); la = STR_SIZE(a.data_.s); } + else if (a.type_ == VAR_STRINGID) { VarString *vs = (VarString *)a.data_.i; sa = STR_DATA(vs); la = STR_SIZE(vs); } + else if (a.type_ == VAR_INT) { la = FlFastItoA(a.data_.i, buf_a); } + else if (a.type_ == VAR_FLOAT) { la = FlVarToStr(a, buf_a, sizeof(buf_a)); } + else { FakeluaThrowError(_S, "attempt to concatenate a non-string value"); return (CVar){VAR_NIL}; } + + if (LIKELY(b.type_ == VAR_STRING)) { sb = STR_DATA(b.data_.s); lb = STR_SIZE(b.data_.s); } + else if (b.type_ == VAR_STRINGID) { VarString *vs = (VarString *)b.data_.i; sb = STR_DATA(vs); lb = STR_SIZE(vs); } + else if (b.type_ == VAR_INT) { lb = FlFastItoA(b.data_.i, buf_b); } + else if (b.type_ == VAR_FLOAT) { lb = FlVarToStr(b, buf_b, sizeof(buf_b)); } + else { FakeluaThrowError(_S, "attempt to concatenate a non-string value"); return (CVar){VAR_NIL}; } + + int total = la + lb; + if (UNLIKELY(total < la || total < lb)) { FakeluaThrowError(_S, "string concatenation result too long"); } + VarString *vs = (VarString *)FlFastAlloc(_S, sizeof(VarString) + total, !__fakelua_init_flag__); + vs->size_ = total; + vs->hash_ = 0; + memcpy(vs->data_, sa, la); + memcpy(vs->data_ + la, sb, lb); + CVar result; + result.type_ = VAR_STRING; + result.flag_ = 0; + result.data_.s = vs; + return result; +} + +// string.char(c) 单字节快路径 +static inline CVar FlStringChar1(int64_t c) { + if (UNLIKELY(c < 0 || c > 255)) { + FakeluaThrowError(_S, "bad argument to string.char: value out of range"); + } + VarString *vs = (VarString *)FlFastAlloc(_S, sizeof(VarString) + 1, !__fakelua_init_flag__); + vs->size_ = 1; + vs->hash_ = 0; + vs->data_[0] = (char)c; + CVar r; + r.type_ = VAR_STRING; + r.flag_ = 0; + r.data_.s = vs; + return r; +} + +// string.lower / upper:ASCII 单遍 + 一次 arena 分配 +static inline CVar FlStringLower(CVar s) { + const char *src = 0; + int len = 0; + if (LIKELY(s.type_ == VAR_STRING)) { src = STR_DATA(s.data_.s); len = STR_SIZE(s.data_.s); } + else if (s.type_ == VAR_STRINGID) { VarString *vs = (VarString *)s.data_.i; src = STR_DATA(vs); len = STR_SIZE(vs); } + else { return FakeluaCallByName(_S, FAKELUA_JIT_TYPE, "string.lower", 1, s); } + VarString *out = (VarString *)FlFastAlloc(_S, sizeof(VarString) + len, !__fakelua_init_flag__); + out->size_ = len; + out->hash_ = 0; + for (int i = 0; i < len; ++i) { + unsigned char c = (unsigned char)src[i]; + out->data_[i] = (c >= 'A' && c <= 'Z') ? (char)(c + 32) : (char)c; + } + CVar r; r.type_ = VAR_STRING; r.flag_ = 0; r.data_.s = out; + return r; +} + +static inline CVar FlStringUpper(CVar s) { + const char *src = 0; + int len = 0; + if (LIKELY(s.type_ == VAR_STRING)) { src = STR_DATA(s.data_.s); len = STR_SIZE(s.data_.s); } + else if (s.type_ == VAR_STRINGID) { VarString *vs = (VarString *)s.data_.i; src = STR_DATA(vs); len = STR_SIZE(vs); } + else { return FakeluaCallByName(_S, FAKELUA_JIT_TYPE, "string.upper", 1, s); } + VarString *out = (VarString *)FlFastAlloc(_S, sizeof(VarString) + len, !__fakelua_init_flag__); + out->size_ = len; + out->hash_ = 0; + for (int i = 0; i < len; ++i) { + unsigned char c = (unsigned char)src[i]; + out->data_[i] = (c >= 'a' && c <= 'z') ? (char)(c - 32) : (char)c; + } + CVar r; r.type_ = VAR_STRING; r.flag_ = 0; r.data_.s = out; + return r; +} + +// string.byte(s, i) 单位置;越界返回 nil +static inline CVar FlStringByte1(CVar s, int64_t i) { + const char *src = 0; + int len = 0; + if (LIKELY(s.type_ == VAR_STRING)) { src = STR_DATA(s.data_.s); len = STR_SIZE(s.data_.s); } + else if (s.type_ == VAR_STRINGID) { VarString *vs = (VarString *)s.data_.i; src = STR_DATA(vs); len = STR_SIZE(vs); } + else { return FakeluaCallByName(_S, FAKELUA_JIT_TYPE, "string.byte", 2, s, (CVar){.type_ = VAR_INT, .data_.i = i}); } + if (i < 0) i = len + i + 1; + if (i < 1 || i > len) return (CVar){VAR_NIL}; + return (CVar){.type_ = VAR_INT, .data_.i = (unsigned char)src[i - 1]}; +} + +// string.rep(s, n):s 重复 n 次(无分隔符,最常见形式) +static inline CVar FlStringRep(CVar s, int64_t n) { + const char *src = 0; + int len = 0; + if (LIKELY(s.type_ == VAR_STRING)) { src = STR_DATA(s.data_.s); len = STR_SIZE(s.data_.s); } + else if (s.type_ == VAR_STRINGID) { VarString *vs = (VarString *)s.data_.i; src = STR_DATA(vs); len = STR_SIZE(vs); } + else { return FakeluaCallByName(_S, FAKELUA_JIT_TYPE, "string.rep", 2, s, (CVar){.type_ = VAR_INT, .data_.i = n}); } + if (n <= 0 || len == 0) { + VarString *vs = (VarString *)FlFastAlloc(_S, sizeof(VarString), !__fakelua_init_flag__); + vs->size_ = 0; vs->hash_ = 0; vs->data_[0] = '\0'; + CVar r; r.type_ = VAR_STRING; r.flag_ = 0; r.data_.s = vs; return r; + } + // 防溢出:总长度超过 1G 回退 native。(n, len) 均转为 uint64_t 避免截断 + if ((uint64_t)n > (1073741824ULL / (uint64_t)len)) { + return FakeluaCallByName(_S, FAKELUA_JIT_TYPE, "string.rep", 2, s, (CVar){.type_ = VAR_INT, .data_.i = n}); + } + int64_t total = (int64_t)len * n; + VarString *out = (VarString *)FlFastAlloc(_S, sizeof(VarString) + total, !__fakelua_init_flag__); + out->size_ = total; + out->hash_ = 0; + char *dst = out->data_; + for (int64_t i = 0; i < n; ++i) { + memcpy(dst, src, len); + dst += len; + } + CVar r; r.type_ = VAR_STRING; r.flag_ = 0; r.data_.s = out; + return r; +} + +// string.reverse(s):字符串反转 +static inline CVar FlStringReverse(CVar s) { + const char *src = 0; + int len = 0; + if (LIKELY(s.type_ == VAR_STRING)) { src = STR_DATA(s.data_.s); len = STR_SIZE(s.data_.s); } + else if (s.type_ == VAR_STRINGID) { VarString *vs = (VarString *)s.data_.i; src = STR_DATA(vs); len = STR_SIZE(vs); } + else { return FakeluaCallByName(_S, FAKELUA_JIT_TYPE, "string.reverse", 1, s); } + VarString *out = (VarString *)FlFastAlloc(_S, sizeof(VarString) + len, !__fakelua_init_flag__); + out->size_ = len; + out->hash_ = 0; + for (int i = 0; i < len; ++i) { + out->data_[i] = src[len - 1 - i]; + } + CVar r; r.type_ = VAR_STRING; r.flag_ = 0; r.data_.s = out; + return r; +} + +// string.find(s, pattern, 1, true) 纯文本子串搜索(plain=true,从位置 1 开始) +static inline CVar FlStringFindPlain(CVar s, CVar pat) { + const char *src = 0; + int slen = 0; + if (LIKELY(s.type_ == VAR_STRING)) { src = STR_DATA(s.data_.s); slen = STR_SIZE(s.data_.s); } + else if (s.type_ == VAR_STRINGID) { VarString *vs = (VarString *)s.data_.i; src = STR_DATA(vs); slen = STR_SIZE(vs); } + else { return FakeluaCallByName(_S, FAKELUA_JIT_TYPE, "string.find", 4, s, pat, (CVar){.type_ = VAR_INT, .data_.i = 1}, (CVar){.type_ = VAR_BOOL, .data_.b = true}); } + const char *psrc = 0; + int plen = 0; + if (LIKELY(pat.type_ == VAR_STRING)) { psrc = STR_DATA(pat.data_.s); plen = STR_SIZE(pat.data_.s); } + else if (pat.type_ == VAR_STRINGID) { VarString *vs = (VarString *)pat.data_.i; psrc = STR_DATA(vs); plen = STR_SIZE(vs); } + else { return FakeluaCallByName(_S, FAKELUA_JIT_TYPE, "string.find", 4, s, pat, (CVar){.type_ = VAR_INT, .data_.i = 1}, (CVar){.type_ = VAR_BOOL, .data_.b = true}); } + if (plen == 0) return (CVar){.type_ = VAR_INT, .data_.i = 1}; + if (plen > slen) return (CVar){VAR_NIL}; + int limit = slen - plen; + for (int i = 0; i <= limit; ++i) { + if (src[i] == psrc[0] && memcmp(src + i, psrc, plen) == 0) { + return (CVar){.type_ = VAR_INT, .data_.i = i + 1}; + } + } + return (CVar){VAR_NIL}; +} + +// string.format("%d", int) +static inline CVar FlFormatInt(int64_t v) { + char buf[32]; + int n = FlFastItoA(v, buf); + VarString *vs = (VarString *)FlFastAlloc(_S, sizeof(VarString) + n, !__fakelua_init_flag__); + vs->size_ = n; + vs->hash_ = 0; + memcpy(vs->data_, buf, (size_t)n); + CVar r; r.type_ = VAR_STRING; r.flag_ = 0; r.data_.s = vs; + return r; +} + +// select("#", multi) / select(i, multi) 的内联辅助。 +// 空 Multi(含空指针)是 0 个值。编译器把没有实参的 ... 传成 nil,所以 nil 也是 0。 +// 其它单个值是 1 个值。 +static inline CVar FlSelectHash(CVar vararg) { + if (vararg.type_ == VAR_MULTI) { + if (vararg.data_.m == NULL) return (CVar){.type_ = VAR_INT, .data_.i = 0}; + return (CVar){.type_ = VAR_INT, .data_.i = (int64_t)vararg.data_.m->count}; + } + if (vararg.type_ == VAR_NIL) return (CVar){.type_ = VAR_INT, .data_.i = 0}; + return (CVar){.type_ = VAR_INT, .data_.i = 1}; +} + +static inline CVar FlSelectIndex(int64_t idx, CVar vararg) { + if (vararg.type_ == VAR_MULTI) { + VarMulti *m = vararg.data_.m; + int64_t count = (m != NULL) ? (int64_t)m->count : 0; + if (idx < 0) { + if (idx < -count - 1) FakeluaThrowError(_S, "bad argument #1 to 'select': index out of range"); + idx = count + idx + 1; + } + if (idx < 1) FakeluaThrowError(_S, "bad argument #1 to 'select': index out of range"); + if (idx > count || m == NULL) return FlAllocMulti(_S, 0); + uint32_t start = (uint32_t)(idx - 1); + uint32_t nret = m->count - start; + if (nret == 1) return m->vars[start]; + return FlSliceMulti(_S, vararg, start); + } + // 没有实参的 ... 传进来是 nil,和空 Multi 一样是 0 个值。 + // 正下标返回空,0 和负数都越界。Multi 在上面已经返回,这里碰不到热路径。 + if (vararg.type_ == VAR_NIL) { + if (idx < 1) FakeluaThrowError(_S, "bad argument #1 to 'select': index out of range"); + return FlAllocMulti(_S, 0); + } + /* 其它单个实参是 1 个值。越界下标报错;超过末尾返回 0 个值。 */ + if (idx < 0) { + if (idx < -2) FakeluaThrowError(_S, "bad argument #1 to 'select': index out of range"); + idx = idx + 2; + } + if (idx < 1) FakeluaThrowError(_S, "bad argument #1 to 'select': index out of range"); + if (idx > 1) return FlAllocMulti(_S, 0); + return vararg; +} + +// tonumber 热路径:数字直通;纯十进制整数字符串本地解析;其余回退 native +static inline int FlIsSpaceByte(unsigned char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; +} + +static inline CVar FlTonumber(CVar v) { + if (LIKELY(v.type_ == VAR_INT || v.type_ == VAR_FLOAT)) return v; + const char *p = 0; + int len = 0; + if (LIKELY(v.type_ == VAR_STRING)) { p = STR_DATA(v.data_.s); len = STR_SIZE(v.data_.s); } + else if (v.type_ == VAR_STRINGID) { VarString *vs = (VarString *)v.data_.i; p = STR_DATA(vs); len = STR_SIZE(vs); } + else { return (CVar){VAR_NIL}; } + while (len > 0 && FlIsSpaceByte((unsigned char)p[0])) { ++p; --len; } + while (len > 0 && FlIsSpaceByte((unsigned char)p[len - 1])) { --len; } + if (len <= 0) return (CVar){VAR_NIL}; + // 0x 十六进制等复杂形式走 native + if (len >= 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + return FakeluaCallByName(_S, FAKELUA_JIT_TYPE, "tonumber", 1, v); + } + if (len >= 3 && (p[0] == '+' || p[0] == '-') && p[1] == '0' && (p[2] == 'x' || p[2] == 'X')) { + return FakeluaCallByName(_S, FAKELUA_JIT_TYPE, "tonumber", 1, v); + } + int neg = 0; + int i = 0; + if (p[0] == '+') { i = 1; } + else if (p[0] == '-') { neg = 1; i = 1; } + if (i >= len) return (CVar){VAR_NIL}; + // 含小数点/指数 → native + { int j; for (j = i; j < len; ++j) { + unsigned char c = (unsigned char)p[j]; + if (c == '.' || c == 'e' || c == 'E') { + return FakeluaCallByName(_S, FAKELUA_JIT_TYPE, "tonumber", 1, v); + } + if (c < '0' || c > '9') return (CVar){VAR_NIL}; + } } + int64_t val = 0; + for (; i < len; ++i) { + // 溢出检查:val * 10 + digit > INT64_MAX 时回退到 native(返回 float) + // 阈值: (INT64_MAX - digit) / 10,等价于 val > (INT64_MAX - digit) / 10 + int digit = p[i] - '0'; + if (val > (INT64_MAX - digit) / 10) { + return FakeluaCallByName(_S, FAKELUA_JIT_TYPE, "tonumber", 1, v); + } + val = val * 10 + digit; + } + if (neg) val = -val; + return (CVar){.type_ = VAR_INT, .data_.i = val}; +} + +// 空表预扩到至少 need 个桶(反复 FlTableRehash,空表拷贝成本可忽略) +static inline void FlEnsureTableBuckets(VarTable *tbl, uint32_t need) { + if (need < 1) need = 1; + uint32_t target = need - 1; + target |= target >> 1; target |= target >> 2; target |= target >> 4; + target |= target >> 8; target |= target >> 16; target += 1; + while (tbl->bucket_count_ < target) { + FlTableRehash(tbl); + if (UNLIKELY(tbl->bucket_count_ == 0)) break; + } +} + +// 空表预扩容的节点数组必须能一次分配出来(与 native_table.cpp 的 MovePresizeFits 一致)。 +static inline int FlMovePresizeFits(uint64_t count) { + if (count == 0 || count > 0xFFFFFFFFULL) return 0; + uint32_t min_buckets = (uint32_t)count; + uint32_t buckets = min_buckets - 1; + buckets |= buckets >> 1; buckets |= buckets >> 2; buckets |= buckets >> 4; + buckets |= buckets >> 8; buckets |= buckets >> 16; buckets += 1; + if (buckets < min_buckets) return 0; + uint64_t total = (uint64_t)buckets + (uint64_t)(buckets / 2); + uint64_t bytes = total * sizeof(TableNode) + total * sizeof(uint32_t); + return bytes <= (1024ULL * 1024ULL); +} + +// table.move 内联实现(完整对齐 Lua 5.4 边界/回绕检查与同表重叠后向拷贝) +static inline CVar FlTableMove(CVar src, int64_t f, int64_t e, int64_t t, CVar dst) { + if (dst.type_ == VAR_NIL) dst = src; + if (UNLIKELY(src.type_ != VAR_TABLE || !src.data_.t || dst.type_ != VAR_TABLE || !dst.data_.t)) { + FakeluaThrowError(_S, "bad argument to 'table.move' (table expected)"); + } + FL_CHECK_CONST_TABLE(dst); + if (e >= f) { + uint64_t count = (uint64_t)e - (uint64_t)f + 1; + if (UNLIKELY(count == 0 || count > (uint64_t)INT64_MAX)) { + FakeluaThrowError(_S, "bad argument #3 to 'table.move' (too many elements to move)"); + } + uint64_t last_off = count - 1; + if (UNLIKELY(t > 0 && (uint64_t)t > (uint64_t)INT64_MAX - last_off)) { + FakeluaThrowError(_S, "bad argument #4 to 'table.move' (destination wrap around)"); + } + int64_t icount = (int64_t)count; + int same = (src.data_.t == dst.data_.t); + if (!same && dst.data_.t->count_ == 0 && icount > 8 && t != (int64_t)dst.data_.t->arr_size_ + 1 && FlMovePresizeFits(count)) { + FlEnsureTableBuckets(dst.data_.t, (uint32_t)icount); + } + if (!same || t <= f || t > e) { + int64_t i; + for (i = 0; i < icount; ++i) { + CVar val = FlGetTableInt(src, (int64_t)((uint64_t)f + (uint64_t)i)); + FlSetTableInt(dst, (int64_t)((uint64_t)t + (uint64_t)i), val); + } + } else { + int64_t i; + for (i = icount - 1; i >= 0; --i) { + CVar val = FlGetTableInt(src, (int64_t)((uint64_t)f + (uint64_t)i)); + FlSetTableInt(dst, (int64_t)((uint64_t)t + (uint64_t)i), val); + } + } + } + return dst; +} + +// table.unpack 内联实现(完整对齐 Lua 5.4 区间回绕与单块分配上限检查) +static inline CVar FlTableUnpack(CVar tbl, int64_t start_i, int64_t end_j) { + if (start_i > end_j) return FlAllocMulti(_S, 0); + uint64_t count64 = (uint64_t)end_j - (uint64_t)start_i + 1; + uint64_t max_unpack = (1024ULL * 1024ULL - sizeof(VarMulti)) / sizeof(CVar); + if (UNLIKELY(count64 == 0 || count64 > 2147483647ULL || count64 > max_unpack)) { + FakeluaThrowError(_S, "too many results to unpack"); + } + uint32_t count = (uint32_t)count64; + CVar multi = FlAllocMulti(_S, count); + uint32_t i; + for (i = 0; i < count; ++i) { + multi.data_.m->vars[i] = FlGetTableInt(tbl, (int64_t)((uint64_t)start_i + (uint64_t)i)); + } + return multi; +} + +#define GET_TABLE_ENTRY(tbl, idx, k, v) do { \ + VarTable *__gt = (tbl).data_.t; \ + uint32_t __gi = (idx); \ + uint32_t __spec_cnt = __gt->spec_count; \ + if (LIKELY(__gi < __spec_cnt)) { \ + (k) = __gt->spec_keys[__gi]; \ + (v) = __gt->spec_vals[__gi]; \ + } else { \ + __gi -= __spec_cnt; \ + if (__gi < __gt->arr_size_) { \ + (k).type_ = VAR_INT; (k).flag_ = 0; (k).data_.i = (int64_t)__gi + 1; \ + (v) = __gt->arr_[__gi]; \ + } else { \ + __gi -= __gt->arr_size_; \ + if (UNLIKELY(__gi >= __gt->count_)) { \ + (k).type_ = VAR_NIL; (v).type_ = VAR_NIL; \ + } else if (LIKELY(__gt->bucket_count_ == 0)) { \ + (k) = __gt->quick_data_[__gi].key; \ + (v) = __gt->quick_data_[__gi].val; \ + } else { \ + uint32_t __gti_node_idx = __gt->active_list_[__gi]; \ + (k) = __gt->nodes_[__gti_node_idx].entry.key; \ + (v) = __gt->nodes_[__gti_node_idx].entry.val; \ + } \ + } \ + } \ +} while(0) + +#define FlFloorDivQuotient(a, b, q) do { \ + if (UNLIKELY((b) == (int64_t)-1)) { \ + (q) = (int64_t)((uint64_t)0 - (uint64_t)(int64_t)(a)); \ + } else { \ + (q) = (a) / (b); \ + if (((a) ^ (b)) < 0 && (a) % (b) != 0) { (q) -= 1; } \ + } \ +} while(0) + +#define FlFloorDivInt(a, b, result) do { \ + int64_t __fl_a = (a); int64_t __fl_b = (b); \ + if (UNLIKELY(__fl_b == 0)) { FakeluaThrowError(_S, "floor division by zero"); } \ + int64_t __fl_q; \ + FlFloorDivQuotient(__fl_a, __fl_b, __fl_q); \ + (result) = __fl_q; \ +} while(0) + +#define FlModInt(a, b, result) do { \ + int64_t __fm_a = (a); int64_t __fm_b = (b); \ + if (UNLIKELY(__fm_b == 0)) { FakeluaThrowError(_S, "modulo by zero"); } \ + if (UNLIKELY(__fm_b == (int64_t)-1)) { (result) = 0; } \ + else { \ + int64_t __fm_q; \ + FlFloorDivQuotient(__fm_a, __fm_b, __fm_q); \ + (result) = __fm_a - __fm_b * __fm_q; \ + } \ +} while(0) + +#define FlModFloat(a, b, result) do { \ + double __fmf_a = (a); double __fmf_b = (b); \ + (result) = __fmf_a - __fmf_b * floor(__fmf_a / __fmf_b); \ +} while(0) + +// Lua 5.4 pushnumint:d 能无损落进 int64 时返回 integer,否则保留 float +// (inf/-inf/nan/超出 [-2^63, 2^63) 均不报错,仍是 float)。 +static inline CVar FlPushNumInt(double d) { + CVar r; + if (isfinite(d) && d >= -9223372036854775808.0 && d < 9223372036854775808.0) { + r.type_ = VAR_INT; + r.data_.i = (int64_t)d; + } else { + r.type_ = VAR_FLOAT; + r.data_.f = d; + } + return r; +} + +static inline CVar FlMathFloor(CVar v) { + if (v.type_ == VAR_INT) return v; + return FlPushNumInt(floor((double)v.data_.f)); +} + +static inline CVar FlMathCeil(CVar v) { + if (v.type_ == VAR_INT) return v; + return FlPushNumInt(ceil((double)v.data_.f)); +} + +// Lua 5.4 math.fmod:两整数走整数取模(0 报错、-1 特判为 0 避开溢出), +// 任一浮点恒返回 float(浮点除零是 NaN,不报错)。 +static inline CVar FlMathFmod(CVar a, CVar b) { + CVar r; + if (a.type_ == VAR_INT && b.type_ == VAR_INT) { + int64_t d = b.data_.i; + if (d == 0) { + // 错误串结尾不能紧贴右圆括号:本头文件整体以 R 圆括号原始串内嵌进 c_gen, + // 右圆括号紧跟双引号会提前终止原始串。 + FakeluaThrowError(_S, "bad argument #2 to 'fmod': zero divisor"); + } + r.type_ = VAR_INT; + r.data_.i = (d == -1) ? 0 : a.data_.i % d; + return r; + } + r.type_ = VAR_FLOAT; + r.data_.f = fmod(CVAR_TO_DOUBLE(a), CVAR_TO_DOUBLE(b)); + return r; +} + +// Lua 5.4 math.modf:整数部分向零取整并按 pushnumint 决定 int/float, +// 无小数部分(整数值或 inf)时小数部分为 +0.0。 +static inline CVar FlMathModf(CVar v) { + double n; + double ip; + double fp; + CVar r; + r = FlAllocMulti(_S, 2); + if (v.type_ == VAR_INT) { + r.data_.m->vars[0] = v; + r.data_.m->vars[1] = (CVar){.type_ = VAR_FLOAT, .data_.f = 0.0}; + return r; + } + n = (double)v.data_.f; + ip = (n < 0) ? ceil(n) : floor(n); + fp = (n == ip) ? 0.0 : n - ip; + r.data_.m->vars[0] = FlPushNumInt(ip); + r.data_.m->vars[1] = (CVar){.type_ = VAR_FLOAT, .data_.f = fp}; + return r; +} + +static inline double FlMathLdexp(double value, int64_t exponent) { + if (exponent > 2147483647LL) exponent = 2147483647LL; + if (exponent < (-2147483647LL - 1)) exponent = (-2147483647LL - 1); + return ldexp(value, (int)exponent); +} + +static inline int64_t FlRandomInt64(void) { + uint64_t value = 0; + for (int i = 0; i < 5; ++i) { + value = (value << 15) | (uint64_t)(rand() & 0x7fff); + } + return (int64_t)value; +} + +// 整数 for 步进:用无符号加法探测有符号溢出。溢出返回 0 且不改 *ctrl +//(避免 maxinteger++ 变成 mininteger 后死循环)。continue 仍走 for 的 incr 子句。 +static inline int FlForIntAdvance(int64_t *ctrl, int64_t step) { + int64_t cur = *ctrl; + int64_t next = (int64_t)((uint64_t)cur + (uint64_t)step); + if ((cur ^ step) >= 0 && (next ^ cur) < 0) return 0; + *ctrl = next; + return 1; +} + +// Lua 5.4 forlimit:无法落入 int64 时按符号裁成 min/max integer;NaN 与负无穷同样处理。 +// 返回 1 表示整段跳过。 +static inline int FlForLimitToInt(double flim, int64_t step, int64_t *out) { + int64_t p = 0; + if (FlDoubleFitsInt64(flim, &p)) { + *out = p; + return 0; + } + if (!isfinite(flim)) { + if (flim > 0.0) { + if (step < 0) return 1; + *out = INT64_MAX; + return 0; + } + if (step > 0) return 1; + *out = INT64_MIN; + return 0; + } + if (flim >= FL_INT64_FLOAT_EXCL) { + if (step < 0) return 1; + *out = INT64_MAX; + return 0; + } + if (flim < (double)INT64_MIN) { + if (step > 0) return 1; + *out = INT64_MIN; + return 0; + } + *out = step > 0 ? (int64_t)floor(flim) : (int64_t)ceil(flim); + return 0; +} + +#define FlToIntChecked(v, result) do { \ + if (!FlDoubleFitsInt64((double)(v), &(result))) { \ + FakeluaThrowError(_S, "number has no integer representation"); \ + } \ +} while(0) + +#define FlShiftIntImpl(a, b, result, _right) do { \ + int64_t __sh_a = (a); int64_t __sh_b = (b); \ + if (UNLIKELY(__sh_b >= 64 || __sh_b <= -64)) { \ + (result) = (int64_t)0; \ + } else if (__sh_b >= 0) { \ + (result) = (int64_t)((_right) ? ((uint64_t)__sh_a >> __sh_b) : ((uint64_t)__sh_a << __sh_b)); \ + } else { \ + (result) = (int64_t)((_right) ? ((uint64_t)__sh_a << (-__sh_b)) : ((uint64_t)__sh_a >> (-__sh_b))); \ + } \ +} while(0) + +#define FlLShiftInt(a, b, result) FlShiftIntImpl(a, b, result, 0) + +#define FlRShiftInt(a, b, result) FlShiftIntImpl(a, b, result, 1) + +#define FlLenInt(v, result) do { \ + CVar __fl_v = (v); \ + if (LIKELY(__fl_v.type_ == VAR_STRING)) { \ + (result) = (int64_t)STR_SIZE(__fl_v.data_.s); \ + } else if (UNLIKELY(__fl_v.type_ == VAR_STRINGID)) { \ + (result) = (int64_t)STR_SIZE((VarString *)__fl_v.data_.i); \ + } else if (UNLIKELY(__fl_v.type_ == VAR_TABLE)) { \ + (result) = (int64_t)FlGetTableSeqLen(__fl_v.data_.t); \ + } else { \ + FakeluaThrowError(_S, "attempt to get length of a non-string/table value"); \ + (result) = 0; \ + } \ +} while(0) + +// 获取本 State 的日志级别(由 C++ 侧初始化和设置) +// 返回值: 0=Trace, 1=Debug, 2=Info, 3=Warn, 4=Error, 5=Critical, 6=Off +// 使用函数而非全局变量,避免 Windows DLL 导入需要 __declspec(dllimport) 的问题 +extern int GetLogLevel(State *s); + +// C++ 日志函数声明(供宏调用) +void FakeluaLogLua(State *s, int level, CVar msg, const char *file, int line, const char *fname); + +// 日志级别常量(与 C++ LogLevel 枚举一致) +enum { + FAKELUA_LOGLEVEL_TRACE = 0, + FAKELUA_LOGLEVEL_DEBUG = 1, + FAKELUA_LOGLEVEL_INFO = 2, + FAKELUA_LOGLEVEL_WARN = 3, + FAKELUA_LOGLEVEL_ERROR = 4, + FAKELUA_LOGLEVEL_CRITICAL = 5, + FAKELUA_LOGLEVEL_OFF = 6, +}; + +// 日志级别检查宏:先检查级别,只有启用时才调用 C++ 函数 +// 这样 log.debug(expensive_func()) 在级别禁用时完全不会执行 expensive_func() +#define FAKELUA_LOG_CHECK(level) ((level) >= GetLogLevel(_S)) + +// 日志宏:级别检查 + 调用 C++ 日志函数 +// 参数 msg 只在级别启用时才会被求值 +#define FAKELUA_LOG_TRACE(msg, file, line, fname) \ + do { if (FAKELUA_LOG_CHECK(FAKELUA_LOGLEVEL_TRACE)) FakeluaLogLua(_S, FAKELUA_LOGLEVEL_TRACE, msg, file, line, fname); } while (0) +#define FAKELUA_LOG_DEBUG(msg, file, line, fname) \ + do { if (FAKELUA_LOG_CHECK(FAKELUA_LOGLEVEL_DEBUG)) FakeluaLogLua(_S, FAKELUA_LOGLEVEL_DEBUG, msg, file, line, fname); } while (0) +#define FAKELUA_LOG_INFO(msg, file, line, fname) \ + do { if (FAKELUA_LOG_CHECK(FAKELUA_LOGLEVEL_INFO)) FakeluaLogLua(_S, FAKELUA_LOGLEVEL_INFO, msg, file, line, fname); } while (0) +#define FAKELUA_LOG_WARN(msg, file, line, fname) \ + do { if (FAKELUA_LOG_CHECK(FAKELUA_LOGLEVEL_WARN)) FakeluaLogLua(_S, FAKELUA_LOGLEVEL_WARN, msg, file, line, fname); } while (0) +#define FAKELUA_LOG_ERROR(msg, file, line, fname) \ + do { if (FAKELUA_LOG_CHECK(FAKELUA_LOGLEVEL_ERROR)) FakeluaLogLua(_S, FAKELUA_LOGLEVEL_ERROR, msg, file, line, fname); } while (0) +#define FAKELUA_LOG_CRITICAL(msg, file, line, fname) \ + do { if (FAKELUA_LOG_CHECK(FAKELUA_LOGLEVEL_CRITICAL)) FakeluaLogLua(_S, FAKELUA_LOGLEVEL_CRITICAL, msg, file, line, fname); } while (0) + diff --git a/src/compile/c_runtime_header.h b/src/compile/c_runtime_header.h index dfcc662e..eabe9ba4 100644 --- a/src/compile/c_runtime_header.h +++ b/src/compile/c_runtime_header.h @@ -4,11 +4,21 @@ // This file contains the C type definitions, macros, and runtime functions // that are injected into every generated C compilation unit. // Extracted from c_gen.cpp for maintainability. +// +// Coverage view: src/compile/c_runtime_cov.h holds the same raw C body (no C++ +// wrapper) so Codecov can browse Fl* hits. CGen emits +// #line 1 "src/compile/c_runtime_cov.h" +// around kCRuntimeHeader. Keep that file byte-identical to the raw-string body +// (leading newline in the string is not part of the file; see GenerateHeader). namespace fakelua { +// Stable in-repo path used in #line for gcov/lcov/Codecov (repo-root relative). +inline constexpr const char *kCRuntimeCovPath = "src/compile/c_runtime_cov.h"; + // The C runtime header content as a raw string literal. // The _S (State pointer) is prepended by CGen::GenerateHeader() before this content. +// Source of truth for the runtime text; sync c_runtime_cov.h when editing. inline constexpr const char *kCRuntimeHeader = R"FL_RUNTIME( #include @@ -133,7 +143,7 @@ enum { spec 里,不在 quick/nodes。以前从 spec_count 起扫,等于把字段个数当成 #。 */ static inline bool FlTableHasIntKey(VarTable *t, int64_t k) { if (t->spec_get) { - CVar key_cvar; key_cvar.type_ = VAR_INT; key_cvar.data_.i = k; + CVar key_cvar; key_cvar.type_ = VAR_INT; key_cvar.flag_ = 0; key_cvar.data_.i = k; bool __finish = false; CVar __r = t->spec_get(t, key_cvar, &__finish); if (__finish) return __r.type_ != VAR_NIL; @@ -1270,7 +1280,7 @@ static inline CVar FlGetTableInt(CVar t, int64_t k) { VarTable *tbl = t.data_.t; if (LIKELY((uint64_t)k - 1 < (uint64_t)tbl->arr_size_)) { return tbl->arr_[k - 1]; } if (UNLIKELY(tbl->spec_get)) { - CVar key_cvar; key_cvar.type_ = VAR_INT; key_cvar.data_.i = k; + CVar key_cvar; key_cvar.type_ = VAR_INT; key_cvar.flag_ = 0; key_cvar.data_.i = k; bool __finish = false; CVar __r = tbl->spec_get(tbl, key_cvar, &__finish); if (__finish) return __r; @@ -1283,14 +1293,14 @@ static inline void FlSetTableIntImpl(CVar t, int64_t k, CVar v) { FL_CHECK_CONST_TABLE(t); VarTable *tbl = t.data_.t; if (UNLIKELY(tbl->spec_set)) { - CVar key_cvar; key_cvar.type_ = VAR_INT; key_cvar.data_.i = k; + CVar key_cvar; key_cvar.type_ = VAR_INT; key_cvar.flag_ = 0; key_cvar.data_.i = k; bool __finish = false; tbl->spec_set(tbl, key_cvar, v, &__finish); if (__finish) return; } if (FlArrSetInt(tbl, k, v)) { return; } uint32_t h = (uint32_t)(k ^ (k >> 32)); - CVar key_cvar; key_cvar.type_ = VAR_INT; key_cvar.data_.i = k; + CVar key_cvar; key_cvar.type_ = VAR_INT; key_cvar.flag_ = 0; key_cvar.data_.i = k; if (UNLIKELY(v.type_ == VAR_NIL)) { FlHashSet(tbl, key_cvar, v); return; @@ -1383,7 +1393,7 @@ static inline CVar FlGetTableStrId(CVar t, int64_t str_id) { if (UNLIKELY(t.type_ != VAR_TABLE)) { FakeluaThrowError(_S, "attempt to index a non-table value"); } VarTable *tbl = t.data_.t; if (UNLIKELY(tbl->spec_get)) { - CVar k; k.type_ = VAR_STRINGID; k.data_.i = str_id; + CVar k; k.type_ = VAR_STRINGID; k.flag_ = 0; k.data_.i = str_id; bool __finish = false; CVar __r = tbl->spec_get(tbl, k, &__finish); if (__finish) return __r; @@ -1434,7 +1444,7 @@ static inline void FlSetTableStrId(CVar t, int64_t str_id, CVar v) { FL_CHECK_CONST_TABLE(t); VarTable *tbl = t.data_.t; if (UNLIKELY(tbl->spec_set)) { - CVar k; k.type_ = VAR_STRINGID; k.data_.i = str_id; + CVar k; k.type_ = VAR_STRINGID; k.flag_ = 0; k.data_.i = str_id; bool __finish = false; tbl->spec_set(tbl, k, v, &__finish); if (__finish) return; @@ -1442,7 +1452,7 @@ static inline void FlSetTableStrId(CVar t, int64_t str_id, CVar v) { // hash fallback VarString *vs = (VarString *)str_id; uint32_t h = LIKELY(vs->hash_ != 0) ? vs->hash_ : (vs->hash_ = FlHashString(vs->data_, vs->size_)); - CVar key_cvar; key_cvar.type_ = VAR_STRINGID; key_cvar.data_.i = str_id; + CVar key_cvar; key_cvar.type_ = VAR_STRINGID; key_cvar.flag_ = 0; key_cvar.data_.i = str_id; if (UNLIKELY(v.type_ == VAR_NIL)) { FlSetTable((CVar){.type_ = VAR_TABLE, .data_.t = tbl}, key_cvar, v); return; diff --git a/src/jit/gcc_handle.cpp b/src/jit/gcc_handle.cpp index c24362b8..0f73b63b 100644 --- a/src/jit/gcc_handle.cpp +++ b/src/jit/gcc_handle.cpp @@ -32,9 +32,14 @@ GCCHandle::~GCCHandle() { // Best-effort cleanup of temporary compilation artifacts. std::error_code ec; +#if defined(USE_COV) + // Keep the emitted .c so lcov can attribute arcs to a real source path. + // .gcno/.gcda sit beside the .so basename and are intentionally left for harvest. +#else if (!c_file_.empty()) { std::filesystem::remove(c_file_, ec); } +#endif if (!so_file_.empty()) { std::filesystem::remove(so_file_, ec); } diff --git a/src/jit/gcc_jit.cpp b/src/jit/gcc_jit.cpp index bc845135..01caae42 100644 --- a/src/jit/gcc_jit.cpp +++ b/src/jit/gcc_jit.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #if defined(_WIN32) #define NOMINMAX @@ -82,7 +83,23 @@ GccJitter::GccJitter(State *s) : s_(s) { void GccJitter::Compile(const ParseResult &pr, const GenResult &gr, const CompileConfig &cfg) { LOG_INFO(s_, "engine", "GCC JIT compile start: {}, {} functions", pr.file_name, gr.function_names.size()); +#if defined(USE_COV) + // Coverage builds: put JIT temps under the CMake build tree so .gcno/.gcda are + // visible to `lcov --directory build`. Keep random names to avoid collisions. + std::string c_file; + { + const std::filesystem::path cov_dir(FAKELUA_JIT_COV_DIR); + std::error_code ec; + std::filesystem::create_directories(cov_dir, ec); + std::mt19937 rng{std::random_device{}()}; + std::uniform_int_distribution dist(100000, 999999); + do { + c_file = (cov_dir / ("fakelua_jit_" + std::to_string(dist(rng)) + ".c")).string(); + } while (std::ifstream(c_file)); + } +#else const std::string c_file = GenerateTmpFilename("fakelua_jit_", ".c"); +#endif const std::string so_file = c_file.substr(0, c_file.size() - kCExtLen) + #if defined(_WIN32) ".dll"; @@ -93,10 +110,13 @@ void GccJitter::Compile(const ParseResult &pr, const GenResult &gr, const Compil #endif const std::string log_file = c_file.substr(0, c_file.size() - kCExtLen) + ".gcc.log"; + const std::string obj_file = c_file.substr(0, c_file.size() - kCExtLen) + ".o"; + // RAII cleanup for temp files on early error paths (before GCCHandle takes ownership). const auto CleanupOnError = [&]() { std::error_code ec; std::filesystem::remove(c_file, ec); + std::filesystem::remove(obj_file, ec); std::filesystem::remove(so_file, ec); std::filesystem::remove(log_file, ec); }; @@ -119,65 +139,174 @@ void GccJitter::Compile(const ParseResult &pr, const GenResult &gr, const Compil } #endif - std::vector args; - args.emplace_back(compiler); - args.emplace_back("-x"); - args.emplace_back("c"); - args.emplace_back("-shared"); -#if !defined(_WIN32) - args.emplace_back("-fPIC"); -#endif -#if defined(__APPLE__) - // macOS 的 .dylib 需要 -undefined dynamic_lookup 选项, - // 允许运行时由宿主进程提供未解析的符号。 - args.emplace_back("-undefined"); - args.emplace_back("dynamic_lookup"); - args.emplace_back("-Wl,-adhoc_codesign"); -#endif - args.emplace_back(cfg.debug_mode ? "-O0" : "-O3"); - args.emplace_back("-DFAKELUA_JIT_TYPE=" + std::to_string(static_cast(JIT_GCC))); - for (const auto &path: s_->GetStateConfig().gcc_config.include_paths) { - args.emplace_back("-I" + path); - } + const auto opt_flag = cfg.debug_mode ? "-O0" : "-O3"; + const std::string jit_type_def = "-DFAKELUA_JIT_TYPE=" + std::to_string(static_cast(JIT_GCC)); + + auto append_includes_and_libs = [&](std::vector &a, bool for_link) { + for (const auto &path: s_->GetStateConfig().gcc_config.include_paths) { + a.emplace_back("-I" + path); + } + if (!for_link) { + return; + } #if defined(_WIN32) - for (const auto &path: GetWindowsDefaultLibraryPaths()) { - args.emplace_back("-L" + path); - } - args.emplace_back("-lfakelua"); + for (const auto &path: GetWindowsDefaultLibraryPaths()) { + a.emplace_back("-L" + path); + } + a.emplace_back("-lfakelua"); #endif - for (const auto &path: s_->GetStateConfig().gcc_config.library_paths) { - args.emplace_back("-L" + path); - } - for (const auto &lib: s_->GetStateConfig().gcc_config.libraries) { - args.emplace_back("-l" + lib); - } - args.emplace_back("-o"); - args.emplace_back(so_file); - args.emplace_back(c_file); + for (const auto &path: s_->GetStateConfig().gcc_config.library_paths) { + a.emplace_back("-L" + path); + } + for (const auto &lib: s_->GetStateConfig().gcc_config.libraries) { + a.emplace_back("-l" + lib); + } + }; // Helper: build null-terminated argv from std::vector. - auto build_argv = [&]() { + auto build_argv = [](std::vector &a) { std::vector argv; - argv.reserve(args.size() + 1); - for (auto &arg: args) { + argv.reserve(a.size() + 1); + for (auto &arg: a) { argv.emplace_back(arg.data()); } argv.emplace_back(nullptr); return argv; }; + { + std::ofstream trunc(log_file, std::ios::trunc); + } + + auto run_gcc = [&](std::vector &a) { #if defined(_WIN32) - auto argv = build_argv(); - const int compile_status = _spawnvp(_P_WAIT, compiler.c_str(), argv.data()); - if (compile_status == -1) { - CleanupOnError(); - ThrowFakeluaException(std::format("GCC compile failed for {}: cannot execute gcc (errno {}: {}). cmd: {}", pr.file_name, errno, std::strerror(errno), JoinCommand(args))); + auto argv = build_argv(a); + const int compile_status = _spawnvp(_P_WAIT, compiler.c_str(), argv.data()); + if (compile_status == -1) { + CleanupOnError(); + ThrowFakeluaException(std::format("GCC compile failed for {}: cannot execute gcc (errno {}: {}). cmd: {}", pr.file_name, errno, std::strerror(errno), JoinCommand(a))); + } + if (compile_status != 0) { + CleanupOnError(); + ThrowFakeluaException(std::format("GCC compile failed for {} with exit code {}. cmd: {}", pr.file_name, compile_status, JoinCommand(a))); + } +#else + auto argv = build_argv(a); + const int log_fd = open(log_file.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0644); + if (log_fd < 0) { + CleanupOnError(); + ThrowFakeluaException(std::format("GCC compile failed, cannot open log file {}", log_file)); + } + + // Block SIGCHLD before fork to prevent the default handler from turning + // the child into a zombie before we get to waitpid. + sigset_t old_mask, block_mask; + sigemptyset(&block_mask); + sigaddset(&block_mask, SIGCHLD); + pthread_sigmask(SIG_BLOCK, &block_mask, &old_mask); + + const pid_t pid = fork(); + if (pid < 0) { + pthread_sigmask(SIG_SETMASK, &old_mask, nullptr); + close(log_fd); + CleanupOnError(); + ThrowFakeluaException(std::format("GCC compile failed, fork failed for {}", pr.file_name)); + } + + if (pid == 0) { + // Restore default signal mask in child before exec. + pthread_sigmask(SIG_SETMASK, &old_mask, nullptr); + dup2(log_fd, STDOUT_FILENO); + dup2(log_fd, STDERR_FILENO); + close(log_fd); + execvp(compiler.c_str(), argv.data()); + _exit(kExecFailedStatus); + } + close(log_fd); + + int status = 0; + if (waitpid(pid, &status, 0) < 0) { + pthread_sigmask(SIG_SETMASK, &old_mask, nullptr); + CleanupOnError(); + ThrowFakeluaException(std::format("GCC compile failed, waitpid failed for {}", pr.file_name)); + } + pthread_sigmask(SIG_SETMASK, &old_mask, nullptr); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + std::string gcc_log; + if (std::ifstream ifs(log_file); ifs.is_open()) { + gcc_log.assign(std::istreambuf_iterator(ifs), std::istreambuf_iterator()); + } + CleanupOnError(); + ThrowFakeluaException(std::format("GCC compile failed for {} (log: {})\n{}", pr.file_name, log_file, gcc_log)); + } +#endif + }; + +#if defined(USE_COV) + // Two-step compile so gcov notes/data are named `.gcno/.gcda` (not + // `.so-.gcno` from one-shot -shared of a .c file). + { + std::vector compile_args{compiler, "-x", "c", "-c"}; +#if !defined(_WIN32) + compile_args.emplace_back("-fPIC"); +#endif + compile_args.emplace_back(opt_flag); + compile_args.emplace_back("-fprofile-arcs"); + compile_args.emplace_back("-ftest-coverage"); + compile_args.emplace_back("-fprofile-update=atomic"); + compile_args.emplace_back(jit_type_def); + append_includes_and_libs(compile_args, false); + compile_args.emplace_back("-o"); + compile_args.emplace_back(obj_file); + compile_args.emplace_back(c_file); + run_gcc(compile_args); } - if (compile_status != 0) { - CleanupOnError(); - ThrowFakeluaException(std::format("GCC compile failed for {} with exit code {}. cmd: {}", pr.file_name, compile_status, JoinCommand(args))); + { + std::vector link_args{compiler, "-shared"}; +#if !defined(_WIN32) + link_args.emplace_back("-fPIC"); +#endif +#if defined(__APPLE__) + link_args.emplace_back("-undefined"); + link_args.emplace_back("dynamic_lookup"); + link_args.emplace_back("-Wl,-adhoc_codesign"); +#endif + link_args.emplace_back("--coverage"); + append_includes_and_libs(link_args, true); + link_args.emplace_back("-o"); + link_args.emplace_back(so_file); + link_args.emplace_back(obj_file); + run_gcc(link_args); + } + { + // Object file not needed after link; keep .c/.gcno for lcov. + std::error_code ec; + std::filesystem::remove(obj_file, ec); } +#else + { + std::vector args{compiler, "-x", "c", "-shared"}; +#if !defined(_WIN32) + args.emplace_back("-fPIC"); +#endif +#if defined(__APPLE__) + // macOS 的 .dylib 需要 -undefined dynamic_lookup 选项, + // 允许运行时由宿主进程提供未解析的符号。 + args.emplace_back("-undefined"); + args.emplace_back("dynamic_lookup"); + args.emplace_back("-Wl,-adhoc_codesign"); +#endif + args.emplace_back(opt_flag); + args.emplace_back(jit_type_def); + append_includes_and_libs(args, true); + args.emplace_back("-o"); + args.emplace_back(so_file); + args.emplace_back(c_file); + run_gcc(args); + } +#endif +#if defined(_WIN32) HMODULE module_handle = LoadLibraryA(so_file.c_str()); if (!module_handle) { CleanupOnError(); @@ -191,55 +320,6 @@ void GccJitter::Compile(const ParseResult &pr, const GenResult &gr, const Compil return p ? reinterpret_cast(p) : nullptr; }; #else - auto argv = build_argv(); - const int log_fd = open(log_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (log_fd < 0) { - CleanupOnError(); - ThrowFakeluaException(std::format("GCC compile failed, cannot open log file {}", log_file)); - } - - // Block SIGCHLD before fork to prevent the default handler from turning - // the child into a zombie before we get to waitpid. - sigset_t old_mask, block_mask; - sigemptyset(&block_mask); - sigaddset(&block_mask, SIGCHLD); - pthread_sigmask(SIG_BLOCK, &block_mask, &old_mask); - - const pid_t pid = fork(); - if (pid < 0) { - pthread_sigmask(SIG_SETMASK, &old_mask, nullptr); - close(log_fd); - CleanupOnError(); - ThrowFakeluaException(std::format("GCC compile failed, fork failed for {}", pr.file_name)); - } - - if (pid == 0) { - // Restore default signal mask in child before exec. - pthread_sigmask(SIG_SETMASK, &old_mask, nullptr); - dup2(log_fd, STDOUT_FILENO); - dup2(log_fd, STDERR_FILENO); - close(log_fd); - execvp(compiler.c_str(), argv.data()); - _exit(kExecFailedStatus); - } - close(log_fd); - - int status = 0; - if (waitpid(pid, &status, 0) < 0) { - pthread_sigmask(SIG_SETMASK, &old_mask, nullptr); - CleanupOnError(); - ThrowFakeluaException(std::format("GCC compile failed, waitpid failed for {}", pr.file_name)); - } - pthread_sigmask(SIG_SETMASK, &old_mask, nullptr); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - std::string gcc_log; - if (std::ifstream ifs(log_file); ifs.is_open()) { - gcc_log.assign(std::istreambuf_iterator(ifs), std::istreambuf_iterator()); - } - CleanupOnError(); - ThrowFakeluaException(std::format("GCC compile failed for {} (log: {})\n{}", pr.file_name, log_file, gcc_log)); - } - void *dl_handle = dlopen(so_file.c_str(), RTLD_NOW | RTLD_LOCAL); if (!dl_handle) { // 在高并发多进程测试场景下,macOS 内核 AMFI 代码签名检查偶现缓存同步微延迟,重试以确保平滑加载 diff --git a/test/test_common.cpp b/test/test_common.cpp index 8fa3bca3..9e9e3e1b 100644 --- a/test/test_common.cpp +++ b/test/test_common.cpp @@ -5,6 +5,10 @@ #include "gtest/gtest.h" #include +#include +#include +#include +#include namespace { struct UnbufferedStdio { @@ -335,3 +339,26 @@ TEST(common, ToFloat_hex_errors) { // Hex integer with invalid digits. EXPECT_THROW(ToFloat("0xGGG"), std::exception); } + +#include "compile/c_runtime_header.h" + +// Keep c_runtime_cov.h (Codecov view / #line target) identical to kCRuntimeHeader's +// raw-string body. The string begins with a leading '\n' that is not in the file. +TEST(common, c_runtime_cov_matches_kCRuntimeHeader) { + const char *home = std::getenv("FAKELUA_HOME"); + std::filesystem::path cov_path; + if (home && *home) { + cov_path = std::filesystem::path(home) / kCRuntimeCovPath; + } else { + // unit_tests usually run with cwd=test/lua + cov_path = std::filesystem::path("../../") / kCRuntimeCovPath; + } + std::ifstream ifs(cov_path); + ASSERT_TRUE(ifs.good()) << "missing view header: " << cov_path; + std::ostringstream oss; + oss << ifs.rdbuf(); + const std::string file_body = oss.str(); + const std::string expected = kCRuntimeHeader[0] == '\n' ? std::string(kCRuntimeHeader + 1) : std::string(kCRuntimeHeader); + EXPECT_EQ(file_body, expected) << "Edit kCRuntimeHeader and sync " << kCRuntimeCovPath + << " (or the reverse); Codecov #line mapping depends on it."; +}