LEVIATHAN v962456e · 962456eee1

Compiler

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.

since 0.1.0-alpha.1linuxwindowswasm

Description

The LLVM backend can generate code for a machine other than the one the compiler runs on. You name the target with --target <triple>, where the triple has the usual LLVM form such as x86_64-pc-windows-gnu or aarch64-linux-gnu. The compiler runs on Linux; its targets are Linux, Windows and WebAssembly (see lang.wasm-target).

leviathan --build-native <out> --target <triple> [--runtime <archive>] <source-file>

An executable is the program plus the runtime library, a small C library that provides memory management, the event loop and the system services. Generating the program's object file for another machine needs nothing but the compiler. Linking needs three more things for that machine, and the compiler reports which one is missing:

  1. A runtime library built for the target. The compiler looks for <triple>/liblvrt.a beside the leviathan program and then for runtime/<triple>/liblvrt.a in a source checkout. In a source checkout, runtime/build-triple.sh <triple> builds it with a cross C compiler for the target: clang (set LVRT_SYSROOT to a sysroot for the target) or the distribution's cross GCC, which for Windows is the MinGW-w64 package whose compiler is called x86_64-w64-mingw32-gcc. --runtime <path> names an archive explicitly. A cross runtime is built without TLS by default, so the program is plain-text only.
  2. A cross linker. The compiler tries clang++ -target <triple> first and then the distribution's <prefix>-g++ and <prefix>-gcc, where the prefix is the triple (and x86_64-w64-mingw32 for the Windows triple).
  3. For WebAssembly, wasm-ld. See lang.wasm-target.

The error messages say exactly what was looked for, for example cannot locate the x86_64-pc-windows-gnu runtime archive (looked next to 'leviathan' and in runtime/x86_64-pc-windows-gnu/).

On a Windows target the compiler adds .exe to the output name if it is not already there, and links the Windows socket library. The result is an ordinary Windows console program.

--native-obj <out.o> --target <triple> writes the object file for the target and stops, which is useful to check that a program compiles for a target before its runtime is available. A triple that LLVM does not know is an error: No available targets are compatible with triple.

The target seen by the program

A program can ask what it is being built for with three compile-time constants, target::os ("linux", "windows", "wasm" and so on), target::arch and target::triple. They describe the target you named with --target, not the machine the compiler runs on, and they only exist at compile time, so they are used in comptime declarations and comptime if. The branch that is not taken is not compiled at all, so each target only compiles the code written for it.

Code that depends on the target

comptime string os = target::os;
console.writeln("compiled for ${os}");
comptime if (target::os == "windows") {
    console.writeln("path separator is a backslash");
} else {
    console.writeln("path separator is a slash");
}
compiled for linux
path separator is a slash

Built for Windows from a Linux machine and run there, the same source prints compiled for windows and path separator is a backslash:

runtime/build-triple.sh x86_64-pc-windows-gnu
leviathan --build-native hello --target x86_64-pc-windows-gnu hello.lev

That command leaves hello.exe. Because the constants describe the target, --run with a --target also folds them to the target's values, which lets you test the choice of branch without building anything:

leviathan --run --target x86_64-pc-windows-gnu hello.lev

What a target can refuse

A target that lacks a system service rejects, at compile time, a program that uses it. The diagnostic names the construct. On Windows, starting a child process is not available: a program that constructs a Process stops with a diagnostic that begins process spawn: unsupported on Windows. Files, timers, threads, sockets, async and await, and reading standard input all compile for Windows. The browser target rejects many more services; see lang.wasm-target.

Fine on Linux, rejected for a Windows target

Process p = Process("/bin/echo", ["hello"]);
console.writeln("started process ${p.pid}");

Rules

  • --target names the machine for --native-obj and --build-native, and the value the target:: constants report in every mode.
  • Linking for a target needs that target's runtime library and a linker that can produce its executables. A missing one is a compile-time error that says what was searched for.
  • On a Windows target the output file name ends in .exe; on a WebAssembly target it ends in .wasm. The compiler adds the suffix when you leave it out.
  • If your program uses a construct the target cannot support, the compile fails with a diagnostic; the program is not built.
  • The target:: constants are not available outside compile-time contexts.

Examples

Checking that a program compiles for an architecture before its runtime exists:

leviathan --native-obj app_arm.o --target aarch64-linux-gnu app.lev

A complete Windows build with an explicit runtime archive:

LVRT_OUT_DIR=/tmp/rt-win runtime/build-triple.sh x86_64-pc-windows-gnu
leviathan --build-native app --target x86_64-pc-windows-gnu --runtime /tmp/rt-win/liblvrt.a app.lev

The trident build driver passes --target through, so trident build --target x86_64-pc-windows-gnu builds a whole project the same way (see trident.commands).

Notes

Support for a target is a matter of the runtime library having a platform layer for it. The targets exercised by the project's tests are x86_64-pc-windows-gnu, aarch64-linux-gnu and wasm32-unknown-unknown.

See also