Artifacts¶
A .cdlb file is a compiled candela program. You build one with the candela
toolchain and run it with candela-vm, the small runtime that carries no
parser, compiler or REPL.
Building one¶
This writes game.cdlb beside the source. Pass -o to choose the path. See
the command line.
The whole program goes into the one file. Every workspace .cdl module the
program imports, and every standard-library module it uses, is linked into the
artifact, so it runs with no source tree present.
Running one¶
The artifact produces the same output as running the source through candela.
candela-vm loads it, binds anything it refers to by name, and runs main.
What the artifact records¶
- The bytecode instructions and the register file they run against.
- The constant pools: strings, objects and maps built at compile time.
- The struct and enum type tables, with field and variant names.
- The registers each recursive call site saves across its call, and the allocation sizes the VM needs up front.
- The source text of every file that went into the program, along with the span each instruction came from. This is what lets a runtime error from an artifact print the same underlined source report you get when running from source.
- A recipe for each dynamic-library binding: the library name, the C symbol, and
the marshalling signature. A library your program names is recorded exactly as
the
dylibblock wrote it; one a standard-library module names is recorded relative to thelibsdirectory, and marked as the library's own. - A recipe for each
hostfunction: its namespace, name, signature, and whether it is variadic. - An export table: one entry per host-callable function, with the call trampoline the compiler emitted for it.
The two recipe tables are references, not contents. A dynamic library's bytes are never embedded; the runtime re-opens the library by name and re-resolves the symbol when it loads the artifact, then rebuilds the calling interface from the recorded signature. This keeps artifacts small and lets a system library be upgraded underneath one, and it means the library has to be present wherever the artifact runs. A Rust host that ships its libraries in a directory of its own names it, and the load looks there before the loader's own paths. See C libraries.
The same applies to the standard library. A program that imports only
pure-candela modules is self-contained. The math, random and time modules
bind a dynamic library, so an artifact using them needs that library at run
time, and candela-vm looks for it where the toolchain keeps it: the directory
CANDELA_LIB_PATH names, or libs/ beside the binary, which is where an
install puts it. Such an artifact therefore runs from any working directory. See
the standard library overview.
The export table¶
A host calls into a script by name, and working out how to make that call is the
compiler's job: it specialises the function for its parameter types and lays
down a short run of instructions that moves the arguments into place, calls it,
and stops. The runtime carries no compiler, so candela build does that work
ahead of time and records the result. Each entry names the function, where its
trampoline starts, which registers its parameters live in, and their declared
types.
A function gets an entry when all of the following hold:
- It is defined in the file you are building, not in a module it imports.
- It is reachable by its bare name, and it is not
main. - Every parameter is annotated, with a type a host value can be:
int,float,bool,string,null,any, a union of those, an array of those, or a string-keyed map of those.
The last one is what a host's arguments are checked against, which is why an un-annotated parameter is left out: there is nothing to check. A parameter typed as a struct or an enum is left out too, since a host has no way to build one. See embedding for how a host makes the call.
Compiling an entry point is also what type-checks the function's body, so the build compiles one for every function whose parameters are all annotated, not only for the ones that end up in the table. A body that does not compile at its declared parameter types fails the build, naming the error; it used to lose its entry and ship as a name the runtime reported as unknown. A function with a bare parameter has no declared type to compile against, so it is left for whichever call reaches it first.
Version compatibility¶
The file starts with a four-byte marker and a one-byte format version. The runtime accepts exactly the version it was built for and rejects anything else rather than risk decoding it wrongly.
The version is raised whenever the shape of what is recorded changes. It has been raised for:
- the dynamic-library and host-function tables
- the enum type table
- the map, JSON and
anyoperations - the export table
- the map
removeinstruction, together with the mark on the dynamic libraries the standard library owns intwidening to 64 bits, which gave every recorded value a second word- the cell instructions a captured variable is read and written through
- the indirect call, which dispatches on the function a value holds
- the instruction that builds a function value, which tells a function from the list it is built as
- the string comparison and bitwise instructions, which sit among the existing instructions and so renumber the ones after them
- maps keeping insertion order, which made the order of each map's recorded pairs the order the program built them in
There is no forward or backward compatibility across a change, and there is no conversion tool.
In practice this means: build the artifact with the toolchain whose candela-vm
will run it, and rebuild after a toolchain upgrade. The failure is loud, not
silent, so a stale artifact is reported rather than mis-run.
What candela-vm refuses¶
- A file that does not carry the
.cdlbmarker, or is too short to hold a header. - An artifact built for a different format version.
- An artifact whose body does not decode.
- An artifact whose dynamic library cannot be opened. The message names the library as written and the filename it resolved to.
- An artifact whose library opened but does not export a symbol it needs.
- An artifact that declares a
hostblock. Host functions come from the program that embeds candela, and the standalone runtime registers none, so it names the functions and refuses rather than fail at the call. Run such a program from a host instead; see embedding.
All of these are reported before the program starts, so an artifact either runs or tells you why it cannot.
An embedding host loads the same artifact by handing over the closures it registered. Binding happens at load, and it checks arity and types the way compiling a script does, so an unregistered name or a closure of the wrong shape is reported before the program starts rather than at the call.