fakelua

FakeLua

codecov

中文 English

FakeLua 是一个可嵌入的 Lua 子集编译引擎:将 Lua 脚本编译为 C 代码,通过 GCC 后端动态编译为原生机器码执行。提供 C++23 接口,支持脚本与原生代码高效互操作。

设计初衷与内存设计哲学

FakeLua 的设计初衷是为了在高性能游戏服务器或类似的实时系统中,解决传统脚本语言(如标准 Lua/LuaJIT)由于垃圾回收(GC)机制带来的吞吐量抖动和内存膨胀问题

1. 脚本定位:高内聚的”业务粘合剂”

在典型的实时高性能服务器架构中:

2. 内存设计:极速的 Arena 内存池 + 帧重置

为了配合上述定位,FakeLua 没有引入复杂的动态垃圾回收器(如三色标记、分代 GC),而是采用了极其高效的 Arena 内存池(Bump Allocator)

这种设计使得 FakeLua 在保持 JIT 原生执行速度的同时,能够彻底消除垃圾回收停顿(GC Pause)对帧率的影响,让内存开销保持在一条完全可预测的、极低的水平线上。

核心特性

双 JIT 后端

支持两种 JIT 模式,同一套 API 无缝切换:

int ret = 0;
// 同一套 Call API,可按需指定 JIT_GCC 或 JIT_TCC 后端
Call(s, JIT_GCC, "add", ret, 10, 20); // 生产环境推荐:GCC 后端 (-O3 高性能)
Call(s, JIT_TCC, "add", ret, 10, 20); // 开发调试推荐:TCC 后端 (极速编译)

数值参数特化(Numeric Specialization)

编译器对函数的数学参数自动做类型推断与特化:

  1. TypeInferencer 对每个顶层函数运行迭代不动点推断(leave-one-out),识别出真正参与算术运算的参数(math params)。
  2. CGen 为每个含数学参数的函数生成 $2^k$ 个特化版本(int64_t / double 组合),以及一个运行时入口分发器,根据实际参数类型路由到对应特化体。
  3. 特化体内的算术运算直接用原生 C 类型(int64_t/double)计算,比较表达式也生成原生 C bool 而非走 CVar 装箱路径,彻底消除热路径上的类型判断开销。
-- 示例 Lua 函数:递归计算 Fibonacci
function fib(n)
    if n <= 1 then return n end
    return fib(n - 1) + fib(n - 2)
end

编译器自动生成的特化 C 代码:

// 1. 数值参数特化体:形参/返回值直接提升为原生 int64_t,无需 CVar 装箱与运行时类型判断
static int64_t fib_spec_0(int64_t n) {
    if (n <= 1) {
        return n;
    }
    return fib_spec_0(n - 1) + fib_spec_0(n - 2);
}

// 2. 通用入口分发器:快速判断传入类型,零开销路由到原生 C 特化函数
static CVar fib_dispatcher(CVar n_var) {
    if (LIKELY(n_var.type_ == VAR_INT)) {
        return (CVar){.type_ = VAR_INT, .data_.i = fib_spec_0(n_var.data_.i)};
    }
    // ... 动态路由至 double 特化体或通用 CVar 分支
}

以递归 Fibonacci(n=32)为例,GCC 后端比 Lua 5.4 快 36.6x,TCC 后端快 11.2x(详见 benchmark/README.zh.md / English)。

Table 结构体特化(Table Specialization)

如果 Table 构造函数在编译期可以静态推断出其所有 Key(如字符串字面量、显式/隐式整型索引、布尔型、浮点型),编译器会将其特化为 C 语言结构体:

  1. 结构体布局生成:编译器在编译期动态为该 Table 生成对应的 C 结构体布局,各个特化 Key 映射为结构体中固定偏移的成员变量。
  2. 初始化与去重:构造函数初始化时,会采用单次遍历在 JIT 特化结构体内进行填充(遵循 Lua 左到右的语法顺序),并进行静态 Key 重复性的检查。
  3. 极速指针偏移读写:对于特化的 Key 读取和写入操作,直接使用指针偏移宏(FL_SPEC/FL_SET_SPEC)进行极速读写,彻底避免了哈希查找与键值对比。
  4. 动态降级:如果读写时使用的 Key 是动态变量,则自动回退到常规运行时动态分发逻辑,通过注册在 Table 上的 spec_get / spec_set 专有函数指针进行回调查找。
-- 示例 Lua 代码:定义与读写 Table 字段
local point = { x = 10, y = 20 }
point.x = point.x + 5

