logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·29.10.26
← Back to news

Trials and Tribulations of Native Lua Compilation

Original article
samyeyo.github.io|8 points|1 comments|by _samt_|Oct 18, 2026

The Trials and Tribulations of Native Lua Compilation

Blog — clx cl x
October 2026 · 12 min read

For a long time, I have been a devotee of Lua. Its primary appeal lies in its minimalism; the language is elegant, portable, and remarkably lightweight.

Perhaps its most defining characteristic is how effortlessly it can be embedded. This is a cornerstone of Lua's widespread adoption: whether you are building a video game, a professional application, or a specialized tool, you can drop a Lua interpreter inside and instantly provide a sophisticated scripting interface.

However, after years of development, I began to encounter the limitations inherent in this design.

The Deployment Dilemma

Whenever I sought to distribute a Lua-based application, the Lua Virtual Machine (VM) was always an inescapable part of the equation. While I had previously addressed much of this deployment friction on Windows using LuaRT and its rtc compiler, a fundamental issue remained.

The application was a standalone executable. While it appeared to be a native binary, it was essentially a wrapper. The executable bundled the interpreter, which then executed Lua code via the VM. It looked native on the surface, but underneath, it was still an interpreted program.

I wanted to push the boundary further. I envisioned a genuinely native Lua experience—one that functioned without relying on:

  • lua55.dll
  • liblua55.so
  • An embedded VM to drive the execution.

Clarification: I wasn't attempting to delete the runtime entirely. A compiled Lua program still requires native support for core semantics like garbage collection, strings, coroutines, and tables. My goal was to swap the general-purpose VM for a runtime specifically engineered to support compiled native Lua code.

As a secondary benefit, I hoped this approach would yield a noticeable boost in performance.

Packaging a VM vs. True Compilation

The core distinction between rtc and clx lies in their fundamental philosophy.

FeatureVM-Based Packaging (rtc)Native Compilation (clx)
MechanismBundles source/bytecode with an interpreter.Translates Lua →\rightarrow→ C++ →\rightarrow→ Machine Code.
ExecutionThe VM interprets the bundled code.The CPU executes native instructions.
DependenciesRequires the Lua VM runtime.Requires the clx specialized runtime.
OutputA "wrapped" executable.A true native binary.

How clx Works

The clx process is a multi-stage pipeline:

  1. It parses Lua 5.5 source code.
  2. It analyzes the logic and generates equivalent C++ code.
  3. This C++ code is passed to a standard native compiler (such as GCC, Clang, or MSVC).

The final executable consists of the native machine code derived from your Lua logic, linked with the clx runtime. The runtime handles the "heavy lifting" of Lua's dynamic nature:

  • Memory management & Garbage Collection
  • Table implementations
  • String handling
  • Coroutine support
  • Standard library facilities

The application logic itself, however, calls these runtime functions directly as native code.

While this sounds simple, the reality is grueling. Translating syntax is easy; preserving Lua semantics is the hard part. Mapping closures, metatables, environments, multiple return values, and upvalues into C++ while maintaining the expected behavior is far more complex than a simple syntax swap.

From Source to Binary

The clx workflow is designed to be intuitive. For a simple program:

hello.lua

print("Hello clx!")

You can compile it directly:

# Generate a native executable
clx hello.lua

Advanced Output Options

To aid in debugging or integration into existing build pipelines, clx offers several output modes:

  • C++ Source: Generate the intermediate C++ code.
    clx hello.lua --cpp
    
  • Object Files: Create a .o or .obj file.
    clx hello.lua --object
    
  • Static Libraries: Create a .a or .lib file.
    clx hello.lua --static
    

By supporting object and static files, clx allows compiled Lua to serve as a component within a much larger native project, rather than just being a standalone script.

Maintaining the Module System

I refused to sacrifice Lua's flexible module system. In a typical project:

project/

  • main.lua
  • greet.lua
  • utils.lua

You can compile them all together:

clx main.lua greet.lua utils.lua --output app

The standard require call remains unchanged:

local greet = require("greet")

The magic here is that require() no longer needs to search for and interpret a separate file at runtime; the module is already baked into the native binary.

Bridging to Native Modules

Using C++ as the target language provides a seamless bridge to native libraries. A native function that interacts with Lua looks like this:

fastmath.cpp

static clx::MultiValue add(clx::LState* L, const clx::LValue* args, size_t n) {
    double sum = 0;
    for (size_t i = 0; i < n; i++) {
        sum += clx::check_number(L, args[i]);
    }
    return {clx::number(sum)};
}

In mathematical terms, this function is simply calculating: ∑i=0n−1args[i]\sum_{i=0}^{n-1} \text{args}[i]∑i=0n−1​args[i]

To integrate this into your application, you link the module during compilation:

clx main.lua --modules fastmath

And then load it normally within your Lua code:

local fastmath = require("fastmath")

This architecture allows the developer to leverage the speed of C++ for performance-critical tasks while maintaining the agility of Lua for the rest of the application.