| 中文 | English |
FakeLua is an embeddable Lua-subset runtime for high-performance hosts: it compiles scripts to a bytecode VM and optionally to native code via GCC/TCC JIT, ships a large C++ native standard library (net/http/db/crypto/…), and uses an arena allocator with frame reset so there is no GC pause.
FakeLua was designed to address the throughput jitter and memory bloat caused by garbage collection in traditional scripting languages (standard Lua/LuaJIT) when used in high-performance game servers or similar real-time systems.
In a typical real-time high-performance server architecture:
To support this positioning, FakeLua does not implement a complex dynamic garbage collector (tri-color marking, generational GC, etc.). Instead, it uses an extremely efficient Arena memory pool (Bump Allocator):
malloc fragmentation or overhead.State::Reset() is called. It invokes destructors in reverse order, but instead of freeing individual blocks, the pool offset pointer is simply reset to zero. There is no complex object graph traversal, no system-level free cost or defragmentation — cleanup is instantaneous.This design allows FakeLua to fully eliminate GC pause impact on frame rates while maintaining JIT native execution speed, keeping memory overhead at a completely predictable, extremely low level.
The same Call API can target any registered backend:
-O3) to generate high-quality native code. Primary backend for production.src/interp/). No external C compiler required; useful for portability, tooling, and mixed JIT↔interp closures.int ret = 0;
Call(s, JIT_GCC, "add", ret, 10, 20); // Production: GCC (-O3)
Call(s, JIT_TCC, "add", ret, 10, 20); // Dev/test: TCC (fast compile)
Call(s, JIT_INTERP, "add", ret, 10, 20); // Bytecode VM (no host C compiler)
The compiler automatically performs type inference and specialization for function math parameters:
int64_t / double combinations) plus a runtime entry dispatcher that routes to the appropriate specialization based on actual argument types.int64_t/double) for arithmetic and generate native C bool for comparisons, completely eliminating boxing overhead on hot paths.-- Example Lua function: recursive Fibonacci
function fib(n)
if n <= 1 then return n end
return fib(n - 1) + fib(n - 2)
end
Auto-generated specialized C code:
// 1. Numeric specialization: params/return promoted to native int64_t, no boxing
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. Generic entry dispatcher: fast type check, zero-overhead routing
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)};
}
// ... dynamic dispatch to double specialization or generic CVar path
}
With recursive Fibonacci (n=32) as an example, the GCC backend is 36.6x faster than Lua 5.4, and the TCC backend is 11.2x faster (see benchmark/README.md / 中文).
If a Table constructor can statically infer all its keys at compile time (string literals, explicit/implicit integer indices, booleans, floats), the compiler specializes it as a C struct:
FL_SPEC/FL_SET_SPEC) are used directly, completely avoiding hash lookups and key comparisons.spec_get / spec_set function pointers.-- Example Lua code: defining and accessing Table fields
local point = { x = 10, y = 20 }
point.x = point.x + 5
Auto-generated specialized C struct and pointer-offset access:
// 1. Compile-time key layout inference, auto-generate C struct definition
typedef struct Table_Spec_1 {
CVar x;
CVar y;
} Table_Spec_1;
// 2. On Table initialization, bind specialized struct layout and spec accessors
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. Field access converted to ultra-fast pointer member offsets (no hash table lookup)
FL_SPEC(Table_Spec_1, point, x) = NativeAdd(FL_SPEC(Table_Spec_1, point, x), (CVar){.type_ = VAR_INT, .data_.i = 5});
CVar *), shared across closures in the same scope.return a, b; C++ side receives via std::tie(a, b, c). Vararg functions with ... fully supported.function(args) body end as values, arbitrary callee calls like tbl[key]() or (fn)().obj:method(args) sugar with implicit self parameter.for in iterators: Stateless iterators, closure generators, and pairs/ipairs with native C struct-optimized loops.package "Name" for namespace isolation, zero-require cross-module calls.__fakelua_init().RegisterMethod, colon-syntax calls from Lua.string.find/match/gmatch/gsub use a self-contained Lua-pattern engine (%d/%w classes, custom sets, lazy -, captures, frontier %f[set], balanced %bxy), fully compatible with standard Lua.string.trim/trim_left/trim_right/split/join/replace/starts_with/ends_with/contains/iequals/icontains/istarts_with/iends_with via Boost.Algorithm.coroutine.create/resume/yield support.__index, __newindex, metamethods, or operator overloading.require/module: No standard module system (replaced by package "Name" mechanism).rawequal/rawget/rawset/rawlen: Meaningless without metatables.debug.* standard library."10" + 1 errors).FakeLua provides 30+ independent C++ native modules under src/native/ (registered automatically on each State), covering math, string, table, IO, networking, timers, events, random, containers, compression, cryptography, serialization, databases, protobuf, config formats, logging, and subprocesses.
Full API reference: src/native/README.md / 中文
| Category | Modules |
|---|---|
| Core Lua | basic, math, table, string, os, utf8, io, random |
| Runtime / I/O | runtime (runtime.tick()), net (TCP/UDP), http (HTTP/1.1), url, timer, event |
| Data | json, csv, serialize, protobuf, container (Boost.Container deque/vector/list/map/set) |
| Config | yaml, toml, xml, ini |
| Database | mysql (async + pool), redis (async), sqlite (synchronous) |
| Crypto / compress | compress (LZ4/zlib/gzip/Zstd), crypto (OpenSSL digests/ciphers, UUID, CRC-32, xxHash) |
| Process | process (process.run; does not replace os.execute) |
| Logging | log (levels, tagged output, file rotation) |
| Object | object (NativeObject Lua-side API) |
Pattern note: string.find/match/gmatch/gsub use Lua 5.4 patterns (a self-contained byte-pattern engine in src/native/string/lua_pattern.*), not ECMAScript/POSIX regex. See Lua Pattern Matching below.
string.find/match/gmatch/gsub follow PUC-Rio Lua 5.4 semantics exactly, including:
%. %( %) %% %+ … for punctuation; . matches any byte.%a %c %d %g %l %p %s %u %w %x and their uppercase complements; %z matches the zero byte.[set], [^set], [a-z], classes inside sets ([%d_]), leading ]/- as literals.* + - ? — including Lua’s lazy - (a.-b).^ at pattern start, $ at pattern end.(...), position captures (), back-references %1… in patterns and %0…%9 + %% in gsub replacement strings.%f[set] and balanced match %bxy.gsub: string/function/table replacements; a function/table result of nil/false keeps the original match; plain=true on find bypasses the pattern engine.pcall) instead of silently returning no match.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 patterns are not regular expressions: there is no alternation (
a|b), and the escape prefix is%, not\. Scripts written for the old ECMAScript behavior (e.g."\\d+",$1replacements) must be updated to Lua form ("%d+","%1").
cmake -S . -B build
cmake --build build --parallel
On macOS, first
brew install lua cmakeand add-DCMAKE_PREFIX_PATH="$(brew --prefix)"to the cmake command.
Build only core library and CLI tools (no tests/benchmarks):
cmake --build build --target fakelua flua --parallel
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
Unit tests and benchmarks require the Lua development package (header
lua.hand library files).
- Linux:
sudo apt-get install liblua5.4-devorliblua5.3-dev- macOS:
brew install lua- Windows MSYS2:
pacman -S mingw-w64-x86_64-lua
flua./build/bin/flua <script.lua> --entry=<func> --jit_type=<0|1|2> --repeat=<N>
--entry: Entry function name (default main)--jit_type: 0=TCC, 1=GCC, 2=INTERP (bytecode VM)--repeat: Repeat call count (for performance measurement)--debug: Enable debug mode (default false; when true, outputs generated C source / richer diagnostics)# Build
cmake -S . -B build
cmake --build build --parallel
# Install (default prefix: /usr/local)
sudo cmake --install build
# or: cd build && sudo make install
After installing to your system, other CMake projects can discover and link FakeLua using standard find_package:
cmake_minimum_required(VERSION 3.20)
project(my_project CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Discover FakeLua package
find_package(fakelua REQUIRED)
add_executable(my_project main.cpp)
# Link against fakelua (automatically sets up include directories and link flags)
target_link_libraries(my_project PRIVATE fakelua::fakelua)
# Alternatively, the unqualified alias is also supported:
# target_link_libraries(my_project PRIVATE fakelua)
In your C++ code:
#include "fakelua.h"
// or
#include <fakelua/fakelua.h>
FakeLua also installs fakelua.pc for pkg-config consumers.
Comparing Lua 5.4, FakeLua TCC, FakeLua GCC across 11 algorithms (Release -O3 mode):
| Algorithm (typical params) | 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 is generally faster than Lua for pure computation; in Table-operation-heavy scenarios, Table struct specialization gives both GCC and TCC a significant boost. Full data available in benchmark/README.md / 中文.
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); // embed-call a Lua function
// Manual management (not recommended — easy to leak)
State* s = FakeluaNewState(StateConfig{});
// ... use s ...
FakeluaDeleteState(s);
// Or RAII style (recommended)
FakeluaStateGuard guard(StateConfig{});
State* s = guard.GetState();
// ... use s ...
// automatically freed
| Function | Description |
|---|---|
FakeluaNewState() |
Create FakeLua state |
FakeluaDeleteState() |
Free FakeLua state |
CompileFile() |
Compile a Lua file |
CompileString() |
Compile a Lua code string |
Call() |
Invoke a compiled function |
GetLastRecordedCCode() |
Get the most recently compiled C code |
SetVarInterfaceNewFunc() |
Set custom VarInterface factory |
SetDebugLogLevel(s, level) |
Set this State’s debug log level (0=Trace … 6=Off; Lua: log.set_level) |
// Native → FakeLua
CVar v_int = inter::NativeToFakelua(s, 42);
CVar v_str = inter::NativeToFakelua(s, std::string("hello"));
// FakeLua → Native
int native_int = inter::FakeluaToNative<int>(v_int);
std::string native_str = inter::FakeluaToNative<std::string>(v_str);
class CustomVar : public VarInterface { /* ... */ };
SetVarInterfaceNewFunc(s, []() { return new CustomVar(); });
// Table-type arguments in Call automatically construct CustomVar instances
Lua source
↓
[Lexing] → tokens (flexer)
↓
[Parsing] → AST (bison + syntax_tree)
↓
[File-level stmt check] → reject non-declaration statements (semantic_analysis)
↓
[Preprocessing] → normalized AST (preprocessor)
↓
[Semantic analysis] → analysis result (semantic_analysis)
↓
[Type inference] → type hints (type_inferencer)
↓
┌─────────────────────────────┬──────────────────────────────┐
↓ ↓ ↓
[C code generation] [Bytecode codegen] (shared AST)
(c_gen) (interp/codegen)
↓ ↓
[JIT TCC / GCC] [Interpreter VM]
native code (interp/interpreter)
└───────────── Call(s, JIT_*, …) ─────────────┘
| Module | Responsibility |
|---|---|
lexer/parser |
Lua lexing and parsing |
syntax_tree |
AST representation and traversal |
preprocessor |
Lua syntax normalization (e.g., functiondef hoisting) |
semantic_analysis |
Semantic and control flow analysis |
type_inferencer |
Static type inference and specialization decisions |
c_gen |
C code generation and type-driven optimization |
interp/* |
Bytecode codegen, opcodes, and interpreter VM |
compile_common |
Common type inference and codegen utilities |
jit/* |
TCC/GCC backends plus Vm function registry |
native/* |
Built-in standard libraries (net, http, db, crypto, …) |
state |
FakeLua runtime state management |
var |
Dynamic value CVar and conversion utilities |
A: Certain dynamic features of full Lua (e.g., metatables) are difficult to compile efficiently. The subset focuses on statically analyzable common patterns, achieving near-C performance through type inference and JIT compilation.
A: GCC is the primary production backend (-O3). TCC compiles extremely fast for development and CI. INTERP runs bytecode without a host C compiler — good for constrained environments, tooling, and validating script semantics; hot paths can still call JIT closures when mixed.
A: Yes — use JIT_INTERP (no GCC/TCC required at runtime) or the small TCC backend. Native modules that need OpenSSL/MySQL/etc. are optional at the dependency level for your build.
A: Enable CompileConfig::debug_mode to inspect logs and C code; use GetLastRecordedCCode() to export C code for analysis.
A: Each State is currently thread-local; in multithreaded environments, create an independent State per thread.