编译器自动生成的特化 C 结构体与指针偏移访问代码:

// 1. 编译期推断 Key 布局,自动生成 C 结构体定义
typedef struct Table_Spec_1 {
    CVar x;
    CVar y;
} Table_Spec_1;

// 2. 初始化 Table 时绑定专有 struct 布局与 spec 读写句柄
SET_TABLE_SPEC(point, Table_Spec_1, spec_get_fn, spec_set_fn, 2);
FL_SET_SPEC(Table_Spec_1, point, x, 0, (CVar){.type_ = VAR_INT, .data_.i = 10});
FL_SET_SPEC(Table_Spec_1, point, y, 1, (CVar){.type_ = VAR_INT, .data_.i = 20});

// 3. 字段读写转换为极速指针成员偏移(彻底免除哈希表查找开销)
FL_SPEC(Table_Spec_1, point, x) = NativeAdd(FL_SPEC(Table_Spec_1, point, x), (CVar){.type_ = VAR_INT, .data_.i = 5});

语言特性

已支持

未支持

标准内置扩展库

FakeLua 在 src/native/ 下提供 29 个独立 C++ 原生模块,覆盖数学、字符串、表、IO、网络、定时器、事件、随机数、容器、压缩、加密、序列化、数据库、Protobuf、配置解析、日志、子进程等领域。

完整 API 文档: src/native/README.zh.md / English

分类 模块
核心 Lua mathtablestringosutf8iorandom
网络 net(TCP/UDP 服务端/客户端)、http(Beast HTTP/1.1)、urltimerevent
数据 jsoncsvserializeprotobufcontainer(Boost.Container deque/vector/list/map/set)
配置解析 yamltomlxmlini
数据库 mysql(异步 + 连接池)、redis(异步)、sqlite(同步)
加解密 compress(LZ4/zlib/gzip/Zstd)、crypto(MD5/SHA/AES/RC4/Blowfish/DES、UUID、CRC-32、xxHash)
进程 processprocess.run;不替换 os.execute
日志 log(7 级别,分类标签输出,文件滚动)
对象 object(NativeObject Lua 侧 API)

⚠️ string.find/match/gmatch/gsub 底层使用 ECMAScript 正则boost::regex::ECMAScript),而非 Lua pattern。从标准 Lua 迁移时需改写模式串。

正则匹配:ECMAScript 语法

