fakelua

FakeLua

codecov

中文 English

FakeLua is an embeddable Lua-subset compilation engine: it compiles Lua scripts into C code and dynamically compiles them into native machine code via the GCC backend for execution. It provides a C++23 interface with high-performance interop between scripts and native code.

Design Philosophy & Memory Model

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.

1. Scripting as High-Cohesion “Business Glue”

In a typical real-time high-performance server architecture:

2. Memory: Ultra-Fast Arena Pool + Frame Reset

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):

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.

Core Features

Dual JIT Backends

Supports two JIT modes with a seamless API switch:

int ret = 0;
// Same Call API, switching between JIT_GCC and JIT_TCC on demand
Call(s, JIT_GCC, "add", ret, 10, 20); // Production: GCC backend (-O3 high performance)
Call(s, JIT_TCC, "add", ret, 10, 20); // Development: TCC backend (ultra-fast compilation)

Numeric Specialization

The compiler automatically performs type inference and specialization for function math parameters:

  1. TypeInferencer runs iterative fixed-point inference on each top-level function (leave-one-out) to identify parameters that truly participate in arithmetic (math params).
  2. CGen generates $2^k$ specializations (int64_t / double combinations) plus a runtime entry dispatcher that routes to the appropriate specialization based on actual argument types.
  3. Specialized bodies use native C 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 / 中文).

Table Struct Specialization

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:

  1. Struct layout generation: The compiler dynamically generates a C struct layout at compile time, with each specialized key mapped to a fixed-offset member.
  2. Initialization & deduplication: Constructor initialization fills the JIT specialized struct in a single pass (following Lua’s left-to-right order) and checks for duplicate keys at compile time.
  3. Ultra-fast pointer-offset access: For specialized key reads/writes, pointer offset macros (FL_SPEC/FL_SET_SPEC) are used directly, completely avoiding hash lookups and key comparisons.
  4. Dynamic fallback: If the key used for read/write is a dynamic variable, it falls back to runtime dynamic dispatch via registered 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});

Language Features

Supported

Not Supported

Built-in Standard Libraries

FakeLua provides 29 independent C++ native modules under src/native/, covering math, string, table, IO, networking, timers, events, random, containers, compression, encryption, serialization, databases, protobuf, config formats, logging, and subprocesses.

Full API reference: src/native/README.md / 中文

Category Modules
Core Lua math, table, string, os, utf8, io, random
Networking net (TCP/UDP server/client), http (Beast 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 (LZ4/zlib/gzip/Zstd), crypto (MD5/SHA/AES/RC4/Blowfish/DES, UUID, CRC-32, xxHash)
Process process (process.run; does not replace os.execute)
Logging log (7 levels, tagged output, file rotation)
Object object (NativeObject Lua-side API)

Regex note: string.find/match/gmatch/gsub use ECMAScript regex (boost::regex::ECMAScript), not Lua patterns. See Regex Guide below for migration tips.

Regex Matching: ECMAScript Syntax, Not Lua Patterns

Purpose Lua Pattern FakeLua (ECMAScript Regex)
Digits %d \\d
Letters %a [A-Za-z]
Alphanumeric %w [A-Za-z0-9] (note \\w additionally includes _)
Whitespace %s \\s
Escape literal %., %% \\.%
Lazy repeat - (e.g. .-) ? (e.g. .*?)
Backreference in replacement %1, %0 $1, $&

Since \d is not a valid escape in Lua string literals, backslashes in regex patterns must be written as "\\d+". FakeLua does not support [[...]] long strings as a workaround.

For scripts that need to be compatible with both standard Lua and FakeLua, use syntax that has the same semantics in both engines, e.g. [0-9]+ instead of %d+.

Key differences:

Quick Start

Building

System Requirements

Linux / macOS

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

On macOS, first brew install lua cmake and 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

Windows (MSYS2 + MinGW)

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

Testing & Benchmarks

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.h and library files).

CLI Tool flua

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

Performance Benchmarks

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 / 中文.

C++ API Reference

Quick Usage

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

State Management

// 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

API Overview

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)

Type Conversion

// 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);

Table ↔ Object Mapping

class CustomVar : public VarInterface { /* ... */ };
SetVarInterfaceNewFunc(s, []() { return new CustomVar(); });
// Table-type arguments in Call automatically construct CustomVar instances

Architecture Overview

Compilation Pipeline

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] → C source (c_gen)
   ↓
[JIT compilation] → machine code (tcc_jit / gcc_jit)
   ↓
[Load & execute] → result

Key Components

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
compile_common Common type inference and codegen utilities
jit/* TCC and GCC backend integration
state FakeLua runtime state management
var Dynamic value CVar and conversion utilities

FAQ

Q: Why choose a Lua subset over full Lua?

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.

Q: How to choose between TCC and GCC backends?

A: GCC is the primary backend for production (with -O3 optimization); TCC compiles extremely fast, primarily for development and testing.

Q: Can it be used in embedded or constrained environments?

A: Yes — the TCC backend is small and fast. The core library has minimal dependencies (C++ standard library only) and is cross-compilable.

Q: How to debug generated C code?

A: Enable CompileConfig::debug_mode to inspect logs and C code; use GetLastRecordedCCode() to export C code for analysis.

Q: Is multithreading supported?

A: Each State is currently thread-local; in multithreaded environments, create an independent State per thread.