Release builds

Kest has two engines for one language. The bytecode machine compiles a program in milliseconds and reloads it while a game runs. The release engine writes the same program as C for the C compiler a game ships with, which runs it several times faster. A program answers the same on both; CI runs every example both ways and compares.

A single binary

kest build --release game.kest
./game

kest build --release writes the program as C, compiles it with the compiler CC names (or cc, and cl on Windows) against the library and kest.h found beside the command line, and writes one binary named after the program without .kest (with .exe on Windows). The library is libkest.a, or kest.lib for the Windows command line, which MSVC builds. A compiler named cl or clang-cl is handed MSVC's flags, and any other cc's; on Windows, run it where cl is on the path, such as a Developer Command Prompt, and the object cl writes beside the C is taken away with it. That name is letters, digits, ., _ and -, not starting with -, which every shell, cmd.exe and file system read the same; a program named otherwise is refused (K0663) rather than made into a binary that cannot be moved or run everywhere. The compiler is started directly with its arguments, with no shell between, so a path with a space or a quote in it needs nothing done to it. On Windows a CC that names a .bat or .cmd file is the exception: Windows runs it through cmd.exe, which reads %, ^ and & itself. Such a wrapper still works, and what kest hands it is only the install's paths and the checked release name. CC is split into words at spaces and tabs: the first is the compiler and the rest are flags it is given first, as in CC="gcc -m32", and a run in double quotes stays one word, as in CC='"C:/Program Files/LLVM/bin/clang"'. The binary carries every file the program was read from, its modules, the standard library's and the project manifest, and builds from those in memory, so it runs where there is no source and no library. Every word after the binary's name on its command line belongs to the program. A release that cannot be made says why (K0663).

The binary carries readable source. A game that must not ship readable Kest packs the binary the way it packs its other content; there is no bytecode format to ship instead, by design (see Versions and editions).

Inside an engine

An engine with its own main and its own doors uses the C directly:

kest emit --c rules.kest > rules.c
cc -O2 -Iinclude -DKEST_NO_MAIN -c rules.c -o rules.o

rules.c is one translation unit. With -DKEST_NO_MAIN it exports one function, bool kest_natives_here(KestRuntime *runtime), which binds every compiled body to a machine the engine started. The engine builds the same program from source as usual (kest_build, kest_start), calls kest_natives_here on the machine, and from then on calls into compiled bodies through the same kest_call and the same frames. A compiled body that calls a door goes through the host's binding as the machine would.

A body the C backend cannot write is named in the file with the reason and stays with the machine, called from the compiled bodies through a door of its own; one such body costs that body and nothing above it. Over the examples and the standard library every body is written. The one thing it does not write today is a constant run of numbers containing an infinity.

examples/colony does this: --native 1 binds its rules as C, and without it the machine runs them from source, which is what a reload needs. The engine can switch between them at a reload.

Keeping the two engines alike

Floating point. The machine rounds a multiply and an add separately. The generated file asks the C compiler to do the same (no floating-point contraction), which GCC and MSVC respect. A compiler that fuses them anyway answers different bits from deterministic code; clang told -ffp-contract=fast ignores what the file asks for, so do not build release Kest with that flag.

Promises. The release engine keeps no.alloc and no.host. It spends no fuel budget, but it does hear kest_cancel at the back of every while, so a runaway loop in a release still stops when asked.

Untrusted code. A release is for code its author trusts: nothing proves the C a host compiler made the way the verifier proves bytecode. A machine started with kest_start_untrusted never enters a compiled body, even one the host linked in. See Running untrusted code.