Compiler
Native backends: LLVM and C++
The two ways to turn a program into a native executable, what each one covers, and the runtime limits of natively built programs.
since 0.1.0-alpha.1linux
Description
The compiler can produce a native executable in two ways. Both start from the same checked and
lowered program that the interpreters run (see lang.engines).
The LLVM backend
The LLVM backend is the primary native backend. It covers the whole language: classes, collections,
closures, exceptions, and the standard library's system services such as the event loop, timers,
async and await, files and threads. It is available when the compiler was built with LLVM; a
compiler built without it reports this build has no LLVM backend.
| Option | Result |
|---|---|
--emit-llvm |
Prints the program as LLVM IR text. |
--native-obj <out.o> |
Writes an object file directly, without calling any external tool. |
--build-native <out> |
Writes the object file, links it with the runtime library and the system C++ compiler, and leaves the executable <out>. |
--opt-level <0|2> |
2 (the default) optimises. 0 generates code faster and is easier to follow in a debugger. Any value other than 0 optimises. |
--build-native links against liblvrt.a, the runtime library, which is installed beside the
leviathan program; --runtime <path> names a different one. The linker driver is the first of
clang++, g++ and cc found on the PATH. A file named lvrt.link beside the runtime library
lists any extra linker flags the runtime needs, such as the TLS libraries, and --build-native
reads it for you. If you link an object file from --native-obj yourself, you have to supply
those flags yourself.
A program for either native backend
bool isPrime(int n) {
if (n < 2) {
return false;
}
int d = 2;
while (d * d <= n) {
if (n % d == 0) {
return false;
}
d = d + 1;
}
return true;
}
int found = 0;
int last = 0;
for (int n in 1..50) {
if (isPrime(n)) {
found = found + 1;
last = n;
}
}
console.writeln("${found} primes up to 50, the largest is ${last}");
15 primes up to 50, the largest is 47
Saved as primes.lev:
leviathan --build-native primes primes.lev
./primes
leviathan --opt-level 0 --build-native primes_debug primes.lev
leviathan --native-obj primes.o primes.lev
Memory limits of a natively built program
The escaping heap, which holds objects, arrays, maps, closures and strings that live beyond the
function that made them, starts as one 256 MiB region per thread and grows by mapping further regions
when it runs out, up to a ceiling of 16 GiB of address space per thread. A program that fits in the first
region never pays for the growth. The environment variable LANG_HEAP_MAX_MB changes the
ceiling, in whole mebibytes, when it is set for the running program:
- A positive value below 256 is raised to 256, so an override can never make the limit smaller than the first region.
- A value that is not a whole positive number, such as
0,-1orabc, means "use the default 16 GiB". - Reaching the ceiling, or being refused memory by the operating system, prints
lvrt: heap exhaustedand ends the program with exit status1. - A single object larger than 2 GiB, less a 16-byte header, can never be allocated however much
heap is left. The program prints
lvrt: object too largeand exits with status1. - The arena that holds short-lived values of a single function is a fixed 64 MiB per thread. Running
out of it prints
lvrt: arena exhaustedand exits with status1.
LANG_HEAP_MAX_MB=512 ./primes
The C++ backend
The C++ backend is the older native path, kept for environments without LLVM. --emit-cpp
prints a self-contained C++ translation unit, with its runtime included, on standard output.
--build <out> pipes that unit to the first of clang++, g++ and cc found on the PATH, at
optimisation level -O2, and leaves the executable <out>. It is always available.
It compiles the language itself, including classes, multiple inheritance, accessors and operators,
collections, closures and exceptions, and the standard library's pure code. It does not compile
programs that use the system services of the event loop. Files, timers, sockets and async/await
are rejected at compile time with a diagnostic such as
error: native backend: native 'sysOpen'
and no executable is produced. Reading standard input is supported. Use the LLVM backend, or run
the program with --run, for programs that need the rejected features.
Rules
--build-nativeand--native-objneed a compiler built with the LLVM backend.--buildand--emit-cppdo not.--buildneeds a C++ compiler on thePATH.--build-nativeneeds one as the linker driver.--targetand--opt-levelapply to--native-objand--build-native.- A program that the C++ backend cannot compile fails with a diagnostic naming the feature. It never compiles into a program that behaves differently.
LANG_HEAP_MAX_MBis read by the running program, not by the compiler.
Examples
Reading the generated code, for debugging or learning:
leviathan --emit-llvm primes.lev > primes.ll
leviathan --emit-cpp primes.lev > primes.cpp
Building with the C++ backend and checking that it agrees with the LLVM backend:
leviathan --build primes_cpp primes.lev
leviathan --build-native primes_llvm primes.lev
./primes_cpp > a.txt
./primes_llvm > b.txt
cmp a.txt b.txt
Notes
- The standard library ships as files that the compiler reads at start-up: the directory is found
as described in
lang.compiler-cli, with a copy built into the compiler as the last resort. The part of it that exposes the browser's DOM is only read when the target is WebAssembly.
See also
- The leviathan command line — Every option of the
leviathancompiler, grouped by what it does, with the exit statuses and how arguments reach your program. - The execution engines — The four ways the compiler runs a program, why they always agree, and the few places where one of them has to refuse a program.
- Cross-compilation: --target and per-target runtimes — Building executables for another operating system or processor, what the build needs for each target, and what a target can reject.
- The WebAssembly (browser) target — Building a program as a WebAssembly module for the browser, what it can use, what it cannot, and how it reaches the page.