用途 Lua pattern FakeLua(ECMAScript 正则)
数字 %d \\d
字母 %a [A-Za-z]
字母或数字 %w [A-Za-z0-9](注意 \\w 额外包含 _
空白 %s \\s
惰性重复 -(如 .- ?(如 .*?
替换串捕获引用 %1%0 $1$&

Lua 字符串中 \d 不是合法转义,正则里的反斜杠需写成 "\\d+"。FakeLua 不支持 [[...]] 长字符串。

兼容写法:用 [0-9]+ 代替 %d+[A-Za-z]+ 代替 %a+,两种引擎语义一致。

主要差异:

快速上手

构建

系统要求

Linux / macOS

cmake -S . -B build
cmake --build build --parallel

macOS 需先 brew install lua cmake,并在 cmake 时加 -DCMAKE_PREFIX_PATH="$(brew --prefix)"

仅构建核心库与命令行工具(不含测试/基准):

cmake --build build --target fakelua flua --parallel

Windows(MSYS2 + MinGW)

cmake -S . -B build -G Ninja
cmake --build build --parallel
ctest --test-dir build -V

测试与基准

cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build --parallel
ctest --test-dir build -V
./build/bin/bench_mark

单元测试与 benchmark 依赖 Lua 开发包(头文件 lua.h 和库文件)。

命令行工具 flua

./build/bin/flua <script.lua> --entry=<func> --jit_type=<0|1> --repeat=<N>

性能基准

对比 Lua 5.4、FakeLua TCC、FakeLua GCC,覆盖 Fibonacci、GCD、快速幂、线性求和、冒泡排序、筛质数等 11 类算法(Release -O3 模式):

算法(典型参数) Lua 5.4 FakeLua TCC FakeLua GCC
Fibonacci n=32 297.9 ms 26.7 ms(11.2x↑) 6.8 ms(36.6x↑)
Sum n=5000000 33.9 ms 18.4 ms(1.8x↑) 1.1 ms(30.4x↑)
Popcount n=100000 18.2 ms 3.1 ms(5.9x↑) 488.0 μs(37.3x↑)
BubbleSort n=200 1.5 ms 3.3 ms(0.45x) 738.8 μs(1.9x↑)
Sieve n=5000 353.4 μs 1.0 ms(0.34x) 219.3 μs(1.8x↑)
FloatPoly n=1000000 34.9x↑(浮点特化,GCC 2x 快于 C++)

TCC 纯计算类场景普遍快于 Lua;在包含 Table 操作的场景下,Table 结构体特化使 GCC 与 TCC 的 Table 读写性能均大幅提升。完整数据见 benchmark/README.zh.md / English

C++ API 详细文档

快速使用

FakeluaStateGuard guard;
State* s = guard.GetState();
CompileFile(s, "script.lua", CompileConfig{.debug_mode = false});

int sum = 0;
Call(s, JIT_GCC, "add", sum, 10, 20); // 嵌入调用 Lua 函数

状态管理

// 手动管理(不推荐,容易泄漏)
State* s = FakeluaNewState(StateConfig{});
// ... 使用 s ...
FakeluaDeleteState(s);

// 或使用 RAII 风格(推荐)
FakeluaStateGuard guard(StateConfig{});
State* s = guard.GetState();
// ... 使用 s ...
// 自动释放

API 概览

函数 功能
FakeluaNewState() 创建 FakeLua 状态
FakeluaDeleteState() 释放 FakeLua 状态
CompileFile() 编译 Lua 文件
CompileString() 编译 Lua 代码字符串
Call() 调用编译后的函数
GetLastRecordedCCode() 获取最近编译的 C 代码
SetVarInterfaceNewFunc() 设置自定义 VarInterface 工厂
SetDebugLogLevel(s, level) 设置本 State 的调试日志级别(0=Trace … 6=Off;Lua 侧用 log.set_level

类型转换

// 原生 → FakeLua
CVar v_int = inter::NativeToFakelua(s, 42);
CVar v_str = inter::NativeToFakelua(s, std::string("hello"));

// FakeLua → 原生
int native_int = inter::FakeluaToNative<int>(v_int);
std::string native_str = inter::FakeluaToNative<std::string>(v_str);

Table 与对象互转

class CustomVar : public VarInterface { /* ... */ };
SetVarInterfaceNewFunc(s, []() { return new CustomVar(); });
// Call 中传递的 table 类型参数会自动构造为 CustomVar 实例

架构概览

编译流程

Lua 源码
   ↓
[词法分析] → tokens (flexer)
   ↓
[语法分析] → AST (bison + syntax_tree)
   ↓
[文件级语句校验] → 拒绝非声明语句 (semantic_analysis)
   ↓
[预处理] → normalized AST (preprocessor)
   ↓
[语义分析] → analysis result (semantic_analysis)
   ↓
[类型推导] → type hints (type_inferencer)
   ↓
[C 代码生成] → C 源码 (c_gen)
   ↓
[JIT 编译] → 机器码 (tcc_jit / gcc_jit)
   ↓
[加载执行] → 结果

关键组件

模块 职责
lexer/parser Lua 词法和语法解析
syntax_tree AST 表示和遍历
preprocessor Lua 语法规范化(如 functiondef 提升)
semantic_analysis 语义和控制流分析
type_inferencer 静态类型推导和 specialization 决策
c_gen C 代码生成和类型驱动优化
compile_common 公共类型推导和代码生成工具
jit/* TCC 和 GCC 后端集成
state FakeLua 运行时状态管理
var 动态值 CVar 和转换工具

常见问题

Q: 为什么选择 Lua 子集而不是完整 Lua?

A: 完整 Lua 的某些动态特性(如 metatable)很难高效编译。子集实现聚焦于可静态分析的常见模式,通过类型推导和 JIT 编译获得接近 C 的性能。

Q: TCC 和 GCC 后端如何选择?

A: GCC 是生产环境主力后端(-O3 生成高质量原生代码);TCC 编译极快,主要用于开发调试和测试。

Q: 可以在嵌入式环境中使用吗?

A: 可以,TCC 后端体积小、编译速度快。核心库依赖极少(仅 C++ 标准库),可交叉编译。

Q: 如何调试生成的 C 代码?

A: 启用 CompileConfig::debug_mode 查看日志和 C 代码;使用 GetLastRecordedCCode() 导出 C 代码进行分析。

Q: 支持多线程吗?

A: 每个 State 当前为线程本地对象,多线程环境中应为每个线程创建独立的 State