fakelua

FakeLua

codecov

中文 English

FakeLua 是面向高性能宿主的可嵌入 Lua 子集运行时:脚本可编译为字节码由内置虚拟机执行,也可经 GCC/TCC JIT 生成原生机器码;并内置大量 C++ native 标准库(网络/HTTP/数据库/加密等),采用 Arena 内存池 + 帧重置,无 GC 停顿。

设计初衷与内存设计哲学

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

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

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

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

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

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

核心特性

三种执行后端

同一套 Call API 可切换任意已注册后端:

int ret = 0;
Call(s, JIT_GCC, "add", ret, 10, 20);    // 生产:GCC (-O3)
Call(s, JIT_TCC, "add", ret, 10, 20);    // 开发/测试:TCC(极速编译)
Call(s, JIT_INTERP, "add", ret, 10, 20); // 字节码虚拟机(无需宿主 C 编译器)

数值参数特化(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/ 下提供 30+ 个独立 C++ 原生模块(每个 State 创建时自动注册),覆盖数学、字符串、表、IO、网络、定时器、事件、随机数、容器、压缩、加密、序列化、数据库、Protobuf、配置解析、日志、子进程等领域。

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

分类 模块
核心 Lua basic、math、table、string、os、utf8、io、random
运行时 / IO runtime(runtime.tick())、net(TCP/UDP)、http(HTTP/1.1)、url、timer、event
数据 json、csv、serialize、protobuf、container(Boost.Container deque/vector/list/map/set)
配置解析 yaml、toml、xml、ini
数据库 mysql(异步 + 连接池)、redis(异步)、sqlite(同步)
加解密 / 压缩 compress(LZ4/zlib/gzip/Zstd)、crypto(OpenSSL 摘要/对称加密、UUID、CRC-32、xxHash)
进程 process(process.run;不替换 os.execute)
日志 log(级别、分类标签输出、文件滚动)
对象 object(NativeObject Lua 侧 API)

string.find/match/gmatch/gsub 使用 Lua 5.4 模式(自包含字节模式引擎,位于 src/native/string/lua_pattern.*),不是 ECMAScript/POSIX 正则。

Lua 模式匹配

string.find/match/gmatch/gsub 严格遵循 PUC-Rio Lua 5.4 语义,包括:

string.match("limit=15", "%d+")                          --> "15"
string.gsub("hello world", "(%w+) (%w+)", "%2 %1")      --> "world hello", 1
string.match("a(b(c)d)e", "%b()")                       --> "(b(c)d)"

Lua 模式不是正则:没有分支交替(a|b),转义前缀是 % 而非 \。为旧 ECMAScript 行为写的脚本(如 "\\d+"、$1 替换)需改写为 Lua 写法("%d+"、"%1")。

快速上手

构建

系统要求

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|2> --repeat=<N>

安装与 CMake 集成

安装到系统

# 编译
cmake -S . -B build
cmake --build build --parallel

# 安装(默认前缀:/usr/local)
sudo cmake --install build
# 或:cd build && sudo make install

在其他 CMake 项目中使用

安装到系统后,其他 CMake 项目可以通过标准的 find_package 引入并链接 FakeLua:

cmake_minimum_required(VERSION 3.20)
project(my_project CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找 FakeLua
find_package(fakelua REQUIRED)

add_executable(my_project main.cpp)

# 链接 fakelua(自动引入头文件路径和链接参数)
target_link_libraries(my_project PRIVATE fakelua::fakelua)
# 同时兼容不带命名空间的别名写法:
# target_link_libraries(my_project PRIVATE fakelua)

在 C++ 代码中引用头文件:

#include "fakelua.h"
// 或者使用带目录前缀的形式:
// #include <fakelua/fakelua.h>

FakeLua 还会同时安装 fakelua.pc,支持通过 pkg-config 使用。

性能基准

对比 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 代码生成]                      [字节码生成]                    (共享 AST)
   (c_gen)                         (interp/codegen)
        ↓                             ↓
[JIT TCC / GCC]                   [解释器虚拟机]
   原生机器码                        (interp/interpreter)
        └───────────── Call(s, JIT_*, …) ─────────────┘

关键组件

模块 职责
lexer/parser Lua 词法和语法解析
syntax_tree AST 表示和遍历
preprocessor Lua 语法规范化(如 functiondef 提升)
semantic_analysis 语义和控制流分析
type_inferencer 静态类型推导和 specialization 决策
c_gen C 代码生成和类型驱动优化
interp/* 字节码生成、指令集与解释器虚拟机
compile_common 公共类型推导和代码生成工具
jit/* TCC/GCC 后端与 Vm 函数注册表
native/* 内置标准库(net、http、db、crypto 等)
state FakeLua 运行时状态管理
var 动态值 CVar 和转换工具

常见问题

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

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

Q: TCC、GCC 与 INTERP 如何选择?

A: GCC 是生产环境主力(-O3)。TCC 编译极快,适合开发与 CI。INTERP 跑字节码、无需宿主 C 编译器,适合受限环境、工具链与语义校验;热路径仍可与 JIT 闭包混合调用。

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

A: 可以——用 JIT_INTERP(运行时不依赖 GCC/TCC)或体积很小的 TCC 后端。OpenSSL/MySQL 等 native 依赖可按构建需求裁剪。

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

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

Q: 支持多线程吗?

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