Driver -- Design Document #
The driver is the entry point and orchestrator of the entire compiler. It parses GCC-compatible command-line arguments, selects the target architecture, and dispatches input files through the correct compilation pipeline -- from preprocessing through code generation, assembly, and linking. It produces ELF executables without requiring any external toolchain (via the builtin assembler and linker), though it can optionally delegate to GCC for assembly and linking.
Overview #
The driver receives command-line arguments, classifies input files, and routes each through the appropriate pipeline stage. A single invocation can process multiple input files (C source, assembly, object files, archives) and link them together into an executable.
Command-line arguments (GCC-compatible)
|
| cli.rs: parse_cli_args()
v
Driver struct (all configuration fields populated)
|
| pipeline.rs: run()
v
Dispatch by CompileMode:
|
+-- PreprocessOnly (-E) --> preprocess --> stdout / file
|
+-- AssemblyOnly (-S) --> preprocess -> lex -> parse -> sema ->
| lower -> mem2reg -> optimize -> phi-elim ->
| codegen --> .s file
|
+-- ObjectOnly (-c) --> [compile to asm] -> assemble --> .o file
|
+-- Full (default) --> [compile to asm] -> assemble -> link --> executable
Each C source file passes through the full internal pipeline
(compile_to_assembly). Assembly source files (.s/.S) are assembled
directly. Object files and archives (.o/.a/.so) pass through to the
linker.
Module Layout #
| File | Lines | Responsibility |
|---|---|---|
mod.rs |
7 | Module declarations and public re-exports (Driver, CompileMode). |
pipeline.rs |
~1120 | Driver struct with all configuration fields, new() constructor, run() dispatcher, compilation mode handlers (run_preprocess_only, run_assembly_only, run_object_only, run_full), and the core compile_to_assembly pipeline. |
cli.rs |
~700 | GCC-compatible CLI argument parsing: parse_cli_args(), query flag handling, response file expansion, and the main argument loop. |
external_tools.rs |
~350 | External tool invocation and assembly source file handling: GCC -m16 delegation, source .s/.S file assembly (both builtin and GCC-backed), assembler argument construction, linker argument construction, and dependency file generation. |
file_types.rs |
~110 | Input file classification: object/archive detection by extension and magic bytes, C source detection, assembly source detection, explicit language override, and line marker stripping for -P. |
The Driver Struct #
The Driver struct holds every piece of configuration parsed from the command
line. All fields are pub(super) (visible only within the driver module) and
populated exclusively by parse_cli_args(). The struct is created with
Driver::new() which provides sensible defaults.
Configuration Categories #
Target and output:
| Field | Type | Default | Description |
|---|---|---|---|
target |
Target |
X86_64 |
Architecture (detected from binary name or -m32/-m16) |
output_path |
String |
"a.out" |
Output file path (from -o) |
output_path_set |
bool |
false |
Whether -o was explicitly given |
input_files |
Vec<String> |
[] |
Input source/object/archive paths |
mode |
CompileMode |
Full |
Pipeline stop point (from -E/-S/-c) |
Optimization:
| Field | Type | Default | Description |
|---|---|---|---|
opt_level |
u32 |
2 |
Internal optimization level (always 2; all levels run the same passes) |
optimize |
bool |
false |
Whether user passed -O1 or higher (defines __OPTIMIZE__) |
optimize_size |
bool |
false |
Whether -Os/-Oz (defines __OPTIMIZE_SIZE__) |
The internal opt_level is always 2 regardless of the CLI flag. The optimize
and optimize_size booleans only control predefined macros (__OPTIMIZE__,
__OPTIMIZE_SIZE__), which build systems like the Linux kernel rely on (e.g.,
BUILD_BUG() uses __OPTIMIZE__ to select between a noreturn function call
and a no-op).
Preprocessor:
| Field | Type | Default | Description |
|---|---|---|---|
defines |
Vec<CliDefine> |
[] |
-D macro definitions |
include_paths |
Vec<String> |
[] |
-I include search paths |
quote_include_paths |
Vec<String> |
[] |
-iquote paths (searched only for #include "file") |
isystem_include_paths |
Vec<String> |
[] |
-isystem system include paths |
after_include_paths |
Vec<String> |
[] |
-idirafter paths (searched last) |
force_includes |
Vec<String> |
[] |
-include files (processed before main source) |
undef_macros |
Vec<String> |
[] |
-U macro undefinitions |
undef_all |
bool |
false |
-undef (suppress all predefined macros) |
gnu_extensions |
bool |
true |
GNU C extensions enabled (false with -std=c99 etc.) |
gnu89_inline |
bool |
false |
GNU89 inline semantics (-fgnu89-inline or -std=gnu89) |
nostdinc |
bool |
false |
-nostdinc (no default system include paths) |
suppress_line_markers |
bool |
false |
-P (strip # line "file" from -E output) |
dump_defines |
bool |
false |
-dM (dump all #defines instead of preprocessed text) |
explicit_language |
Option<String> |
None |
-x language override (e.g., "c", "assembler-with-cpp") |
Code generation:
| Field | Type | Default | Description |
|---|---|---|---|
debug_info |
bool |
false |
-g (emit DWARF .file/.loc directives) |
pic |
bool |
false |
-fPIC/-fpic (position-independent code) |
function_return_thunk |
bool |
false |
-mfunction-return=thunk-extern (Spectre v2 retpoline) |
indirect_branch_thunk |
bool |
false |
-mindirect-branch=thunk-extern (retpoline for indirect calls) |
patchable_function_entry |
Option<(u32,u32)> |
None |
-fpatchable-function-entry=N,M (NOP padding for ftrace) |
cf_protection_branch |
bool |
false |
-fcf-protection=branch (Intel CET endbr64) |
no_sse |
bool |
false |
-mno-sse (avoid all SSE/XMM instructions) |
enable_sse3 .. enable_avx2 |
bool |
false |
-msse3 through -mavx2 (SIMD feature flags) |
general_regs_only |
bool |
false |
-mgeneral-regs-only (no FP/SIMD registers; AArch64 kernel) |
code_model_kernel |
bool |
false |
-mcmodel=kernel (negative 2GB address space) |
no_jump_tables |
bool |
false |
-fno-jump-tables (compare-and-branch chains for switches) |
function_sections |
bool |
false |
-ffunction-sections (each function in its own section) |
data_sections |
bool |
false |
-fdata-sections (each global in its own section) |
code16gcc |
bool |
false |
-m16 (16-bit real mode boot code) |
regparm |
u8 |
0 |
-mregparm=N (i686: pass first N int args in registers) |
omit_frame_pointer |
bool |
false |
-fomit-frame-pointer (free EBP as GP register) |
no_unwind_tables |
bool |
false |
-fno-asynchronous-unwind-tables (suppress .eh_frame) |
fcommon |
bool |
false |
-fcommon (COMMON linkage for tentative definitions) |
RISC-V specific:
| Field | Type | Default | Description |
|---|---|---|---|
riscv_abi |
Option<String> |
None |
-mabi= override (e.g., lp64, lp64d) |
riscv_march |
Option<String> |
None |
-march= override (e.g., rv64imac_zicsr_zifencei) |
riscv_no_relax |
bool |
false |
-mno-relax (suppress linker relaxation) |
Linker:
| Field | Type | Default | Description |
|---|---|---|---|
linker_paths |
Vec<String> |
[] |
-L library search paths |
linker_ordered_items |
Vec<String> |
[] |
Ordered list of -l, -Wl,, and object/archive paths |
static_link |
bool |
false |
-static |
shared_lib |
bool |
false |
-shared |
nostdlib |
bool |
false |
-nostdlib |
relocatable |
bool |
false |
-r (relocatable link; merge .o files) |
Diagnostics:
| Field | Type | Default | Description |
|---|---|---|---|
warning_config |
WarningConfig |
all enabled | Warning enable/disable/error state (from -W flags) |
color_mode |
ColorMode |
Auto |
-fdiagnostics-color={auto,always,never} |
verbose |
bool |
false |
-v / --verbose |
Dependency generation:
| Field | Type | Default | Description |
|---|---|---|---|
dep_file |
Option<String> |
None |
Dependency file path (from -MF or -Wp,-MMD,path) |
dep_only |
bool |
false |
-M/-MM (output dependency rules, no compilation) |
dep_target |
Option<String> |
None |
-MT (override target name in dependency rule) |
Other:
| Field | Type | Default | Description |
|---|---|---|---|
pthread |
bool |
false |
-pthread (defines _REENTRANT for configure scripts like ax_pthread.m4) |
assembler_extra_args |
Vec<String> |
[] |
-Wa, assembler passthrough flags |
raw_args |
Vec<String> |
[] |
Raw CLI args for GCC -m16 passthrough |
CLI Parsing (cli.rs) #
The CLI parser is a hand-written while loop with a flat match on each
argument. No external parser library is used. The design priorities are:
-
GCC compatibility. Build systems (Linux kernel, Meson, autoconf) pass a wide variety of GCC flags. The parser accepts all of them, silently ignoring unknown
-fand-mflags to match GCC's behavior. -
Positional ordering. Linker items (
-l,-Wl,, object files) are collected intolinker_ordered_itemspreserving their command-line order. This is critical for flags like-Wl,--whole-archivewhich must appear before the archive they affect. -
Early-exit query flags. Build system probes like
-dumpmachine,-dumpversion,--version,-v(alone),-print-search-dirs, and-print-file-name=are handled before input files are required. These print information and exit immediately.
Target Detection #
The target architecture is detected from the binary name (argv[0]):
| Binary name contains | Target |
|---|---|
arm or aarch64 |
AArch64 |
riscv |
RISC-V 64 |
i686 or i386 |
i686 |
| anything else | x86-64 |
The -m32 flag overrides the target to i686, and -m16 selects i686 with the
code16gcc flag set (for 16-bit real mode boot code).
Response Files #
The @file syntax (GCC/MSVC convention) is supported. When an argument starts
with @, the file is read and its contents are split into tokens respecting
single/double quotes and backslash escaping. Build systems like Meson use this
when command lines exceed OS limits.
GCC Compatibility Probes #
The driver reports as GCC 14.2.0 for build system compatibility:
| Query | Response |
|---|---|
-dumpmachine |
Target triple (e.g., x86_64-linux-gnu) |
-dumpversion |
14 |
--version |
ccc (Claude's C Compiler, GCC-compatible) 14.2.0 + FSF copyright + backend mode |
-v (alone) |
Target and version info |
-print-search-dirs |
Standard library directory layout |
-print-file-name=NAME |
Searches standard GCC library paths; returns bundled include dir for include |
-Wl,--version (no inputs) |
With gcc_linker: delegates to GCC; otherwise prints GNU ld (Claude's C Compiler built-in) 2.42 for Meson linker detection |
The --version output includes "Free Software Foundation" text because Meson
detects GCC by grepping for that string. It also prints the backend mode
("standalone" or a list of enabled GCC fallback features like gcc_linker,
gcc_assembler, gcc_m16).
SIMD Feature Flag Implication Chain #
SIMD feature flags follow GCC's implication chain where each flag implies all lower-tier flags:
-mavx2 → -mavx → -msse4.2 → -msse4.1 → -mssse3 → -msse3
When any of these are set, the corresponding __SSE3__, __AVX__, __AVX2__,
etc. predefined macros are defined so projects like blosc can compile
SIMD-optimized code paths.
Standard Version and GNU Extensions #
The -std= flag controls GNU extensions and inline semantics:
gnu89,gnu99,gnu11,gnu17,gnu23: GNU extensions enabledc89,c99,c11,c17,c23,iso9899:*: strict ISO C (GNU extensions disabled)gnu89,c89,gnu90,c90,iso9899:1990,iso9899:199409: GNU89 inline semantics (__GNUC_GNU_INLINE__)- All others: C99+ inline semantics (
__GNUC_STDC_INLINE__)
When GNU extensions are disabled, bare keywords like typeof and asm are
treated as identifiers; the double-underscore forms (__typeof__, __asm__)
always work.
Compilation Pipeline (pipeline.rs) #
Core Pipeline: compile_to_assembly #
The heart of the driver is compile_to_assembly(), which transforms a C source
file into target-specific assembly text. The pipeline has 9 phases, each
producing output consumed by the next:
Source text (String)
|
[1] | Preprocessor macro expansion, #include, #ifdef, #pragma
v
Preprocessed text (String)
|
[2] | Lexer tokenize with source locations
v
Token stream (Vec<Token>)
|
[3] | Parser recursive descent → spanned AST
v
AST (TranslationUnit)
|
[4] | Sema type-check, symbol resolution, const eval
v
Typed AST + TypeContext + FunctionInfo map
|
[5] | Lowerer AST → alloca-based IR
v
IrModule (alloca-form)
|
[6] | mem2reg promote allocas to SSA
v
IrModule (SSA-form)
|
[7] | Optimization constant fold, DCE, GVN, inline, LICM, etc.
v
IrModule (optimized SSA)
|
[8] | Phi elimination SSA phi nodes → Copy instructions
v
IrModule (non-SSA, copy-form)
|
[9] | Codegen IR → target-specific assembly text
v
Assembly text (String)
Between lowering and mem2reg, the driver applies #pragma weak and
#pragma redefine_extname directives from the preprocessor, and applies
-fcommon by marking qualifying tentative definitions as COMMON symbols.
Phase Timing #
Set CCC_TIME_PHASES=1 in the environment to print per-phase wall-clock timing
to stderr:
[TIME] preprocess: 0.012s
[TIME] lex: 0.003s (1847 tokens)
[TIME] parse: 0.005s
[TIME] sema: 0.008s
[TIME] lowering: 0.015s (42 functions)
[TIME] mem2reg: 0.002s
[TIME] opt passes: 0.025s
[TIME] phi elimination: 0.001s
[TIME] codegen: 0.018s (24576 bytes asm)
[TIME] total compile input.c: 0.089s
Diagnostic Engine Threading #
The diagnostic engine (DiagnosticEngine) owns the source manager and is
threaded through the pipeline:
- Created after preprocessing, configured with the warning config and color mode. Preprocessor warnings are immediately routed through it.
- Source manager created during lexing; transferred to the diagnostic engine before parsing (holds file content, line maps, and macro expansion info).
- Passed to the parser for span-based error reporting.
- Retrieved from the parser and passed to sema.
- Retrieved from sema; source manager extracted for debug info emission.
Preprocessor warnings are routed through the diagnostic engine with Cpp kind
so they can be controlled via -Wcpp/-Wno-cpp/-Werror=cpp. After sema
completes, the driver checks diagnostics.has_errors() to catch warnings
promoted to errors by -Werror.
Preprocessor Configuration #
The configure_preprocessor() method sets up the preprocessor with all
target-specific and user-specified state:
- Target macros: Architecture-specific predefined macros (set via
set_target("x86_64")etc.) - RISC-V overrides:
-mabi=and-march=values override default RV64GC/lp64d macros - Strict ANSI:
__STRICT_ANSI__defined when-std=c*(non-GNU) mode is selected - Inline semantics:
__GNUC_GNU_INLINE__vs__GNUC_STDC_INLINE__ - Optimization macros:
__OPTIMIZE__for-O1+,__OPTIMIZE_SIZE__for-Os/-Oz - PIC macro:
__PIC__/__pic__when-fPICis active - SSE/MMX macros:
__SSE__,__SSE2__,__MMX__(always for x86-64, unless-mno-sse) - Extended SIMD macros:
__SSE3__,__AVX__, etc. per feature flags - User defines:
-Dmacros _FORTIFY_SOURCEsuppression: Always undefined to prevent glibc fortification wrappers that use unsupported GCC builtins (__builtin_va_arg_pack)- Include paths:
-I,-iquote,-isystem,-idirafter
Force-include files (-include) are processed after the preprocessor is
configured but before the main source. They are searched in the current
working directory first, then through the configured include paths.
Source File Reading #
The driver handles non-UTF-8 C source files. Raw bytes with values 0x80-0xFF
are encoded as Private Use Area code points (U+E080-U+E0FF) so the source can
be stored as a Rust String. The lexer decodes these back to raw bytes inside
string and character literals. This allows compiling code with Latin-1 string
literals or other non-UTF-8 content.
Run Modes #
PreprocessOnly (-E) #
Two sub-modes:
-
Assembly source (
.Sfiles or-x assembler-with-cpp): With thegcc_assemblerfeature, delegates preprocessing to GCC, forwarding all include paths, defines, force-include files,-nostdinc,-Uflags,-undef,-xlanguage override, and dependency file generation flags. Withoutgcc_assembler, uses the built-in C preprocessor with__ASSEMBLER__defined and assembly-mode tokenization enabled. -
C source: Runs the built-in preprocessor. Special cases:
-dMmode: preprocesses the source then dumps all resulting#definemacros instead of the preprocessed text.-M/-MMmode: outputs Make-compatible dependency rules and exits.-Pflag: strips line markers (# <line> "file") from output.
AssemblyOnly (-S) #
Compiles C source files through compile_to_assembly() and writes the
resulting .s file. With the gcc_m16 feature enabled and -m16, delegates
to GCC instead (see GCC -m16 Delegation below).
ObjectOnly (-c) #
Compiles and assembles. Assembly source files (.s/.S) are assembled
directly (via the builtin assembler or GCC). C source files go through
compile_to_assembly() then assembly. RISC-V-specific assembler flags
(-mabi, -march, -mno-relax, -fno-pic) are forwarded.
Full (default) #
Compiles, assembles, and links. Input files are classified:
- C source (
.c): compiled to assembly, assembled to a temp.ofile - Assembly source (
.s/.S): assembled to a temp.ofile - Object/archive (
.o/.a/.so): passed through to the linker vialinker_ordered_items(preserving command-line position) - Unknown extension with ELF/ar magic bytes: detected at runtime by
looks_like_binary_object()and passed through to the linker
Temporary .o files use RAII-based TempFile guards that clean up on all
exit paths (success, error, or panic). Freshly compiled objects are placed
first in the link order, followed by passthrough items in their original
command-line order.
In verbose mode (-v), the driver prints a synthetic link line containing
/usr/bin/ld and -L flags. This exists for CMake compatibility -- CMake's
CMakeParseImplicitLinkInfo.cmake extracts -L paths from it to populate
CMAKE_C_IMPLICIT_LINK_DIRECTORIES.
Linker arguments are constructed by build_linker_args():
- Order-independent flags:
-nostdlib(if-r),-shared,-static,-nostdlib,-Lpaths - Positional items from
linker_ordered_items: object files,-lflags,-Wl,pass-through flags in their original CLI order
Assembly and Linking #
Assembler Selection #
Assembler selection is a compile-time decision via Cargo features:
| Build Configuration | Behavior |
|---|---|
| Default (no features) | Use the per-architecture builtin assembler |
--features gcc_assembler |
Use GCC as the assembler |
When using the builtin assembler for a source .S file, the driver runs its
own C preprocessor first (with __ASSEMBLER__ defined and assembly-mode
tokenization enabled) before passing the result to the builtin assembler. For
.s files, the content is read directly.
The builtin assembler path also handles the -Wa,--version probe: when
detected, it prints GNU assembler (Claude's C Compiler built-in) 2.42 to satisfy the Linux
kernel's scripts/as-version.sh.
Linker Selection #
Linker selection is also a compile-time decision (handled in
backend/common.rs and backend/mod.rs):
| Build Configuration | Behavior |
|---|---|
| Default (no features) | Use the per-architecture builtin linker |
--features gcc_linker |
Use GCC as the linker |
The driver calls Target::link() or Target::link_with_args() which dispatch
to the selected linker implementation.
GCC -m16 Delegation #
The -m16 flag generates i386 code with .code16gcc prepended for 16-bit real
mode execution (used by the Linux kernel boot code at arch/x86/boot/). Because
the internal i686 backend currently produces code that exceeds the 32KB real-mode
size limit, -m16 compilation of C source files is delegated entirely to GCC.
The delegation preserves the raw CLI arguments (raw_args) to maintain flag
ordering semantics. It strips -o, -c, and -S flags (added back by the
driver) and suppresses GCC warnings with -w.
This is explicitly a temporary hack (// TODO: Remove this once i686 code size optimizations bring boot code under 32KB).
File Type Classification (file_types.rs) #
Input files are classified by extension and magic bytes:
Object/Archive Detection (is_object_or_archive) #
Standard extensions: .o, .a, .so
Non-standard extensions used by build systems:
.os/.od(heatshrink static/dynamic variants).lo(libtool objects).obj(Windows-style, cross-platform projects)
Versioned shared libraries: .so.1, .so.1.2.3, etc.
Suffixed static archives: .a.xyzzy (skarnet.org build system). Only matched
in the filename component to avoid false positives in directory names.
Magic Byte Detection (looks_like_binary_object) #
Files with unrecognized extensions are probed at runtime by reading the first 8 bytes:
\x7fELF→ ELF object file!<arch>\n→ ar archive
These are added to the link order as extra passthrough objects.
Other Classifications #
- C source:
.c,.h,.i - Assembly source:
.s(pure assembly),.S(assembly with C preprocessor) - Explicit language:
-x assembler,-x assembler-with-cppoverrides extension-based detection
Dependency File Generation #
The driver generates Make-compatible dependency files (.d) for build system
integration. Three mechanisms:
-MD/-MMD: Derive.dpath from output path (replace extension)-MF path: Explicit dependency file path-Wp,-MMD,path/-Wp,-MD,path: Preprocessor passthrough syntax
The generated dependency rule has the format output: input\n. The current
implementation is minimal -- it lists only the source file as a dependency,
not included headers. This is sufficient for the Linux kernel's fixdep
processing.
The -M/-MM flags enter dependency-only mode: the driver outputs Make rules
to stdout (or to -o path) instead of compiling. -MT overrides the target
name in the dependency rule.
Codegen Options #
The compile_to_assembly() method constructs a CodegenOptions struct that
captures all CLI-driven flags affecting code generation. This struct is passed
to Target::generate_assembly_with_opts_and_debug():
CodegenOptions {
pic, // -fPIC or -shared
function_return_thunk, // -mfunction-return=thunk-extern
indirect_branch_thunk, // -mindirect-branch=thunk-extern
patchable_function_entry, // -fpatchable-function-entry=N,M
cf_protection_branch, // -fcf-protection=branch
no_sse, // -mno-sse
general_regs_only, // -mgeneral-regs-only
code_model_kernel, // -mcmodel=kernel
no_jump_tables, // -fno-jump-tables
no_relax, // -mno-relax (RISC-V)
debug_info, // -g
function_sections, // -ffunction-sections
data_sections, // -fdata-sections
code16gcc, // -m16
regparm, // -mregparm=N
omit_frame_pointer, // -fomit-frame-pointer
emit_cfi, // !(-fno-asynchronous-unwind-tables)
}
Note that pic is forced on when -shared is specified (shared libraries
require PIC).
Post-Lowering Transformations #
After IR lowering and before mem2reg, the driver applies several transformations to the alloca-form IR:
#pragma weak: Marks symbols as weak or creates weak aliases.#pragma redefine_extname: Creates.setaliases to rename external symbols.-fcommon: Marks tentative definitions (file-scope globals with no initializer, noextern, nostatic, not thread-local, with zero initialization) as COMMON symbols for cross-TU merging.
Main Entry Point (lib.rs / compiler_main) #
The shared entry point compiler_main() (in lib.rs, called by each
binary's main()) spawns a worker thread with a 64 MB stack (the default
~8 MB is insufficient for deeply recursive descent parsing of large generated
C files like Bison parsers). The worker thread:
- Collects command-line arguments
- Creates a
Driverand callsparse_cli_args() - Exits early if a query flag was handled
- Checks for input files
- Calls
driver.run()
Panics from the worker thread are caught and printed as "internal error" messages to avoid silent failures.
The run() method sets two thread-local configuration values before
dispatching:
set_target_ptr_size(): pointer size (4 for i686, 8 for 64-bit targets)set_target_long_double_is_f128():truefor AArch64/RISC-V (IEEE binary128),falsefor x86/i686 (x87 80-bit)
These are used throughout the type system for sizeof, alignment, and ABI
computations.
Environment Variables #
| Variable | Where | Purpose |
|---|---|---|
CCC_TIME_PHASES |
pipeline.rs |
Print per-phase compilation timing to stderr |
CCC_ASM_DEBUG |
external_tools.rs |
Dump preprocessed assembly to /tmp/asm_debug_<name>.s |
CCC_KEEP_ASM |
common::temp_files, backend::common |
Preserve intermediate .s files next to output (for debugging) |
Note: Assembler/linker selection is a compile-time decision via Cargo features
(gcc_assembler, gcc_linker), not environment variables. See the top-level
README.md for details.
Design Decisions #
No External Parser Library #
The CLI parser is a flat while/match loop. This avoids dependencies and
gives fine-grained control over GCC compatibility edge cases (e.g., -Wl,
comma splitting, -Wp,-MMD,path parsing, -D with and without space). The
parser handles approximately 80 distinct flag patterns.
Silent Ignore of Unknown Flags #
Unknown -f and -m flags are silently ignored (logged only in verbose mode).
This matches GCC's behavior and is critical for build system compatibility --
the Linux kernel build passes many architecture-specific flags that only newer
GCC versions understand. Rejecting unknown flags would break kernel builds.
Positional Linker Item Ordering #
Object files, -l flags, and -Wl, flags are collected in a single ordered
list (linker_ordered_items) rather than separate vectors. This preserves the
command-line ordering that the linker depends on -- flags like
-Wl,--whole-archive must precede the archive they affect, and library
ordering determines symbol resolution priority.
RAII Temp File Cleanup #
Temporary .o files in the full compilation mode use TempFile guards
(from common::temp_files) that delete the file when dropped. This ensures
cleanup on all exit paths: normal completion, early ? returns, and panics.
The CCC_KEEP_ASM mechanism can override this for debugging.
Bundled Include Directory #
The -print-file-name=include query returns the path to the compiler's bundled
include directory (containing intrinsic headers like arm_neon.h,
emmintrin.h, etc.) so that build systems pick up compatible headers instead
of the host GCC's headers which use incompatible builtins.
_FORTIFY_SOURCE Suppression #
The driver unconditionally undefines _FORTIFY_SOURCE because glibc's
fortification headers emit extern always_inline wrappers using
__builtin_va_arg_pack() and __builtin_va_arg_pack_len(), which are
GCC-specific constructs that cannot be fully supported. Without this
suppression, the wrappers produce incorrect code (infinite recursion).
File Inventory #
| File | Purpose |
|---|---|
mod.rs |
Module declarations, re-exports Driver and CompileMode. |
pipeline.rs |
Driver struct definition (all config fields), constructor, run() dispatcher, mode handlers (run_preprocess_only, run_assembly_only, run_object_only, run_full), compile_to_assembly core pipeline, preprocessor configuration, force-include processing, source file reading with non-UTF-8 support. |
cli.rs |
parse_cli_args() entry point, target detection from binary name, query flag handling (-dumpmachine, --version, -print-file-name=, etc.), response file expansion with quote handling, main argument parsing loop (~80 flag patterns), -Wl,--version linker detection probe. |
external_tools.rs |
compile_with_gcc_m16() delegation for -m16 boot code, assemble_source_file() with builtin/GCC/custom assembler dispatch, assemble_source_file_builtin() with built-in C preprocessor for .S files, build_asm_extra_args() for RISC-V assembler flag construction, build_linker_args() for ordered linker argument construction, write_dep_file() for Make-compatible dependency generation. |
file_types.rs |
is_object_or_archive() extension-based detection, is_c_source(), looks_like_binary_object() magic-byte probing, is_assembly_source(), is_explicit_assembly() for -x override, strip_line_markers() for -P output. |