Native compilation
How VexaScript compiles to native C++
VexaScript added its first C++ backend in commit a380895e on July 16,
2026. The goal was to generate native programs without creating a second
parser or type checker.
Shared compiler stages
JavaScript and C++ use the same parser, syntax tree, type analysis, diagnostics, and common compiler transformations. They differ only when the compiler starts generating target code.
| Compiler stage | Shared work | C++-specific work |
|---|---|---|
| Parsing | Build the syntax tree | None |
| Type analysis | Resolve names, types, operators, and errors | Choose a native storage type from the result |
| Modules | Find source files and imports | Collect native headers and linker flags |
| Code generation | Select a backend | Generate C++20 |
| Build | None | Run CMake, the C++ compiler, and the linker |
This split also keeps the compiler available in the browser. Shared compiler files cannot use Node.js filesystem or process APIs because Monaco uses the same code. Native build commands live in the CLI instead.
Native value types
The C++ backend cannot map every VexaScript value to a plain C++ value. JavaScript-style object identity, garbage collection, dynamic values, and Promises need runtime support.
| VexaScript value | C++ representation |
|---|---|
int |
std::int32_t |
long |
std::int64_t |
number |
double |
bigint |
Runtime BigInt |
| Class instance | Oilpan-managed class pointer |
| Field that points to an object | cppgc::Member<T> |
| Array | Managed ArrayObject<T>* |
| Unknown value | Tagged runtime Value |
Promise<T> |
Native task and event-loop state |
The first array implementation used std::vector<T>. It was removed because
assignment copied the array, unlike JavaScript. It also could not safely track
cycles between arrays and managed objects. ArrayObject<T> keeps one shared
array object and traces object elements for the garbage collector.
Why the backend uses Oilpan
VexaScript programs can create cycles. For example, an object can contain an
array that points back to the same object. Reference counting with
std::shared_ptr cannot collect that cycle unless the compiler also decides
which references must be weak.
Oilpan traces the object graph instead. Generated classes report their managed fields, and the collector removes objects that can no longer be reached.
This requires clear rules. Managed fields must use traced references. Values that survive an asynchronous pause must stay rooted. Raw data such as bytes does not need tracing. Focused tests cover each of these cases.
CLI commands and generated files
vexa cpp program.vx -o program.cpp
vexa cpp link program.vx -o program
vexa cpp run program.vx
cpp generates a C++ file. cpp link also compiles and links it. cpp run
builds the program and starts it. Generated build files and cached dependencies
stay outside the source tree.
The first backend change touched 20 files and added 1,150 lines. It used C++20 and standalone Oilpan. Native support was still a subset of the language, but it proved that VexaScript could add another backend while keeping parsing and type analysis shared.