Files
libvct-spec/spec/013-build-packaging.md

9.6 KiB

013 — Build & Packaging

  • Status: Draft
  • Normative language: MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described in RFC 2119.

1. Purpose

This file specifies how libVCT is built, what it produces, what it exposes to embedders, and who owns memory across the API boundary. It also fixes the configuration, threading, versioning, and determinism contracts that the rest of the specification depends on.

2. Scope

Covers the toolchain, the build system, the shipped artifacts, the two API surfaces, module visibility, arena ownership, the Config struct, the threading contract, versioning, and the determinism guarantee. It does not restate stage semantics; those live in the stage files.

3. Definitions

Term Meaning
C API The extern(C) surface exposed to non-D callers.
D API The native D surface; the implementation itself.
Shim The thin translation layer implementing the C API over the D API.
Arena A region of memory reclaimed as a unit, not per object.
Bump allocator The default in-tree allocator that serves requests by advancing a pointer.
Cold path Code that runs at most a few times per compilation: driver, diagnostics, CLI.

4. Toolchain

  1. The library MUST be written in D.
  2. Release builds MUST use LDC2, chosen for generated-code quality.
  3. Debug builds MUST use DMD, chosen for compile and iteration speed.
  4. libVCT MUST have zero runtime dependencies beyond its own code. It MUST NOT require a third-party library at build time or at load time.
  5. A D compiler is required to build libVCT. A system C compiler is required only when the C path is used, and it MUST be discovered at runtime (see 010 — Driver & CLI). Prebuilt binaries MUST NOT require a D compiler at load time.

5. Build system

  1. The build system MUST be xmake. A single xmake.lua MUST drive every target.
  2. The build system MUST NOT be dub.
  3. The release profile MUST select LDC2. The debug profile MUST select DMD.
  4. Building libVCT MUST NOT require an external package manager.

6. Artifacts

  1. A conforming build MUST produce all of the following:
Artifact Kind Purpose
libvct.a static library Static linking of the full library.
libvct.so shared library Dynamic linking; embedders load it at runtime.
vct executable The command-line driver (see 010 — Driver & CLI).
vctc.h C header The declarations of the C API.
vct.test.hirbuild D module The fixture harness, shipped publicly (see 012 — Testing).
  1. The static and shared libraries MUST expose the same symbols and behavior; choosing one over the other MUST NOT change observable results.

7. Public API surfaces

  1. The C API MUST be the primary public surface in v1. It MUST be declared extern(C) so that C, C++, and other FFI callers can use it without a D toolchain.
  2. The C API MUST represent library state through opaque handles for Context, Module, Builder, and Config. Callers MUST NOT depend on the layout of these types, and the header MUST NOT expose their internal fields.
  3. The C API MUST provide operations to build IR, run the pipeline, and query diagnostics.
  4. The D API MUST be the full-feature implementation surface. The C API is a shim over it.
  5. The shim MUST NOT contain logic beyond argument marshalling, error translation, and handle conversion. Every behavior reachable through the C API MUST be implemented in the D API, so that every C API call exercises the D API.
  6. An implementation MUST NOT place optimizer, lowering, or backend logic in the header or the shim.

An illustrative header shape (not a complete declaration set):

typedef struct vct_context vct_context;
typedef struct vct_module  vct_module;
typedef struct vct_builder vct_builder;
typedef struct vct_config  vct_config;

vct_context *vct_context_create(const vct_config *cfg);
void         vct_context_destroy(vct_context *ctx);

8. Module visibility

  1. The following modules MUST be public: vct.ir.hir, vct.traits, vct.context, the entry points of vct.backend.c and vct.backend.llvm, vct.driver, vct.diag, and vct.test.hirbuild.
  2. The following modules MUST be internal: vct.ir.vir, vct.lower, vct.hir.opt, vct.vir.opt, and the internals of vct.comptime.
  3. A public module MUST NOT expose internal types in its public signatures in a way that forces embedders to depend on internal layout. The trait vocabulary and node identity cross this boundary and are versioned accordingly (see section 12).

9. Memory ownership and lifecycle

  1. A Context MUST own one or more arenas. An arena is the unit of reclamation. The default is one arena per Module, freed wholesale.
  2. All IR and optimizer objects MUST be arena-owned and non-GC. Passes MUST mutate them in place under epoch guards (see 002 — Traits).
  3. Garbage collection MUST be permitted only on cold paths: the driver, diagnostics, and the CLI. The optimizer, lowering, and backends MUST NOT depend on the GC for IR objects.
  4. The Context MUST accept an ArenaAllocator interface so embedders can supply their own backing memory. The interface MUST provide exactly these operations:
    • allocate(size, alignment) returns uninitialized storage owned by the arena; callers MUST NOT assume zeroed storage;
    • reset() returns the arena to its initial state so it can be reused;
    • destroy() releases the arena's backing memory.
  5. A default bump allocator MUST ship in-tree and MUST be the allocator used when the embedder supplies none.
  6. Ownership rules:
    • Objects allocated into a Context or arena live until that arena is reset or destroyed.
    • A caller MUST NOT free an individual arena-owned object. Freeing happens only through reset or destroy.
    • A pointer or handle returned across the API MUST remain valid until the owning arena is reset or destroyed.
    • Use of an object after its arena has been reset or destroyed is undefined, and the implementation MUST NOT be required to detect it.

10. Configuration

  1. A single Config MUST carry every setting that affects output. At minimum it MUST carry:
Field group Contents
Optimization level -O0, -O1, -O2, -O3, -Ofast, -Oz.
Target -march, -mcpu, -target <triple>.
Budgets comptime and HIR budgets (see 004 — HIR Optimizer) and VIR budgets (see 007 — VIR Optimizer).
Pass toggles enable and disable for every pass exposed as -f and -fno-.
Warning policy -Wall, -Wextra, -Werror, -Wno-error[=<category>], -w.
C compiler selection -cc=gcc or -cc=clang, or automatic discovery.
Mode -S, -emit-llvm/-llvm, or -mangled.
  1. The CLI MUST be a pure function from argv to a Config. The library MUST take a Config and MUST NOT read process arguments itself.
  2. Programmatic callers MUST be able to bypass the CLI and construct a Config directly.
  3. The determinism guarantee (section 13) is stated relative to a fixed Config.

11. Threading

  1. libVCT MUST be thread-safe when each compilation unit has its own Context and arenas.
  2. libVCT MUST NOT hold shared mutable global state.
  3. Intra-module parallelism is internal and MUST be bounded by the -j setting. The observed output MUST NOT depend on -j (see 000 — Overview).

12. Versioning

  1. The library MUST follow semantic versioning.
  2. The textual IR format MUST carry its own version, independent of the library version.
  3. The trait vocabulary MUST carry its own version. Adding an attribute or request is a backward-compatible change; changing the meaning of an existing one is a major change.
  4. Experimental passes MUST be gated behind feature flags so that default behavior stays stable within a minor version.

13. Determinism guarantee

  1. For a fixed Config and a fixed input, libVCT MUST produce byte-identical output on every run, independent of thread count. This binds HIR optimization, lowering, VIR optimization, and both backends. It is a correctness property, not an optimization.
  2. Determinism MUST be demonstrated in CI as specified in 012 — Testing.

14. Invariants

  • The C API and the D API MUST expose the same behavior; a divergence is a bug in the shim.
  • An arena MUST be reclaimable as a unit; no arena-owned object may outlive its arena.
  • A stage MUST NOT depend on shared mutable global state.
  • The same Config and input MUST yield the same bytes regardless of -j.

15. Cross-references