Chapter 40 / 51
Volume 5 · Extensions, Ops & Projects

Nested TCC & the plugin system: opening a compiler from your script

xsCreateTCC hands the script a fully preloaded compiler — dynamic rules, expression evaluation, third-party plugins all travel the road of "compiling native code at runtime".

Overview

Positioning: ch. 27's management route, "ascended" — once the management plane starts compiling code, it becomes a plugin platform. func_test carries a real nested-TCC-with-reload concurrency case (4 threads × 12 requests); this chapter takes it as the acceptance bar.

40.1 Intro

A class of needs can't be met with "hard-coded logic": risk rules changing weekly, report formulas user-defined, third parties wanting to extend your platform. The traditional answer embeds an interpreter (Lua/JS) — at the price of a second runtime and boundary overhead. XServer's answer is natural: the host compiles scripts with TCC; scripts can compile code with TCC too — same language, same symbols, zero boundary. xsCreateTCC preloads include paths and every host symbol, so nested code can call your APIs from birth.

40.2 The five plugin-compile steps

An expression plugin (a complete runnable pattern)
/* requirement: the user supplies a C expression; the server evaluates it */
typedef int (*ExprFn)(int);

static bool CompileExpr(const char* sExpr, ExprFn* ppFn)
{
    char sSource[1024];
    TCCState* pTcc;

    snprintf(sSource, sizeof(sSource),
        "int eval(int x) { return %s; }", sExpr);
    /* 1. create (include paths and symbols preloaded) */
    pTcc = xsCreateTCC();
    if ( pTcc == NULL ) return false;
    /* 2. compile the string */
    if ( tcc_compile_string(pTcc, sSource) < 0 ) {
        xsDestroyTCC(pTcc);
        return false;             /* syntax error — reply 400 */
    }
    /* 3. relocate into memory */
    if ( tcc_relocate(pTcc, TCC_RELOCATE_AUTO) < 0 ) {
        xsDestroyTCC(pTcc);
        return false;
    }
    /* 4. take the symbol (declare the function-pointer type first — the signature contract is yours) */
    *ppFn = (ExprFn)tcc_get_symbol(pTcc, "eval");
    /* 5. pTcc is held by the caller: never destroy while the symbol is in use */
    return *ppFn != NULL;
}
/* when done: xsDestroyTCC(pTcc) — ppFn dies immediately after */

What just happened: the key discipline of the five steps (create → compile → relocate → take symbol → destroy) is that a symbol's lifetime binds to the TCCState — the artifact lives in memory the TCCState owns; Destroy kills it. So a plugin cache's correct structure is "cache the TCCState", never "cache the function pointer".

40.3 Error callbacks & symbol injection

  • Take over error output: tcc_set_error_func routes compile diagnostics into your collector (default: stderr) — returning diagnostics to a user who submitted a broken expression is part of product quality
  • Symbol injection: tcc_add_symbol lets plugins call host functions you define — expose a small plugin_api_*-prefixed function set and you have a plugin SDK
  • Permissions wide open by default: xsCreateTCC injects the full host symbol set — narrow it yourself (add_symbol only a whitelist), the raw material for designing your plugin sandbox boundary (full discussion in ch. 44)
  • VFS available: plugin code includes VFS headers the same way; tcc_vfs_mount_memory can inject "virtual files" at runtime — exactly how the host loads scripts

40.4 Concurrency with hot reload

The safety question when nested compilers and reloads race: could a plugin's resolved host function be reclaimed at a swap? func_test's case gives the answer and the bar — 4 threads concurrently running nested compiles × 12 requests, with repeated reloads throughout, zero failures. Mechanically:

  • A plugin's TCCState is your generation's state — hang it in a registry, destroy it with the generation, and safety is automatic (DestroyTCC inside ServiceUnit)
  • Symbols an old-generation plugin resolved point at old-generation host functions — the old lease keeps them alive until the plugin retires together
  • The new generation recompiles plugins (or lazily on demand) — isomorphic with route-table rebuilding

40.5 Contract

  • Lifecycle: function pointers live inside the TCCState; the unit of caching is the TCCState; pointers die at Destroy.
  • Generations: plugin state is managed per generation (create in Init / destroy in Unit) — concurrency with reload is then safe.
  • Signatures: the post-lookup pointer type is your declaration — document the contract; getting it wrong is undefined behavior.

Common mistakes

Caching a function pointer, destroying the compiler
Symptom
Plugin cache hits jump into freed memory.
Cause
The artifact memory belongs to the TCCState. Both cache key and value should be "expression → TCCState", lifetime identical to the cache's.
Calling with the wrong function-pointer signature
Symptom
Arguments misaligned, stack corruption, random crashes.
Cause
tcc_get_symbol returns an address without checking signatures — the type contract is entirely yours. Fix one signature template in the plugin SDK (e.g. int fn(int)) and document it hard.

Exercises

BasicAn expression service

Implement POST /expr {code:"x*2+1", x:5} → 11.

Acceptance: correct evaluation; syntax errors return 400 + diagnostics.
AdvancedA compile cache

Add an "expression → TCCState" cache (LRU capped at 100) with hit stats.

Acceptance: repeated expressions skip compiling; at capacity the least recently used gets destroyed.
ChallengeA concurrency + reload storm

Replicate the func_test case: 4 threads evaluating concurrently × 12 requests, with 5 swaps in between.

Acceptance: zero failures; old-generation plugins' Unit visibly calls DestroyTCC.

Summary

PointConclusion
Entry pointxsCreateTCC: include paths + the full host symbol set preloaded
Five stepscreate → compile → relocate → take symbol → (when done) destroy
Cache unitthe TCCState (not the function pointer)
Concurrency safetyplugin state managed per generation; func_test has the concurrent acceptance case
Boundarypermissions wide open by default; narrowing via your own symbol whitelist (see ch. 44)
API references in this chapter
xrt further reading