Common Module -- Design Document #
The common module is the shared foundation of the compiler. It contains
types, data structures, and algorithms that are used across multiple compiler
phases -- from the preprocessor and parser through semantic analysis, IR
lowering, optimization passes, and code generation. Nothing in common
depends on any specific phase; all dependencies flow inward.
This document describes the 12 sub-modules, explains the key design decisions, and provides enough context to understand the system without reading code.
Table of Contents #
- Module Overview
- Dependency Diagram
- The Dual Type System: CType vs IrType
- Module Reference
- types.rs -- Type Representations
- error.rs -- Diagnostic Infrastructure
- source.rs -- Source Location Tracking
- symbol_table.rs -- Scoped Symbol Table
- type_builder.rs -- Shared Type Resolution
- const_arith.rs -- Constant Arithmetic Primitives
- const_eval.rs -- Shared Constant Expression Evaluation
- long_double.rs -- Long Double Precision Support
- encoding.rs -- Non-UTF-8 Source File Handling
- asm_constraints.rs -- Inline Assembly Constraint Classification
- fx_hash.rs -- Fast Non-Cryptographic Hash
- temp_files.rs -- RAII Temporary File Handling
- Eliminating Duplication: The const_eval / const_arith Pattern
Module Overview #
| Module | Purpose | Primary Consumers |
|---|---|---|
types.rs |
CType, IrType, StructLayout, ABI classification | Every phase |
error.rs |
Diagnostic engine with GCC-compatible output | Parser, sema, driver |
source.rs |
Span, SourceLocation, SourceManager | Lexer, parser, sema, backend |
symbol_table.rs |
Scoped symbol table for name resolution | Sema, sema const_eval |
type_builder.rs |
Shared AST-to-CType conversion trait | Sema, lowering |
const_arith.rs |
Low-level integer/float constant arithmetic | Sema const_eval, lowering const_eval |
const_eval.rs |
Shared constant expression evaluation logic | Sema const_eval, lowering const_eval |
long_double.rs |
x87 80-bit and IEEE binary128 arithmetic/conversion | const_arith, constant_fold pass, IR constants |
encoding.rs |
PUA encoding for non-UTF-8 source bytes | Lexer |
asm_constraints.rs |
Inline asm constraint classification | Inline pass, cfg_simplify, backend |
fx_hash.rs |
FxHashMap / FxHashSet type aliases | Every phase |
temp_files.rs |
RAII temp file management | Driver, backend (assembler invocation) |
Dependency Diagram #
The following diagram shows which compiler phases use which common modules. Arrows point from consumer to provider.
+--------------------------------------------------+
| common/ |
| |
Compiler Phases | Modules |
=============== | ======= |
| |
Preprocessor --------->| fx_hash, encoding |
| |
Lexer ---------------->| source (Span), encoding (decode_pua_byte) |
| |
Parser --------------->| source (Span), error (DiagnosticEngine), |
| fx_hash, types (AddressSpace) |
| |
Sema ----------------->| types (CType, StructLayout), symbol_table, |
| type_builder, const_arith, const_eval, |
| error, fx_hash, long_double |
| |
IR Lowering ---------->| types (CType, IrType, StructLayout), |
| type_builder, const_arith, const_eval, |
| source (Span), fx_hash, long_double |
| |
Optimization Passes --->| types (IrType), fx_hash, long_double, |
| asm_constraints |
| |
Backend -------------->| types (IrType, EightbyteClass, RiscvFloatClass, |
| AddressSpace), source, fx_hash, |
| asm_constraints, temp_files |
| |
Driver --------------->| error (DiagnosticEngine, WarningConfig, |
| ColorMode), source (SourceManager), |
| temp_files |
+--------------------------------------------------+
Key observations:
types.rsandfx_hash.rsare truly universal -- imported by every phase.const_arith.rsandconst_eval.rsare consumed by exactly two callers (sema and lowering), which is their entire reason for existing.encoding.rsis used only by the lexer.temp_files.rsis used only by the driver and backend for assembler/linker invocation.
The Dual Type System: CType vs IrType #
One of the most important design decisions in the compiler is maintaining two separate type representations. Understanding why they exist and how they relate is essential for working on any phase.
CType -- The C-Level Type #
CType represents types as the C programmer sees them. It preserves all
semantic distinctions that matter for type checking, sizeof, and ABI:
CType::Void, Bool,
Char, UChar, Short, UShort, Int, UInt,
Long, ULong, LongLong, ULongLong, Int128, UInt128,
Float, Double, LongDouble,
ComplexFloat, ComplexDouble, ComplexLongDouble,
Pointer(Box<CType>, AddressSpace),
Array(Box<CType>, Option<usize>),
Function(Box<FunctionType>),
Struct(RcStr), Union(RcStr), Enum(EnumType),
Vector(Box<CType>, usize)
All 27 variants are shown above: 17 primitive scalar types (Void through LongDouble), 3 complex types, 4 compound type constructors (Pointer, Array, Function, Vector), and 3 named aggregate types (Struct, Union, Enum).
CType distinguishes int from long even when the underlying sizes match
(e.g., both are 32 bits on ILP32), because C requires them to be distinct
types for type compatibility checks and correct format specifier warnings.
CType also carries struct/union identity
through RcStr keys (e.g., "struct.Foo") that index into a layout table.
CType is the primary type during parsing, semantic analysis, and the early stages of IR lowering. It is used for:
- Type checking and implicit conversion rules
- sizeof / alignof evaluation
- Struct layout computation (field offsets, padding, bitfields)
- ABI classification (determining register vs. stack passing)
IrType -- The IR-Level Type #
IrType is a flat enumeration of machine-level types with no nesting:
IrType::I8, I16, I32, I64, I128,
U8, U16, U32, U64, U128,
F32, F64, F128,
Ptr, Void
IrType is what the IR instructions, optimization passes, and code generator work with. It collapses C-level distinctions that are irrelevant at the machine level:
longandlong longon LP64 both becomeI64(whileintis alwaysI32)- All pointer types become
Ptr - Struct and array types are decomposed into sequences of scalar loads/stores
IrType knows its own size and alignment, which vary by target. On i686, for
example, I64 is 4-byte aligned (not 8), and F128 (long double) is 12
bytes with 4-byte alignment. These platform differences are captured by
thread-local target configuration (set_target_ptr_size,
set_target_long_double_is_f128) that the driver sets at startup.
Why Two Systems? #
The lowering phase is the bridge. It reads CType information from the AST and sema, computes struct layouts, performs ABI classification, and produces IR instructions annotated with IrType. Once lowering is complete, CType is no longer needed -- the optimization passes and backend work exclusively with IrType.
If there were only one type system, it would either be too detailed for the
backend (carrying struct names and C-level distinctions through every
optimization pass) or too coarse for semantic analysis (losing the ability to
distinguish int* from long*).
Target Configuration #
Both type systems depend on target parameters stored in thread-locals:
TARGET_PTR_SIZE(4 for i686/ILP32, 8 for x86-64/AArch64/RISC-V LP64)TARGET_LONG_DOUBLE_IS_F128(true for AArch64/RISC-V, false for x86)
The driver calls set_target_ptr_size() and set_target_long_double_is_f128()
before compilation begins. Helper functions like target_int_ir_type() and
widened_op_type() use these to return target-appropriate types, preventing
hardcoded assumptions from leaking into phase-specific code.
Module Reference #
types.rs -- Type Representations #
The largest module in common/. Contains:
CType enum -- All 27 C-level type variants, from Void through Vector.
Struct and union variants carry an RcStr (reference-counted string) key that
indexes into a StructLayout table. This makes cloning a struct type a cheap
refcount bump instead of a heap allocation.
CType has an extensive public method surface (20+ methods):
- Size/alignment:
size(),size_ctx(ctx),align_ctx(ctx),preferred_align_ctx(ctx)-- the_ctxvariants accept aStructLayoutProviderto resolve struct/union sizes. - Classification:
is_integer(),is_signed(),is_unsigned(),is_floating(),is_complex(),is_arithmetic(),is_vector(),is_pointer_like(),is_function_pointer(),is_struct_or_union(). - Type arithmetic:
usual_arithmetic_conversion(lhs, rhs)-- implements C11 6.3.1.8 usual arithmetic conversions.integer_rank()-- returns the integer conversion rank per C11 6.3.1.1.integer_promoted()-- promotes sub-int types toint.conditional_composite_type(then, else)-- merges types for the ternary operator. - Complex support:
complex_component_type()-- returns the component type (e.g.,FloatforComplexFloat). - Pointer/function introspection:
get_function_type(),func_ptr_return_type(strict),vector_info(). - Display: implements
std::fmt::Displayfor user-friendly C type names in diagnostics (e.g.,unsigned long *,void (*)(int, double)).
IrType enum -- Flat machine-level types (15 variants). Methods:
size(),align()-- target-dependent size and alignment.is_signed(),is_unsigned(),is_integer(),is_float(),is_long_double(),is_128bit().to_unsigned()-- converts signed variants to unsigned (e.g., I32 to U32).truncate_i64(val)-- truncates an i64 to the width of this type, then sign-extends back, implementing C truncation semantics.from_ctype(ct)-- converts a CType to the corresponding IrType.
RcLayout type alias -- Rc<StructLayout>. Cloning is a cheap reference
count increment instead of deep-copying all field names, types, and offsets.
This eliminates the most expensive cloning in the lowering phase.
StructField struct -- Input description of a field for layout computation:
name, CType, optional bitfield width, optional per-field alignment override,
and per-field is_packed flag.
StructLayout -- Computed layout of a struct or union: field offsets,
total size, alignment, and is_union / is_transparent_union flags.
Fields are represented as StructFieldLayout entries, each carrying a name,
byte offset, CType, and optional bitfield offset/width. Layout computation
follows the System V ABI rules, including:
- Natural alignment with padding
- Bitfield packing across storage units (both standard and packed modes)
__attribute__((packed))and#pragma pack(N)support_Alignas/__attribute__((aligned(N)))per-field overrides- Zero-width bitfield alignment forcing
A StructLayoutBuilder handles the stateful layout algorithm, tracking the
current byte offset, maximum alignment, and bitfield accumulation state.
StructLayout public methods:
- Construction:
for_struct_with_packing(fields, max_field_align, ctx),for_union_with_packing(fields, max_field_align, ctx),empty(),empty_union(),empty_rc(),empty_union_rc(). - ABI classification:
classify_sysv_eightbytes(ctx)-- System V AMD64 ABI eightbyte classification for register passing. Returns a vector ofEightbyteClassvalues (one per eightbyte).classify_riscv_float_fields(ctx)-- RISC-V LP64D floating-point calling convention classification. ReturnsOption<RiscvFloatClass>. - Field resolution:
resolve_init_field(designator, idx, ctx)andresolve_init_field_idx(...)-- resolve designated initializer field names, including drill-down into anonymous struct/union members. ReturnsInitFieldResolution(Direct or AnonymousMember).field_offset(name, ctx),field_layout(name),field_offset_with_bitfield(...). - Analysis:
has_pointer_fields(ctx)-- recursively checks for pointer fields.
InitFieldResolution enum -- Result of resolving a designated initializer:
Direct(usize) for a field found at a given index, or
AnonymousMember { anon_field_idx, inner_name } when the field is inside an
anonymous struct/union member.
EnumType -- Stores an optional name: Option<String>, variant name/value
pairs, and a is_packed flag. The packed_size() method computes the minimum
integer size needed to hold all variant values (1, 2, 4, or 8 bytes), or
returns 4 for non-packed enums (with a GCC extension to use 8 bytes for
values exceeding 32-bit range).
FunctionType -- Return type, parameter list with optional names, and variadic flag.
ABI classification types:
EightbyteClass-- System V AMD64 ABI classification (NoClass, Sse, Integer) with amerge()method implementing the standard merging rules: NoClass yields to anything, Integer dominates, and Sse merges with itself.RiscvFloatClass-- RISC-V LP64D hardware floating-point calling convention classification. Variants:OneFloat { is_double }-- a single float or double field.TwoFloats { lo_is_double, hi_is_double }-- two float/double fields (two booleans indicating whether each is double).FloatAndInt { float_is_double, float_offset, int_offset, int_size }-- one float/double followed by one integer, with byte offsets and the integer's size.IntAndFloat { float_is_double, int_offset, int_size, float_offset }-- one integer followed by one float/double, with byte offsets and the integer's size.
AddressSpace-- GCC named address space extension for x86 segment-relative addressing:Default,SegGs(__seg_gs),SegFs(__seg_fs).
StructLayoutProvider trait -- Abstraction that lets CType methods compute
sizes and alignments for struct types without depending on the lowering module.
Both TypeContext (in sema) and FxHashMap<String, RcLayout> implement this
trait.
align_up() function -- Aligns an offset up to the next multiple of a given alignment. Used throughout layout computation.
Target helpers -- set_target_ptr_size(), target_ptr_size(),
target_is_32bit(), set_target_long_double_is_f128(),
target_long_double_is_f128(), target_int_ir_type(), widened_op_type().
The widened_op_type() function returns the widened type for integer
arithmetic: on LP64 everything widens to I64, while on i686 most types widen
to I32 but I64/U64 stay at I64 (requiring register pairs). Float types, Void,
and I128/U128 pass through unchanged.
error.rs -- Diagnostic Infrastructure #
A complete diagnostic system that renders GCC-compatible error messages with source snippets, caret highlighting, and ANSI color output.
Severity -- Three levels: Error, Warning, Note.
Diagnostic -- A single diagnostic message with:
- Severity and message text
- Optional
Spanfor source location - Optional
WarningKindfor filtering and-Werrorpromotion - Attached
notesvector (sub-diagnostics providing additional context) - Optional fix-it hint text (rendered below the snippet)
- Optional explicit location string (for preprocessor-phase diagnostics where the SourceManager is not yet available)
Diagnostic builder methods:
- Constructors:
error(msg),warning(msg),warning_with_kind(msg, kind),note(msg). - Chainable modifiers:
with_span(span),with_note(diag),with_fix_hint(text),with_location(file, line, col).
WarningKind -- Enumeration of warning categories matching GCC flag names:
Undeclared, ImplicitFunctionDeclaration, Cpp, ReturnType. Each
variant maps to a -W<name> flag (e.g., -Wimplicit-function-declaration).
Supports -Wall and -Wextra groupings. The Undeclared variant is
retained for CLI flag parsing compatibility but is no longer emitted as a
warning (undeclared variables are now hard errors).
WarningConfig -- Per-warning enabled/disabled and error-promotion state.
Processes CLI flags left-to-right so that later flags override earlier ones:
-Wall -Wno-return-type enables all warnings except return-type. The
process_flag() method handles the full set of supported flags:
-Werror(global promotion of all warnings to errors)-Wno-error(global demotion, clears the-Werrorflag)-Werror=<name>(per-warning promotion, which also implicitly enables it)-Wno-error=<name>(demotion of a specific warning back from error)-Wall(enable the standard warning set)-Wextra(enable additional warnings, superset of-Wall)-W<name>(enable a specific warning by name)-Wno-<name>(disable a specific warning entirely)
ColorMode -- Three modes matching -fdiagnostics-color={auto,always,never}.
The auto mode detects whether stderr is a terminal. Color scheme matches GCC:
bold red for errors, bold magenta for warnings, bold cyan for notes, bold green
for carets and fix-it hints, bold white for file:line:col locations.
DiagnosticEngine -- Central diagnostic collector. Tracks error and warning
counts, holds a WarningConfig and an owned Option<SourceManager>. Emits
diagnostics to stderr with source snippets and include-chain traces (GCC-style
"In file included from X:Y:" headers, with deduplication to avoid repeating the
same chain for consecutive errors in the same included file). The source manager
is attached after preprocessing creates it, so early-phase diagnostics use
explicit location strings instead.
DiagnosticEngine convenience methods:
error(msg, span),warning(msg, span),warning_with_kind(msg, span, kind)-- shorthand methods that construct a Diagnostic and emit it in one call.has_errors(),error_count(),warning_count()-- query counts.take_source_manager()-- transfers ownership of the source manager out (e.g., for passing to codegen for debug info).
Macro expansion traces -- When a diagnostic occurs on a line produced by
macro expansion, the engine calls render_macro_expansion_trace() to emit
"note: in expansion of macro 'X'" messages. The is_uninteresting_macro()
filter suppresses noise from ubiquitous predefined macros (NULL, INT_MAX,
__builtin_*, etc.), showing only user-defined macros likely relevant to the
error. Traces are limited to 3 levels of nesting.
source.rs -- Source Location Tracking #
Maps byte offsets in compiler output back to original source file locations.
Span -- A byte-offset range (start, end, file_id) in source code,
all stored as u32. Compact (12 bytes) and cheap to copy. Constructors:
Span::new(start, end, file_id) and Span::dummy() (zero-valued placeholder
for generated code).
SourceLocation -- Human-readable location: filename, line number, column.
Implements Display as file:line:col.
SourceManager -- Resolves spans to locations. Holds a Vec<SourceFile>
(supporting multiple files via add_file()). Operates in two modes:
- Simple mode: Files registered via
add_file(). Spans are resolved by binary-searching a precomputed line-offset table within the file identified byspan.file_id. - Line-map mode: Preprocessed output with embedded
# linenum "filename"markers. The line map is built bybuild_line_map()and maps byte offsets in the preprocessed stream back to original source files and line numbers.
The source manager also tracks:
- Include chains (
IncludeOrigin): When a# 1 "file.h" 1marker appears (flag 1 = enter-include), it records thatfile.hwas included from the previously active file at a specific line. This enables "In file included from X:Y:" traces in error diagnostics. - Macro expansion info (
MacroExpansionInfo): Records which preprocessed-output lines involved macro expansion, enabling "in expansion of macro 'X'" diagnostic notes. Stored sorted by preprocessed line number for binary search lookup.
Public methods beyond add_file() and resolve_span():
get_content(file_id)-- returns the raw source text for a file.get_source_line(span)-- returns the full source line text containing the span start position (for error snippet display).get_include_chain(filename)-- walks the include origin chain from a file back to the main source file.set_macro_expansions(expansions)-- stores macro expansion metadata collected by the preprocessor.get_macro_expansion_at(span)-- looks up macro expansion info for a span via binary search.build_line_map()-- scans preprocessed output for line markers and builds the mapping tables.
Internally, filenames are deduplicated into a table indexed by u16, so
LineMapEntry structs are compact (12 bytes: pp_offset as u32 +
filename_idx as u16 + 2 bytes padding + orig_line as u32) even for large
translation units.
symbol_table.rs -- Scoped Symbol Table #
A simple, stack-based symbol table for lexical scoping during semantic analysis.
Symbol -- A declared symbol with a name, CType, and optional explicit
alignment (from _Alignas or __attribute__((aligned(N)))). The explicit
alignment is needed because _Alignof(var) must return the declared
alignment per C11 6.2.8p3, not the natural type alignment.
SymbolTable -- A stack of Scope objects, each containing an
FxHashMap<String, Symbol>. Operations:
push_scope()/pop_scope()-- Enter/leave a lexical scope.declare(symbol)-- Insert a symbol into the current (innermost) scope.lookup(name)-- Search scopes from innermost to outermost, returning the first match. This implements C's name shadowing rules.
The table is initialized with a single scope (file scope) and is used by the semantic analysis phase and its constant expression evaluator.
type_builder.rs -- Shared Type Resolution #
Prevents divergence in how AST type syntax is converted to CType between sema and lowering.
TypeConvertContext trait -- A trait with one large default method
(resolve_type_spec_to_ctype) and five required methods. The default method
handles all shared cases:
- 22 primitive C types (Void through ComplexLongDouble)
- Pointer, Array, FunctionPointer, and BareFunction construction (BareFunction
produces
CType::Functiondirectly, not wrapped in a Pointer -- this is used fortypeofon function names) - TypeofType and AutoType
Only four cases differ between sema and lowering and must be implemented by each:
resolve_typedef(name)-- Typedef name resolution (lowering also checks function pointer typedefs for richer type info).resolve_struct_or_union(...)-- Struct/union layout computation (lowering has caching and forward-declaration logic).resolve_enum(...)-- Enum type resolution (sema preservesCType::Enum, lowering collapses toCType::Int).resolve_typeof_expr(expr)-- typeof(expr) evaluation (sema returnsCType::Intas a placeholder; lowering evaluates the full expression type).
A fifth required method, eval_const_expr_as_usize(expr), handles
compile-time evaluation of array dimension expressions.
This design guarantees that the mapping from TypeSpecifier::Int to
CType::Int (and all 21 other primitives) is defined in exactly one place.
build_full_ctype(ctx, type_spec, derived) and its variant
build_full_ctype_with_base(ctx, base, derived) -- Public functions
(~140 lines of shared implementation) that build a complete CType from a
DerivedDeclarator chain. The first resolves the TypeSpecifier to a base
CType, while the second accepts an already-resolved base type (used to
avoid re-resolving anonymous struct type specs which would generate
different anonymous struct keys). Together they are the single canonical
implementation of the C "inside-out" declarator rule, used by both sema
and lowering. They handle:
- Simple pointer and array chains (
int **p,int arr[3][4]) - Function pointer declarators (
int (*fp)(int)) - Arrays of function pointers (
int (*fp[10])(void)) - Nested function pointer return types (
Page *(*fetch)(int)) - Bare function declarators (
int main(int, char**))
The function identifies the function-pointer "core" in the declarator list,
folds prefix pointer declarators into the return type, processes the core and
any trailing declarators, then applies outer array wrappers from the prefix.
Helper functions find_function_pointer_core() and
convert_param_decls_to_ctypes() support the implementation.
const_arith.rs -- Constant Arithmetic Primitives #
Low-level arithmetic functions for compile-time constant evaluation with proper C semantics.
The functions here handle pure arithmetic: given IrConst operands and
width/signedness parameters, they compute the result. Callers (sema and
lowering) determine width and signedness from their own type systems before
calling these shared functions.
Key internal helpers:
wrap_result(v, is_32bit)-- Truncate an i64 to 32-bit width when needed, preserving C truncation semantics (cast to i32, then sign-extend back).unsigned_op(l, r, is_32bit, op)-- Apply an operation in the unsigned domain with correct width handling.bool_to_i64(b)-- Convert boolean to 0/1.
Key evaluators:
eval_const_binop(op, lhs, rhs, is_32bit, is_unsigned, lhs_unsigned, rhs_unsigned)-- (public) Top-level dispatch entry point for all constant binary evaluation. Dispatches to float, i128, or i64 paths based on operand types.eval_const_binop_int(op, l, r, is_32bit, is_unsigned)-- (module-private) Integer binary operations (add, sub, mul, div, mod, shifts, bitwise, comparisons) with wrapping, division-by-zero checking, and proper width truncation.eval_const_binop_float(op, lhs, rhs)-- (public) Floating-point binary operations on&IrConstparameters. Handles F32, F64, and LongDouble formats: for LongDouble, uses full-precision f128 software arithmetic or x87 80-bit software arithmetic depending on the target. For F32/F64, uses native Rust arithmetic. Comparison and logical operations always returnIrConst::I64.eval_const_binop_i128(op, lhs, rhs, ...)-- (module-private) Native i128 arithmetic for__int128operations.negate_const(val)-- (public) Unary negation with sub-int promotion. For LongDouble, negates by flipping the sign bit in the f128 bytes directly, preserving full 112-bit precision.bitnot_const(val)-- (public) Bitwise NOT with sub-int promotion to i32.is_zero_expr(expr)-- (public) Detects zero-literal expressions (0 or cast of 0), used for offsetof pattern detection (&((type*)0)->member).is_null_pointer_constant(expr)-- (public) Checks whether an expression is a null pointer constant per C11 6.3.2.3p3 (integer literal 0, or a(void*)cast of an integer constant expression that evaluates to zero). Used by sema and lowering for ternary operator type checking.truncate_and_extend_bits(bits, target_width, target_signed)-- (public) Raw bit truncation to a target width and optional sign-extension back to 64 bits, used for evaluating cast chains at compile time.
const_eval.rs -- Shared Constant Expression Evaluation #
Higher-level constant evaluation logic extracted from the near-identical
implementations that previously existed in both sema::const_eval and
ir::lowering::const_eval.
The functions are parameterized by closures that abstract over the sema-vs-lowering differences, allowing both callers to share the same evaluation logic for:
-
Literal evaluation (
eval_literal) -- ConvertsExpr::IntLiteral,Expr::FloatLiteral,Expr::CharLiteral, and all other literal kinds toIrConstvalues.IntLiteralproducesIrConst::I32when the value fits in 32 bits, otherwiseIrConst::I64. Other integer literal kinds (LongLiteral,LongLongLiteral,UIntLiteral,ULongLiteral,ULongLongLiteral) always produceIrConst::I64. Char literals are sign-extended fromsigned chartoint, matching GCC behavior. Long double literals produceIrConst::long_double_with_byteswith full-precision byte storage. -
Builtin constant folding (
eval_builtin_call) -- Evaluates__builtin_bswap,__builtin_clz,__builtin_ctz,__builtin_popcount,__builtin_ffs,__builtin_parity,__builtin_clrsb, and similar builtins at compile time when their arguments are constants. Also handles__builtin_nan/__builtin_inf/__builtin_huge_valfamilies for float/double/long-double NaN and infinity constants. Thelsuffix variants (e.g.,__builtin_clzl) are target-aware, using 32-bit or 64-bit operations based ontarget_is_32bit(). -
Sub-int promotion (
promote_sub_int) -- Handles unary operations on types narrower thanint(I8/I16), applying C integer promotion rules with correct zero-extension for unsigned types. -
IrConst-to-bits conversion (
irconst_to_bits) -- Converts an IrConst to raw u64 bits for bit-level operations. -
Binary operations with type parameters (
eval_binop_with_types) -- Wrapsconst_arith::eval_const_binopwith C usual arithmetic conversion logic (C11 6.3.1.8). For shifts, only the LHS type determines the result type (C11 6.5.7); for other ops, the wider of both types is used.
Functions that require caller-specific state (global address resolution, sizeof/alignof, binary operations with type inference) remain in the respective callers. See the final section for the full explanation of how this eliminates duplication.
long_double.rs -- Long Double Precision Support #
Full-precision support for the two long double formats used across
target architectures:
| Architecture | Format | Storage | Exponent Bias |
|---|---|---|---|
| x86-64, i686 | x87 80-bit extended | 16 bytes (6 padding) | 16383 |
| AArch64, RISC-V | IEEE 754 binary128 | 16 bytes | 16383 |
Both formats use 15-bit exponent fields with bias 16383. The key structural difference is that x87 has an explicit integer bit in the mantissa (64 bits total significand), while binary128 uses an implicit leading 1 with 112 stored mantissa bits.
Parsing:
parse_long_double_to_f128_bytes(text)-- Parses a decimal or hex float string to IEEE binary128 bytes. Handles infinity, NaN, hex floats with binary exponents, and decimal floats via a big-integer algorithm that preserves all 112 bits of mantissa precision. Uses a pure-RustBigUintimplementation with no external dependencies.
Internal preprocessing (preparse_long_double) strips suffixes, detects
signs, and classifies the input as hex, infinity, NaN, or decimal, shared
by both x87 and f128 parsing paths.
Format conversion:
x87_bytes_to_f128_bytes(x87)-- Convert x87 80-bit to IEEE binary128.x87_bytes_to_f64(bytes)-- Convert x87 80-bit to f64.x87_to_f64(bytes)-- Convenience alias forx87_bytes_to_f64.f128_bytes_to_x87_bytes(f128_bytes)-- Convert IEEE binary128 to x87.f128_bytes_to_f64(f128_bytes)-- Convert IEEE binary128 to f64.f64_to_x87_bytes_simple(val)-- Convert f64 to x87 bytes.f64_to_f128_bytes_lossless(val)-- Convert f64 to IEEE binary128 losslessly.
Integer-to-float conversion:
i64_to_f128_bytes(val),u64_to_f128_bytes(val)-- Signed/unsigned 64-bit integer to binary128.i128_to_f128_bytes(val),u128_to_f128_bytes(val)-- Signed/unsigned 128-bit integer to binary128.
Float-to-integer conversion (all return Option, None on overflow):
f128_bytes_to_i64(bytes),f128_bytes_to_u64(bytes)-- Binary128 to 64-bit integer.f128_bytes_to_i128(bytes),f128_bytes_to_u128(bytes)-- Binary128 to 128-bit integer.
Note that all float-to-integer conversions operate on f128 bytes. Code working
with x87 values should first convert to f128 via x87_bytes_to_f128_bytes,
then call the appropriate f128_bytes_to_* function.
x87 arithmetic (pure software): All x87 operations are implemented in pure Rust without inline assembly, producing bit-identical results to x87 FPU hardware:
x87_add(a, b),x87_sub(a, b),x87_mul(a, b),x87_div(a, b)-- Full 64-bit mantissa precision arithmetic using_softimplementations.x87_rem(a, b)-- Software remainder withfpremsemantics.x87_neg(a)-- Negates by flipping the sign bit (result[9] ^= 0x80).x87_cmp(a, b)-- Software comparison returning: -1 if a < b, 0 if equal, 1 if a > b, ori32::MINfor unordered (NaN) comparisons.
Because all implementations are pure software, no cross-compilation fallbacks are needed -- the same code runs identically on any host.
f128 arithmetic (pure Rust software implementation): For AArch64/RISC-V targets (or cross-compilation on non-x86 hosts), all binary128 operations are implemented in pure Rust without inline assembly:
f128_add(a, b),f128_sub(a, b),f128_mul(a, b),f128_div(a, b)-- Full 112-bit mantissa precision with correct rounding.f128_rem(a, b)-- Falls back to a lossy f64 approximation for the remainder computation. This is acceptable because remainder is rarely used in long double constant folding; the special cases (NaN, infinity, zero) are handled correctly.f128_cmp(a, b)-- Returns -1, 0, 1, ori32::MINfor unordered (NaN).
These decompose the 128-bit representation into sign, exponent, and mantissa
components using shared f128_decompose helpers, then perform the operation
with correct rounding.
The constant folding optimization pass uses these arithmetic functions to
evaluate long double expressions at compile time without precision loss. The
choice between x87 and f128 functions is determined by the
target_long_double_is_f128() flag.
encoding.rs -- Non-UTF-8 Source File Handling #
C source files may contain non-UTF-8 bytes in string and character literals (e.g., EUC-JP, Latin-1 encoded files). Since Rust strings must be valid UTF-8, this module provides a round-trip encoding scheme using Unicode Private Use Area (PUA) code points.
Encoding scheme: Byte 0x80+n maps to code point U+E080+n (PUA range
U+E080..U+E0FF).
Public API:
bytes_to_string(bytes)-- Converts raw file bytes to a valid UTF-8 String. If the input is already valid UTF-8, it is returned as-is (zero-copy fast path). Otherwise, bytes are processed one at a time: valid UTF-8 multi-byte sequences are preserved, and invalid bytes0x80-0xFFare encoded as PUA code points. A UTF-8 BOM at the start of the file is stripped, matching GCC/Clang behavior.decode_pua_byte(input: &[u8], pos: usize) -> (u8, usize)-- Recovers the original byte from a PUA-encoded UTF-8 sequence in a byte slice. Returns the decoded byte and the number of input bytes consumed. Used by the lexer when processing string/character literals to emit the correct raw bytes into the compiled output.
asm_constraints.rs -- Inline Assembly Constraint Classification #
A single function shared by three consumers: the inline pass (symbol resolution after inlining), cfg_simplify (dead block detection), and the backend (operand emission).
constraint_is_immediate_only(constraint) -- Returns true for
constraints that accept only compile-time constants ("i", "I", "n",
"N", "e", "E", and x86-specific letters like "K", "M", "G",
"H", "J", "L", "O"). Returns false for multi-alternative
constraints that also accept registers or memory ("ri", "g", "Ir",
etc.).
The function strips output/early-clobber/commutative modifiers (=, +,
&, %) before classification, and rejects named operand references
([name]). It checks that at least one immediate-class letter is present,
and that no GP register-class letters (r, q, R, l), FP/SIMD
register-class letters (x, v, Y), specific register letters (a, b,
c, d, S, D), memory-class letters (m, o, V, p, Q), or
general-class letters (g) appear. Numeric digit references (operand reuse
constraints) also cause a false return.
fx_hash.rs -- Fast Non-Cryptographic Hash #
A copy of the FxHash algorithm used by the Rust compiler (rustc). Replaces
the default SipHash in HashMap/HashSet with a much faster hash for
compiler workloads where DoS resistance is unnecessary.
Type aliases:
FxHashMap<K, V>--HashMapwithFxHasher.FxHashSet<V>--HashSetwithFxHasher.
The FxHasher struct implements std::hash::Hasher. The hash function uses
rotate-left-5-XOR followed by multiply-by-0x517cc1b727220a95. It is used
throughout the entire compiler for all hash maps and hash sets.
temp_files.rs -- RAII Temporary File Handling #
Provides safe temporary file management with automatic cleanup.
temp_dir() -- Returns the platform temp directory, respecting $TMPDIR
on Unix (falling back to /tmp).
make_temp_path(prefix, stem, extension) -- Generates a unique temp file
path. The filename includes the process ID and an atomic counter to avoid
collisions in parallel builds and multi-file compilations. Format:
{prefix}_{pid}_{stem}.{counter}.{extension}.
TempFile -- An RAII guard that deletes the file when dropped. Ensures
cleanup happens even on early returns, panics, or errors. Has a keep flag
for debugging that prevents deletion on drop. Used by the driver when invoking
external assemblers and by the backend for intermediate object files.
Eliminating Duplication: The const_eval / const_arith Pattern #
Compile-time constant expression evaluation is needed in two distinct phases:
-
Semantic analysis (sema) -- Evaluates constant expressions for
_Static_assert, array dimensions, enum values, andcaselabels. Works withCTypeto determine operand width and signedness. -
IR lowering -- Evaluates constant expressions for global variable initializers, constant folding during lowering, and static address computation. Works with
IrType.
Before extraction, both phases contained near-identical implementations of
constant binary operations, literal evaluation, and builtin folding. The
common module eliminates this duplication with a two-layer design:
sema::const_eval ir::lowering::const_eval
| |
| (determines width/signedness | (determines width/signedness
| from CType) | from IrType)
| |
+---------> common::const_eval <--+
|
| (pure evaluation logic,
| parameterized by closures)
|
common::const_arith
|
| (pure arithmetic: wrapping,
| unsigned ops, width truncation)
|
common::long_double
|
| (f128/x87 arithmetic for
| long double constants)
const_arith is the bottom layer. Its functions like
eval_const_binop_int(op, l, r, is_32bit, is_unsigned) take explicit
width/signedness flags and return IrConst results. The callers compute
is_32bit and is_unsigned from their own type systems:
- Sema:
(ctype_size <= 4, ctype.is_unsigned()) - Lowering:
(ir_type.size() <= 4, ir_type.is_unsigned())
const_eval is the upper layer. Its functions like eval_literal(expr) and
builtin evaluation take AST expressions and return IrConst values directly.
Functions that need type information are parameterized by closures, so sema
passes CType-based logic and lowering passes IrType-based logic.
This structure means that:
- Adding a new integer literal kind requires changing only
eval_literal. - Adding a new binary operator requires changing only
eval_const_binop_int. - Adding a new builtin requires changing only the shared builtin evaluator.
- The two callers remain thin wrappers that supply type resolution and delegate to the shared layer.
Functions that inherently require phase-specific context -- global address resolution (lowering only), sizeof/alignof evaluation (different symbol tables), and type inference for binary operations -- remain in their respective callers. The boundary is drawn precisely at the point where the logic diverges.