8.7 KiB
010 — Driver & CLI
- Status: Draft
- Normative language:
MUST,MUST NOT,SHOULD,SHOULD NOT, andMAYare 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
Configand input produce byte-identical outputs and an identical sequence of compiler invocations. - I2.
vct.cliholds no optimizer state. - I3. The
-llvmpath never spawnscc. - I4. The temporary directory is removed unless
-save-tempsis set. - I5. Flag assembly order is stable.
- I6. Every captured compiler error is re-emitted through the diagnostics service.
- I7.
-mangledis 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
- 000 — Overview for determinism and the no-linking rule.
- 001 — Architecture for module boundaries.
- 002 — Traits for the trait vocabulary.
- 007 — VIR Optimizer for pass toggles and the pipeline.
- 008 — C Backend for
#lineand compiler dialect use. - 009 — LLVM Backend for
-targetand the-llvmpath. - 011 — Diagnostics for diagnostics, remarks, and exit codes.
- 013 — Build & Packaging for
Configand the C API.