Files
libvct-spec/spec/010-driver-cli.md
T

8.7 KiB

010 — Driver & CLI

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

1. Purpose

libVCT splits orchestration across two layers. The driver (vct.driver) is the library-level orchestrator: it takes emitted C translation units, invokes the system C compiler, and collects object files. The CLI (vct.cli) is a thin wrapper that parses command-line flags into a Config, drives the whole pipeline, formats diagnostics, and sets exit codes. The CLI holds no optimizer state and adds no compilation logic of its own.

2. Scope

This file specifies the driver/CLI role split, the pipeline order, the full flag surface, the exact C compiler invocation, compiler discovery and probing, error handling, determinism, -mangled mode, and object-file emission and merging. The compiler-facing backends are specified in 008 — C Backend and 009 — LLVM Backend. Diagnostics, remarks, and exit codes are specified in 011 — Diagnostics.

3. Definitions

Term Meaning
Config One struct holding opt level, target, budgets, pass toggles, warning policy, compiler selection, and mode.
TU Translation unit: one emitted C file.
cc The discovered system C compiler.
APE Actually Portable Executable, the output of cosmopolitan tooling.
Fat LTO object An object that carries both native machine code and LTO bitcode.
Response file A file listing arguments for a linker or compiler, referenced by @file.

4. Model

vct.cli is GC-allocated and cold-path only. It converts argv into a Config, runs the pipeline, forwards every diagnostic to the configured consumer, and returns an exit code. vct.driver owns process invocation. Any embedder that has already constructed a Config can call the driver directly and skip the CLI entirely.

The driver also owns the -llvm path, which returns textual LLVM IR and never touches cc, and the -mangled path, which switches to the cosmopolitan toolchain.

5. Normative requirements

5.1 Role split

R1. vct.cli MUST be a thin wrapper. It MUST NOT contain optimization, lowering, or backend logic, and it MUST NOT hold optimizer state.

R2. vct.cli MUST be a pure function from argv to Config plus pipeline invocation. The library entry point MUST accept a Config; programmatic callers MUST be able to bypass the CLI.

R3. vct.driver MUST own all invocation of the system C compiler.

R4. vct.cli MUST be GC-allocated and MUST be usable on the cold path only.

5.2 Pipeline

R5. The default pipeline order MUST be: read input, then HIR optimization if enabled, then lowering, then VIR optimization if enabled, then the C backend, then the driver.

read input
  -> (HIR opt if enabled)
  -> lower
  -> (VIR opt if enabled)
  -> backend.c -> driver -> .o

R6. -S MUST stop after C emission and MUST NOT invoke cc.

R7. -emit-llvm and -llvm MUST stop after LLVM IR emission and MUST NOT invoke cc.

5.3 Flag surface

R8. The CLI MUST accept the flags listed in Table 1.

Table 1. v1 flag surface

Group Flags Meaning
Optimize -O0 -O1 -O2 -O3 -Ofast -Oz Optimization level.
Target -march=<arch>, -mcpu=<cpu>, -target <triple> Target selection; -march/-mcpu forward to cc as cflags.
Modes -S, -emit-llvm, -llvm, -mangled Stop point or alternate mode.
Toolchain -cc=<gcc|clang>, -cflags="...", -j<N>, -save-temps Compiler selection, extra flags, parallelism, temp retention.
LTO -flto[=full|thin], -ffat-lto-objects Link-time optimization control.
Debug -g, --dump-hir, --dump-vir, --verify, --time-passes, --stats Debug output and instrumentation.
Output -o <path> Output path.
Warnings -Wall, -Werror, -Wextra, -Wno-error[=<category>], -w Warning policy.
Diagnostics --remarks, --diagnostics=json Remark and diagnostic channel selection.
Pass toggles -f<pass>, -fno-<pass> Enable or disable an individual pass.

R9. -Wall, -Werror, and -Wextra MUST be on by default. A bare -Wno-error MUST disable every promotion, -Wno-error=<category> MUST disable the promotion of that category, and -w MUST silence warnings.

R10. -Werror MUST apply to the C source that libVCT generates, not only to frontend input.

R11. Every pass MUST be exposed as both -f<pass> and -fno-<pass>.

5.4 C compiler invocation

R12. For each emitted TU, the driver MUST invoke the compiler as:

cc -std=c17 -c <tu.c> -o <tu.o> <cflags> <march> -Wall -Werror -Wextra <g> <flto>

where <g> is -g when debug is enabled, and <flto> is the LTO flag when LTO is requested.

R13. The driver MUST run one compiler process per TU, with concurrency bounded by -j.

R14. The driver MUST discover the compiler via the $CC environment variable first, then by searching PATH, unless -cc=<gcc|clang> forces a selection.

R15. The driver MUST probe the discovered compiler's version and dialect exactly once per run. The probed dialect MUST drive the backend's choice of pragmas and builtins (see 008 — C Backend).

R16. The driver MUST NOT pass -march/-mcpu values to cc that the probed compiler does not accept; it MUST diagnose an unsupported target value.

5.5 Error handling

R17. The driver MUST capture the compiler's stderr.

R18. The driver MUST use the #line directives emitted by the C backend (see 008 — C Backend) to map C compiler errors back to VIR and frontend source positions, then re-emit them through the diagnostics service (see 011 — Diagnostics).

R19. A compiler failure MUST produce a diagnostic and MUST cause a nonzero exit.

R20. The driver MUST NOT discard compiler diagnostics silently.

5.6 Determinism

R21. The driver MUST order TUs and flags deterministically. For a fixed Config and input, the sequence of compiler invocations and their argument order MUST be identical across runs and independent of thread count.

R22. The driver MUST place temporary files under a GC-managed temporary directory.

R23. The temporary directory MUST be cleaned up after the run unless -save-temps is given.

5.7 -mangled mode

R24. -mangled MUST switch the toolchain to cosmocc and the cosmopolitan libc.

R25. -mangled MUST produce an Actually Portable Executable.

R26. -mangled is the one mode in which libVCT links. In every other mode libVCT produces object files and leaves linking to the caller (see 000 — Overview).

5.8 Object emission and merging

R27. In the default path, the driver MUST produce object files only. It MUST NOT link a final executable.

R28. When a single -o path is requested and the module produced multiple per-function objects, the driver MUST merge them with a relocatable link (ld -r -o <out> <tu>.o ...).

R29. The driver MUST emit, for the caller, a recommended link line or response file listing the produced objects and any needed libraries.

6. Invariants

  • I1. A fixed Config and input produce byte-identical outputs and an identical sequence of compiler invocations.
  • I2. vct.cli holds no optimizer state.
  • I3. The -llvm path never spawns cc.
  • I4. The temporary directory is removed unless -save-temps is set.
  • I5. Flag assembly order is stable.
  • I6. Every captured compiler error is re-emitted through the diagnostics service.
  • I7. -mangled is the only mode that links.

7. Examples

Default compile of a single source to an object:

vct -O2 -march=native -cc=clang -j8 -o out.o input.vir

Stop after emitting C, without invoking cc:

vct -O2 -S -o out.c input.vir

Emit textual LLVM IR instead of C:

vct -O3 -emit-llvm -target x86_64-unknown-linux-gnu -o out.ll input.vir

Produce a portable executable:

vct -O2 -mangled -o app.com input.vir

8. Cross-references