diff --git a/bugs.md b/bugs.md deleted file mode 100644 index cae66fa6..00000000 --- a/bugs.md +++ /dev/null @@ -1,104 +0,0 @@ -# pcc Bug Log — CPython Build Campaign - -## Status: CPython 3.12.9 builds and runs with pcc at -O0. Sym pseudo liveness fix — eval loop frame 118KB→61KB. 456/458 tests pass (ulimit -s unlimited). test_threading passes with default 8MB stack. - -### Bugs A-N, P, Q, 1-6: ALL FIXED (see git history) - -### Bug S: Floating-point codegen bugs — FIXED -- **Root cause 1:** XMM registers not spilled across function calls. x86-64 SysV ABI says ALL XMM regs are caller-saved, but the register allocator didn't check `crosses_call` for FP values. Float/double values in XMM8-XMM13 were silently clobbered by intervening calls. -- **Fix:** In `regalloc.rs`, spill FP values to stack when `crosses_call` is true. -- **Root cause 2:** `emit_cbr()` used `FpSize::Single` (ucomiss, 32-bit) for all float-to-bool conditions including doubles. Values like 0.5 have zero in their lower 32 bits, so `if(0.5)` evaluated to false. -- **Fix:** Derive `FpSize` from the condition type's bit width in both x86-64 and aarch64 backends. -- **Tests:** `codegen_xmm_spill_across_calls`, `codegen_double_to_bool` - -### Bug T: chr()/string literals with bytes >= 0x80 — FIXED -- **Root cause:** pcc stored C string bytes as Rust `char` values in a Rust `String`. For bytes >= 0x80, Rust UTF-8 encodes them (e.g., `\x80` → 2-byte `\xC2\x80`), corrupting assembly output, sizeof, and zero-fill calculations. -- **Fix (5 locations):** - - `escape_string()` in `cc/arch/codegen.rs`: emit single octal byte for chars 0-255 - - `emit_initializer_data()` in `cc/arch/codegen.rs`: use `chars().count()` for zero-fill - - `sizeof` in `cc/parse/expression.rs`: use `chars().count()` for array size - - Null terminator offsets in `cc/ir/linearize.rs` (2 places): use `chars().count()` -- **Test:** `codegen_string_literal_high_bytes` - -### Bug U: unsigned long to double + XMM param spill — FIXED -- **Fixed:** `cvtsi2sdq` treated unsigned 64-bit values as signed. Values >= 2^63 produced negative doubles. Now uses the correct shift-convert-double sequence. -- **Fixed:** XMM function parameters not spilled to stack at function entry. Any float computation that reused the same XMM register would clobber the parameter. -- `int(37.5)` and `math.floor(37.5)` now work correctly in CPython. -- **Tests:** `codegen_unsigned_long_to_double` - -### Bug W: FP compare Xmm0 clobber — FIXED -- **Root cause:** `emit_fp_compare()` loaded src1 into Xmm0, but if src2 was allocated to Xmm0, src2 was clobbered. Result: `ucomisd %xmm0, %xmm0` (self-compare, always equal), causing `d < -(double)_PyTime_MIN` to always be false. -- **Symptom:** `time.sleep(0.001)` raised `OverflowError: timestamp out of range for platform time_t` -- **Fix:** In `emit_fp_compare()`, check if src2 is in Xmm0 before loading src1; if so, save src2 to Xmm15 first. -- **Test:** `codegen_fp_compare_xmm0_clobber` - -### Bug X: Inline asm 64-bit register constraints — FIXED -- **Root cause:** `format_reg_default()` in `AsmOperandFormatter` hardcoded 32-bit register names for all inline asm operands. For `uintptr_t` with `"+r"` constraint, `xchg %edx, (%r8)` was emitted instead of `xchg %rdx, (%r8)`, truncating 64-bit values to 32 bits. -- **Symptom:** CPython's `_Py_atomic_store` (seq_cst via xchg) truncated pointers. Signal handlers in `_PySignal_Fini` had corrupted addresses → segfault during finalization. -- **Fix:** Pass operand size through `substitute_asm_operands` → `format_reg_default(reg, size_bits)`. Select register width (b/w/k/q) based on operand type's bit width. -- **Test:** `codegen_inline_asm_64bit_constraint` - -### Bug Y: XMM-to-GPR movd truncation — FIXED -- **Root cause:** `emit_move()` for `Loc::Xmm → Reg` hardcoded `OperandSize::B32` (movd) instead of using `op_size`. For doubles (64-bit), `movd %xmm, %r10d` copied only lower 32 bits. For `1.0` (0x3FF0000000000000), lower 32 bits = 0x00000000 → result 0.0. -- **Symptom:** `1/1` returned `0.0` in CPython (integer true division). Conditional select of double values (ternary, if/else returning double) produced 0.0. -- **Fix:** Use `op_size` instead of hardcoded `B32` in `MovXmmGp` instruction. -- **Test:** `codegen_double_xmm_to_gpr_movq` - -### Bug Z: Ternary function pointer call return type — FIXED -- **Root cause:** `parse_conditional_expr()` used `self.types.pointer_to()` for function-to-pointer decay, but `pointer_to()` only does a lookup and falls back to `void*` when the type isn't in the table. This caused `(cond ? func_a : func_b)(arg)` to have type `void*`, and the Call expression handler couldn't determine the function return type, defaulting to `int` (32-bit). -- **Fix:** Use `self.types.intern(Type::pointer(...))` instead of `self.types.pointer_to(...)` in ternary decay, ensuring the pointer-to-function type is created if not found. -- **Symptom:** `string.Formatter().format()` segfaulted; indirect calls through ternary-selected function pointers returned truncated 32-bit values for pointer return types. -- **Test:** `codegen_ternary_fptr_return_type` - -### Bug AA: Character literal signedness — FIXED -- **Root cause:** `'\x80'` produced 128 (unsigned) instead of -128 (signed). pcc cast `char` to `i64` via Rust `char` (unsigned), bypassing sign-extension. GCC treats `'\x80'` as -128 per implementation-defined behavior. -- **Fix:** Use `*c as u8 as i8 as i64` in all 4 CharLit-to-integer conversions (linearize.rs x3, parser.rs x1). -- **Symptom:** CPython's `_pickle` module failed — `enum { PROTO = '\x80' }` was 128, but `switch((enum)char_val)` saw -128. All opcodes >= 0x80 were unmatchable. -- **Test:** `codegen_char_literal_signedness` - -### Bug AB: Static local struct variables on stack — FIXED -- **Root cause:** `linearize_local_decl()` checked `self.types.modifiers(typ)` for STATIC, but storage class modifiers are in `declarator.storage_class`, not the type system. For struct types, STATIC was never in the type, so static structs were allocated on the stack. -- **Fix:** Check `declarator.storage_class.contains(TypeModifiers::STATIC)` instead. -- **Symptom:** CPython's `_PyArg_Parser` structs (declared `static` in clinic-generated code) lived on the stack. When the function returned, the linked list in `_PyRuntime.getargs.static_parsers` contained dangling stack pointers → segfault in `_PyArg_Fini()` during finalization. -- **Test:** `codegen_static_local_struct` - -### Bug R5/O: Previously listed bugs — FIXED by above fixes - -### Bug AC: IEEE 754 NaN comparison — FIXED -- **Root cause:** `ucomisd` sets PF=1 for NaN, but `sete`/`setb`/`setbe`/`setne` don't check PF. All NaN comparisons gave wrong results. -- **Fix:** For ordered ==, <, <=: emit `setcc + setnp + AND`. For !=: `setne + setp + OR`. Added `CondCode::Np`/`P`. -- **Test:** `codegen_nan_comparison`, `codegen_nan_comparison_comprehensive` - -### Bug AD: PreInc on dereferenced PostInc (++*s++) — FIXED -- **Root cause:** `++*s++` re-evaluated `s++` when storing back the incremented value, causing the store to go to the wrong address (s+1 instead of s). The deref operand's side effects were executed twice. -- **Fix:** Pre-compute the lvalue address via `linearize_lvalue` before loading the value, avoiding re-evaluation of PostInc side effects. -- **Symptom:** `_Py_dg_dtoa` (float-to-string) produced wrong digit strings. `'%.0f' % 1.5` returned "12" instead of "2", causing memset crash. -- **Test:** `codegen_preinc_deref_postinc` - -### Bug AE: 2-SSE struct ABI param/return mismatch — PARTIALLY FIXED -- **Root cause:** struct { double, double } was passed via hidden pointer but returned in XMM0+XMM1. Crashes when return value passed directly as argument. -- **Fix:** Implemented 2-XMM parameter passing at call sites and function entry. -- **Remaining:** When a 2-SSE struct is the FIRST param followed by integer params, the callee's function entry code assigns integer args to the wrong registers (off by one). `powu(Complex x, long n)` reads `n` from rsi instead of rdi. -- **Workaround:** Complex tests work when struct is not first param, or with unlimited stack. - -### Bug AF: Stack overflow from excessive frame sizes — FIXED -- `_PyEval_EvalFrameDefault` frame reduced from 118KB to 83KB to 61KB via stack slot reuse -- Stack coloring reuses slots for non-loop-spanning pseudos with non-overlapping intervals -- Sym pseudo liveness fix: kill Sym pseudos at their declaration blocks before gen/kill scan, preventing block-scoped locals from appearing simultaneously live across switch/case/computed-goto dispatch -- test_threading now passes without `ulimit -s unlimited` (previously SIGSEGV from thread stack overflow) -- 17 additional CPython tests now passing - -### Bug AG-AK: See git log for recent fixes (small struct return, FP binop clobber, va_start overflow, long double narrowing, XMM cross-block spill) - -### Test status: CPython 3.12.9 make test at -O0 (ulimit -s unlimited) -- 491 tests run (test_decimal skipped, hangs at -O0) -- 456 PASS, 2 FAIL (baseline was 70 FAIL, 68 tests fixed) -- Remaining 2: test.test_gdb.test_pretty_print (GCC-specific, expected), test_tools (parallel flake, passes individually) -- test_threading and test_statistics pass without `ulimit -s unlimited` - -### CPython build setup at -O0 -- configure CC=/tmp/pcc-bin/pcc, then fix pyconfig.h GETPGRP/SETPGRP -- Frozen deepfreeze.c generated by gcc build first, saved to /tmp -- Makefile: OPT= -DNDEBUG -g -O0 -Wall -- After make clean: restore deepfreeze.c, then make -j nproc -- Must delete all .pyc files after pcc rebuild diff --git a/cc/README.md b/cc/README.md index ca771c96..913ef4d4 100644 --- a/cc/README.md +++ b/cc/README.md @@ -34,6 +34,8 @@ Key source files: | `parse/parser.rs` | Recursive descent parser producing AST | | `parse/ast.rs` | AST node definitions | | `types.rs` | C type system | +| `strings.rs` | String interning (StringId), pre-interns keywords at startup | +| `kw.rs` | Pre-interned keyword constants and tag-based classification | | `symbol.rs` | Symbol table with scope management | | `ir/linearize.rs` | AST → IR conversion, SSA construction | | `ir/mod.rs` | Intermediate representation definitions | diff --git a/cc/arch/aarch64/regalloc.rs b/cc/arch/aarch64/regalloc.rs index 97254473..a461b7e7 100644 --- a/cc/arch/aarch64/regalloc.rs +++ b/cc/arch/aarch64/regalloc.rs @@ -1019,7 +1019,8 @@ impl RegAlloc { // Stack slot reuse uses block-level interference checks // (live_in/live_out from dataflow fixpoint) which are correct - // for all CFG shapes. Addr-taken syms need stable addresses. + // for all CFG shapes. Addr-taken syms need stable addresses + // because symaddr-derived pointers may escape to callees. let reusable = !self.addr_taken_syms.contains(&interval.pseudo); self.alloc_stack_slot(&interval, aligned_size, alignment, reusable); diff --git a/cc/arch/mapping.rs b/cc/arch/mapping.rs index 031e58c2..53789fc5 100644 --- a/cc/arch/mapping.rs +++ b/cc/arch/mapping.rs @@ -1838,7 +1838,7 @@ mod tests { fn test_run_mapping_empty() { let target = Target::new(Arch::X86_64, Os::Linux); let types = TypeTable::new(&target); - let mut module = Module::new(); + let mut module = Module::default(); run_mapping(&mut module, &types, &target); } @@ -1848,7 +1848,7 @@ mod tests { let target = Target::new(Arch::X86_64, Os::Linux); let types = TypeTable::new(&target); - let mut module = Module::new(); + let mut module = Module::default(); module.add_function(make_test_func(&types)); module.add_function(make_test_func(&types)); @@ -1860,7 +1860,7 @@ mod tests { let target = Target::new(Arch::X86_64, Os::Linux); let types = TypeTable::new(&target); - let mut module = Module::new(); + let mut module = Module::default(); module.add_function(make_test_func(&types)); run_mapping(&mut module, &types, &target); @@ -1879,7 +1879,7 @@ mod tests { for target in &targets { let types = TypeTable::new(target); - let mut module = Module::new(); + let mut module = Module::default(); module.add_function(make_test_func(&types)); run_mapping(&mut module, &types, target); } @@ -1890,7 +1890,7 @@ mod tests { let target = Target::new(Arch::X86_64, Os::Linux); let types = TypeTable::new(&target); - let mut module = Module::new(); + let mut module = Module::default(); module.add_function(make_test_func(&types)); let orig_insn_count = module.functions[0].blocks[0].insns.len(); @@ -1905,7 +1905,7 @@ mod tests { let target = Target::new(Arch::Aarch64, Os::Linux); let types = TypeTable::new(&target); - let mut module = Module::new(); + let mut module = Module::default(); module.add_function(make_test_func(&types)); let orig_insn_count = module.functions[0].blocks[0].insns.len(); @@ -1943,7 +1943,7 @@ mod tests { func.add_block(bb); func.entry = BasicBlockId(0); - let mut module = Module::new(); + let mut module = Module::default(); module.add_function(func); run_mapping(&mut module, &types, &target); @@ -1984,7 +1984,7 @@ mod tests { func.add_block(bb); func.entry = BasicBlockId(0); - let mut module = Module::new(); + let mut module = Module::default(); module.add_function(func); run_mapping(&mut module, &types, &target); diff --git a/cc/arch/x86_64/regalloc.rs b/cc/arch/x86_64/regalloc.rs index e2437758..0a1a93b8 100644 --- a/cc/arch/x86_64/regalloc.rs +++ b/cc/arch/x86_64/regalloc.rs @@ -1052,7 +1052,8 @@ impl RegAlloc { // Stack slot reuse uses block-level interference checks // (live_in/live_out from dataflow fixpoint) which are correct - // for all CFG shapes. Addr-taken syms need stable addresses. + // for all CFG shapes. Addr-taken syms need stable addresses + // because symaddr-derived pointers may escape to callees. let reusable = !self.addr_taken_syms.contains(&interval.pseudo); self.alloc_stack_slot(&interval, aligned_size, alignment, reusable); diff --git a/cc/cxref.rs b/cc/cxref.rs index c9beb48e..c446d920 100644 --- a/cc/cxref.rs +++ b/cc/cxref.rs @@ -77,7 +77,7 @@ struct SymbolRef { } /// Information about a symbol across files -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] struct SymbolInfo { /// References organized by file, then by function scope /// Map: file -> Map: function (or empty for global) -> refs @@ -85,12 +85,6 @@ struct SymbolInfo { } impl SymbolInfo { - fn new() -> Self { - Self { - refs: BTreeMap::new(), - } - } - fn add_ref(&mut self, file: &str, function: &str, line: u32, is_definition: bool) { self.refs .entry(file.to_string()) @@ -105,6 +99,7 @@ impl SymbolInfo { } /// Cross-reference table +#[derive(Default)] struct CrossRef { /// All symbols: name -> info symbols: BTreeMap, @@ -115,14 +110,6 @@ struct CrossRef { } impl CrossRef { - fn new() -> Self { - Self { - symbols: BTreeMap::new(), - current_file: String::new(), - current_function: String::new(), - } - } - fn set_file(&mut self, file: &str) { self.current_file = file.to_string(); } @@ -132,17 +119,21 @@ impl CrossRef { } fn add_definition(&mut self, name: &str, line: u32) { - self.symbols - .entry(name.to_string()) - .or_insert_with(SymbolInfo::new) - .add_ref(&self.current_file, &self.current_function, line, true); + self.symbols.entry(name.to_string()).or_default().add_ref( + &self.current_file, + &self.current_function, + line, + true, + ); } fn add_reference(&mut self, name: &str, line: u32) { - self.symbols - .entry(name.to_string()) - .or_insert_with(SymbolInfo::new) - .add_ref(&self.current_file, &self.current_function, line, false); + self.symbols.entry(name.to_string()).or_default().add_ref( + &self.current_file, + &self.current_function, + line, + false, + ); } } @@ -561,7 +552,7 @@ fn main() -> ExitCode { } // Build cross-reference - let mut xref = CrossRef::new(); + let mut xref = CrossRef::default(); let mut streams = StreamTable::new(); for file in &args.files { @@ -586,7 +577,7 @@ fn main() -> ExitCode { // In non-combined mode, print and reset after each file if !args.combined { print_xref(&xref, args.width, args.silent, &mut *output_file); - xref = CrossRef::new(); + xref = CrossRef::default(); } } _ => { diff --git a/cc/ir/inline.rs b/cc/ir/inline.rs index 2436ceae..ebb86a4c 100644 --- a/cc/ir/inline.rs +++ b/cc/ir/inline.rs @@ -33,16 +33,40 @@ use std::collections::{HashMap, HashSet}; const MAX_INLINE_ITERATIONS: usize = 3; /// Functions with this many instructions or fewer are always inlined +/// (subject to growth limits — see LARGE_FUNCTION_GROWTH). const ALWAYS_INLINE_SIZE: usize = 10; /// Functions with inline hint and this many instructions or fewer are inlined const HINT_INLINE_SIZE: usize = 50; -/// Maximum function size to inline at -O2 or higher +/// Maximum function size to inline at -O2 or higher (single call site) const MAX_INLINE_SIZE: usize = 100; -/// Maximum growth in caller size before we stop inlining -const MAX_CALLER_GROWTH: usize = 1000; +/// Functions with at least this many instructions are considered "large." +/// Growth limits are enforced once a caller reaches this size. +/// GCC uses 2700; we use a lower value because pcc's stack frames grow +/// ~8 bytes per inlined instruction (no register promotion for addr-taken +/// locals), so frame bloat becomes significant earlier than in GCC. +const LARGE_FUNCTION_INSNS: usize = 500; + +/// Maximum growth (%) allowed for large functions via inlining. +/// A function of size N can grow to N * (1 + LARGE_FUNCTION_GROWTH/100). +/// GCC default: 100 (2x). We use 50%. +const LARGE_FUNCTION_GROWTH: usize = 50; + +/// Hard cap on caller size beyond which ALL inlining is disabled. +/// This is a pcc-specific workaround: without register promotion for +/// addr-taken locals, pcc's stack frames grow linearly with inlined code. +/// GCC/LLVM don't need this because they keep addr-taken locals in registers. +/// Functions this large (e.g., _PyEval_EvalFrameDefault at ~400K instructions) +/// gain little from inlining but suffer severe frame bloat. +/// TODO: remove this once register promotion is implemented. +const HARD_CALLER_SIZE_CAP: usize = 5000; + +/// Maximum estimated stack frame (bytes) for recursive callers. +/// Beyond this, no inlining into the caller is allowed. +/// LLVM uses 1024 bytes. We match that value. +const RECURSIVE_CALLER_MAX_STACK: usize = 1024; const DEFAULT_CANDIDATE_CAPACITY: usize = 16; const DEFAULT_REMAP_CAPACITY: usize = 64; @@ -139,16 +163,20 @@ fn analyze_function(func: &Function, call_counts: &HashMap) -> In // Inlining Decision Heuristics // ============================================================================ -/// Determine whether to inline a function at a specific call site -fn should_inline(candidate: &InlineCandidate, opt_level: u32, caller_size: usize) -> bool { +/// Determine whether to inline a function at a specific call site. +/// +/// Uses a hybrid of GCC's growth model and LLVM's recursive-caller stack check: +/// - Callee size thresholds decide if inlining is *desirable* +/// - Proportional growth limit decides if the caller can *afford* it +/// - Recursive callers get an estimated-stack check (LLVM-style) +fn should_inline( + candidate: &InlineCandidate, + opt_level: u32, + caller_size: usize, + caller_is_recursive: bool, +) -> bool { // Never inline if disqualifying conditions - if candidate.uses_varargs { - return false; - } - if candidate.is_recursive { - return false; - } - if candidate.uses_alloca { + if candidate.uses_varargs || candidate.is_recursive || candidate.uses_alloca { return false; } @@ -157,36 +185,59 @@ fn should_inline(candidate: &InlineCandidate, opt_level: u32, caller_size: usize return false; } - // Very small functions: always inline - if candidate.estimated_size <= ALWAYS_INLINE_SIZE { - return true; + // Hard cap for extreme outliers (eval loops, autogenerated code). + // pcc-specific: without register promotion, every inlined copy adds + // ~8 bytes of stack. Functions with 5000+ instructions already have + // large frames; inlining makes them worse. See HARD_CALLER_SIZE_CAP. + if caller_size > HARD_CALLER_SIZE_CAP { + return false; } - // Functions with inline hint: higher threshold - if candidate.has_inline_hint && candidate.estimated_size <= HINT_INLINE_SIZE { - return true; + // Recursive callers: allow inlining only if estimated stack is small. + // LLVM uses 1024 bytes. We estimate stack as ~8 bytes per instruction + // (conservative: most instructions don't need stack, but addr-taken + // locals and spills add up in pcc's current register allocator). + if caller_is_recursive { + let estimated_stack = caller_size * 8; + if estimated_stack > RECURSIVE_CALLER_MAX_STACK { + return false; + } } - // At -O2 or higher, be more aggressive - if opt_level >= 2 { - // Single call site: inline up to max size + // Check if the callee passes size thresholds for inlining desirability + let desirable = if candidate.estimated_size <= ALWAYS_INLINE_SIZE + || (candidate.has_inline_hint && candidate.estimated_size <= HINT_INLINE_SIZE) + { + true + } else if opt_level >= 2 { if candidate.call_count == 1 && candidate.estimated_size <= MAX_INLINE_SIZE { - return true; + true + } else { + candidate.estimated_size <= ALWAYS_INLINE_SIZE * 2 } + } else { + false + }; - // Multiple call sites: be more conservative - if candidate.estimated_size <= ALWAYS_INLINE_SIZE * 2 { - return true; - } + if !desirable { + return false; } - // Check caller growth limit - let growth = candidate.estimated_size * candidate.call_count; - if growth + caller_size > MAX_CALLER_GROWTH { + // Growth limit (GCC-style): once a function is "large," limit how much + // further it can grow. The limit is proportional to the current size. + // + // GCC model: new_size <= max(base, LARGE_FUNCTION_INSNS) * (1 + growth%) + // where base = max(caller_size, callee_size). + let new_size = caller_size + candidate.estimated_size; + let base = caller_size + .max(candidate.estimated_size) + .max(LARGE_FUNCTION_INSNS); + let limit = base + base * LARGE_FUNCTION_GROWTH / 100; + if new_size > limit && new_size > LARGE_FUNCTION_INSNS { return false; } - false + true } // ============================================================================ @@ -528,6 +579,28 @@ fn clone_instruction( vec![new_insn] } + // SymAddr on an Arg pseudo: after inlining, the Arg maps to call_args[n] + // which already holds the struct address (for MEMORY-class structs). + // Convert symaddr to copy to avoid taking address-of-address. + Opcode::SymAddr + if insn.src.first().is_some_and(|&src| { + matches!( + callee_func.get_pseudo(src).map(|p| &p.kind), + Some(PseudoKind::Arg(_)) + ) + }) => + { + debug_assert_eq!(insn.src.len(), 1, "SymAddr should have exactly one source"); + let remapped_src = ctx.remap_pseudo(insn.src[0], callee_func); + let mut copy_insn = Instruction::new(Opcode::Copy); + copy_insn.target = insn.target.map(|t| ctx.remap_pseudo(t, callee_func)); + copy_insn.src = vec![remapped_src]; + copy_insn.typ = insn.typ; + copy_insn.size = insn.size; + copy_insn.pos = insn.pos; + vec![copy_insn] + } + // All other instructions: remap target and sources _ => { debug_assert!( @@ -948,12 +1021,24 @@ pub fn run(module: &mut Module, opt_level: u32) -> bool { // Process each function for func_idx in 0..module.functions.len() { - let caller_size: usize = module.functions[func_idx] + let caller_name = module.functions[func_idx].name.clone(); + let mut caller_size: usize = module.functions[func_idx] .blocks .iter() .map(|b| b.insns.len()) .sum(); + // Check if caller is recursive (calls itself directly). + // NOTE: mutual recursion (A→B→A) is not detected; those callers + // bypass the stack-size check. The HARD_CALLER_SIZE_CAP and + // proportional growth limits provide sufficient protection. + let caller_is_recursive = module.functions[func_idx].blocks.iter().any(|bb| { + bb.insns.iter().any(|insn| { + insn.op == Opcode::Call + && insn.func_name.as_ref().is_some_and(|n| n == &caller_name) + }) + }); + // Find all call sites in this function that should be inlined // Process in reverse order to avoid invalidating indices let mut call_sites: Vec<(usize, usize, String)> = Vec::new(); @@ -963,9 +1048,14 @@ pub fn run(module: &mut Module, opt_level: u32) -> bool { if insn.op == Opcode::Call { if let Some(callee_name) = &insn.func_name { if let Some(candidate) = candidates.get(callee_name) { - if should_inline(candidate, opt_level, caller_size) { + if should_inline( + candidate, + opt_level, + caller_size, + caller_is_recursive, + ) { // Don't inline recursive calls - if callee_name != &module.functions[func_idx].name { + if *callee_name != caller_name { call_sites.push((bb_idx, insn_idx, callee_name.clone())); } } @@ -988,6 +1078,13 @@ pub fn run(module: &mut Module, opt_level: u32) -> bool { if inline_call_site(&mut module.functions[func_idx], bb_idx, insn_idx, &callee) { changed_this_iteration = true; + // Update caller_size so subsequent inlining decisions + // in this iteration see the actual post-inline size. + caller_size = module.functions[func_idx] + .blocks + .iter() + .map(|b| b.insns.len()) + .sum(); } } } @@ -1142,7 +1239,7 @@ mod tests { #[test] fn test_analyze_simple_function() { let func = make_simple_func("test", true); - let mut module = Module::new(); + let mut module = Module::default(); module.functions.push(func); let call_counts = build_call_count_map(&module); @@ -1167,7 +1264,7 @@ mod tests { }; // Small function should always inline at -O1 - assert!(should_inline(&candidate, 1, 100)); + assert!(should_inline(&candidate, 1, 100, false)); } #[test] @@ -1182,7 +1279,7 @@ mod tests { }; // Varargs functions should never inline - assert!(!should_inline(&candidate, 2, 100)); + assert!(!should_inline(&candidate, 2, 100, false)); } #[test] @@ -1197,7 +1294,7 @@ mod tests { }; // Recursive functions should not inline - assert!(!should_inline(&candidate, 2, 100)); + assert!(!should_inline(&candidate, 2, 100, false)); } #[test] @@ -1212,7 +1309,7 @@ mod tests { }; // Should not inline at -O0 - assert!(!should_inline(&candidate, 0, 100)); + assert!(!should_inline(&candidate, 0, 100, false)); } #[test] @@ -1227,20 +1324,20 @@ mod tests { }; // 30 instructions with inline hint should inline - assert!(should_inline(&candidate, 1, 100)); + assert!(should_inline(&candidate, 1, 100, false)); // Without hint, 30 instructions is too large let candidate_no_hint = InlineCandidate { has_inline_hint: false, ..candidate }; - assert!(!should_inline(&candidate_no_hint, 1, 100)); + assert!(!should_inline(&candidate_no_hint, 1, 100, false)); } #[test] fn test_address_taken_function_not_removed() { let types = TypeTable::new(&Target::host()); - let mut module = Module::new(); + let mut module = Module::default(); // Create a static function "handler" that would be removed if not address-taken let mut handler = Function::new("handler", types.int_id); @@ -1307,7 +1404,7 @@ mod tests { // are NOT removed by dead function elimination. // This tests the fix for: static const struct { func_ptr fn; } = { my_func }; let types = TypeTable::new(&Target::host()); - let mut module = Module::new(); + let mut module = Module::default(); // Create a static function "callback" with no direct callers let mut callback = Function::new("callback", types.int_id); @@ -1366,7 +1463,7 @@ mod tests { fn test_global_struct_initializer_func_ref_preserved() { // Test function refs inside struct initializers in globals let types = TypeTable::new(&Target::host()); - let mut module = Module::new(); + let mut module = Module::default(); // Create static function "my_handler" let mut handler = Function::new("my_handler", types.int_id); @@ -1419,7 +1516,7 @@ mod tests { fn test_global_array_initializer_func_ref_preserved() { // Test function refs inside array initializers in globals let types = TypeTable::new(&Target::host()); - let mut module = Module::new(); + let mut module = Module::default(); // Create static function "arr_func" let mut func = Function::new("arr_func", types.int_id); diff --git a/cc/ir/linearize.rs b/cc/ir/linearize.rs index b3e813f1..7300ba38 100644 --- a/cc/ir/linearize.rs +++ b/cc/ir/linearize.rs @@ -12,14 +12,14 @@ use super::ssa::ssa_convert; use super::{ - AsmConstraint, AsmData, BasicBlock, BasicBlockId, CallAbiInfo, Function, Initializer, - Instruction, MemoryOrder, Module, Opcode, Pseudo, PseudoId, + BasicBlock, BasicBlockId, CallAbiInfo, Function, Initializer, Instruction, MemoryOrder, Module, + Opcode, Pseudo, PseudoId, }; use crate::abi::{get_abi_for_conv, CallingConv}; use crate::diag::{error, get_all_stream_names, Position}; use crate::parse::ast::{ - AsmOperand, AssignOp, BinaryOp, BlockItem, Declaration, Designator, Expr, ExprKind, - ExternalDecl, ForInit, FunctionDef, InitElement, OffsetOfPath, Stmt, TranslationUnit, UnaryOp, + BinaryOp, BlockItem, Expr, ExprKind, ExternalDecl, FunctionDef, InitElement, OffsetOfPath, + TranslationUnit, UnaryOp, }; use crate::strings::{StringId, StringTable}; use crate::symbol::{SymbolId, SymbolTable}; @@ -34,45 +34,45 @@ const DEFAULT_FILE_SCOPE_CAPACITY: usize = 16; /// Information about a local variable #[derive(Clone)] -struct LocalVarInfo { +pub(crate) struct LocalVarInfo { /// Symbol pseudo (address of the variable) - sym: PseudoId, + pub(crate) sym: PseudoId, /// Type of the variable - typ: TypeId, + pub(crate) typ: TypeId, /// For VLAs: symbol holding the number of elements (for runtime sizeof) /// This is stored in a hidden local variable so it survives SSA. - vla_size_sym: Option, + pub(crate) vla_size_sym: Option, /// For VLAs: the element type (for sizeof computation) - vla_elem_type: Option, + pub(crate) vla_elem_type: Option, /// For multi-dimensional VLAs: symbols storing each dimension's size /// For int arr[n][m], this contains [sym_for_n, sym_for_m] /// These are needed to compute runtime strides for outer dimension access. - vla_dim_syms: Vec, + pub(crate) vla_dim_syms: Vec, /// True if this local holds a pointer to the actual data (e.g., va_list parameters). /// When true, linearize_lvalue loads the pointer instead of taking the address. - is_indirect: bool, + pub(crate) is_indirect: bool, } -struct ResolvedDesignator { - offset: usize, - typ: TypeId, - bit_offset: Option, - bit_width: Option, - storage_unit_size: Option, +pub(crate) struct ResolvedDesignator { + pub(crate) offset: usize, + pub(crate) typ: TypeId, + pub(crate) bit_offset: Option, + pub(crate) bit_width: Option, + pub(crate) storage_unit_size: Option, } -struct RawFieldInit { - offset: usize, - field_size: usize, - init: Initializer, - bit_offset: Option, - bit_width: Option, - storage_unit_size: Option, +pub(crate) struct RawFieldInit { + pub(crate) offset: usize, + pub(crate) field_size: usize, + pub(crate) init: Initializer, + pub(crate) bit_offset: Option, + pub(crate) bit_width: Option, + pub(crate) storage_unit_size: Option, } /// Result from member_index_for_designator indicating where positional /// initialization should continue after a designated field. -enum MemberDesignatorResult { +pub(crate) enum MemberDesignatorResult { /// Field found directly at outer level; next positional index is the value. Direct(usize), /// Field found inside an anonymous struct/union at `outer_idx`. @@ -84,46 +84,46 @@ enum MemberDesignatorResult { } /// One level of anonymous struct nesting for positional continuation. -struct AnonLevel { +pub(crate) struct AnonLevel { /// Type of this anonymous struct/union - anon_type: TypeId, + pub(crate) anon_type: TypeId, /// Byte offset of this anonymous struct within the top-level struct - base_offset: usize, + pub(crate) base_offset: usize, /// Next member index to consume within this anonymous struct - inner_next_idx: usize, + pub(crate) inner_next_idx: usize, } /// Tracks continuation state when a designator targeted a field inside /// an anonymous struct/union. Supports arbitrary nesting depth. /// The next positional element should continue from the innermost level, /// popping up through parent anonymous structs when each level is exhausted. -struct AnonContinuation { +pub(crate) struct AnonContinuation { /// Index of the outermost anonymous struct in the top-level members array. - outer_idx: usize, + pub(crate) outer_idx: usize, /// Stack of nesting levels from outermost to innermost. - levels: Vec, + pub(crate) levels: Vec, } /// Grouped array init elements, keyed by array index (sorted). /// Shared between static (ast_init_list_to_ir) and runtime (linearize_init_list_at_offset) paths. -struct ArrayInitGroups { - element_lists: HashMap>, - indices: Vec, +pub(crate) struct ArrayInitGroups { + pub(crate) element_lists: HashMap>, + pub(crate) indices: Vec, } /// A field visit from walking struct/union initializer elements. /// Shared between static and runtime init paths. -struct StructFieldVisit { - offset: usize, - typ: TypeId, - field_size: usize, - kind: StructFieldVisitKind, - bit_offset: Option, - bit_width: Option, - storage_unit_size: Option, +pub(crate) struct StructFieldVisit { + pub(crate) offset: usize, + pub(crate) typ: TypeId, + pub(crate) field_size: usize, + pub(crate) kind: StructFieldVisitKind, + pub(crate) bit_offset: Option, + pub(crate) bit_width: Option, + pub(crate) storage_unit_size: Option, } -enum StructFieldVisitKind { +pub(crate) enum StructFieldVisitKind { /// A single expression to initialize this field Expr(Box), /// Sub-elements from brace elision @@ -132,11 +132,11 @@ enum StructFieldVisitKind { /// Information about a static local variable #[derive(Clone)] -struct StaticLocalInfo { +pub(crate) struct StaticLocalInfo { /// Global symbol name (unique across translation unit) - global_name: String, + pub(crate) global_name: String, /// Type of the variable - typ: TypeId, + pub(crate) typ: TypeId, } // ============================================================================ @@ -146,63 +146,63 @@ struct StaticLocalInfo { /// Linearizer context for converting AST to IR pub struct Linearizer<'a> { /// The module being built - module: Module, + pub(crate) module: Module, /// Current function being linearized - current_func: Option, + pub(crate) current_func: Option, /// Current basic block being built - current_bb: Option, + pub(crate) current_bb: Option, /// Next pseudo ID - next_pseudo: u32, + pub(crate) next_pseudo: u32, /// Next basic block ID - next_bb: u32, + pub(crate) next_bb: u32, /// Parameter -> pseudo mapping (parameters are already SSA values) - var_map: HashMap, + pub(crate) var_map: HashMap, /// Local variables (use Load/Store, converted to SSA later) /// Keyed by SymbolId for proper scope handling - locals: HashMap, + pub(crate) locals: HashMap, /// Label -> basic block mapping - label_map: HashMap, + pub(crate) label_map: HashMap, /// Break target stack (for loops) - break_targets: Vec, + pub(crate) break_targets: Vec, /// Continue target stack (for loops) - continue_targets: Vec, + pub(crate) continue_targets: Vec, /// Whether to run SSA conversion after linearization - run_ssa: bool, + pub(crate) run_ssa: bool, /// Symbol table for looking up enum constants, etc. - symbols: &'a SymbolTable, + pub(crate) symbols: &'a SymbolTable, /// Type table for type information - types: &'a TypeTable, + pub(crate) types: &'a TypeTable, /// String table for converting StringId to String at IR boundary - strings: &'a StringTable, + pub(crate) strings: &'a StringTable, /// Hidden struct return pointer (for functions returning large structs via sret) - struct_return_ptr: Option, + pub(crate) struct_return_ptr: Option, /// Size of struct being returned (for functions returning large structs via sret) - struct_return_size: u32, + pub(crate) struct_return_size: u32, /// Type of struct being returned via two registers (9-16 bytes, per ABI) - two_reg_return_type: Option, + pub(crate) two_reg_return_type: Option, /// Current function name (for generating unique static local names) - current_func_name: String, + pub(crate) current_func_name: String, /// Counter for generating unique static local names - static_local_counter: u32, + pub(crate) static_local_counter: u32, /// Counter for generating unique compound literal names (for file-scope compound literals) - compound_literal_counter: u32, + pub(crate) compound_literal_counter: u32, /// Static local variables (local name -> static local info) /// This is persistent across function calls (not cleared per function) - static_locals: HashMap, + pub(crate) static_locals: HashMap, /// Current source position for debug info - current_pos: Option, + pub(crate) current_pos: Option, /// Target configuration (architecture, ABI details) - target: &'a Target, + pub(crate) target: &'a Target, /// Whether current function is a non-static inline function /// (used for enforcing C99 inline semantic restrictions) - current_func_is_non_static_inline: bool, + pub(crate) current_func_is_non_static_inline: bool, /// Set of file-scope static variable names (for inline semantic checks) - file_scope_statics: std::collections::HashSet, + pub(crate) file_scope_statics: std::collections::HashSet, /// Calling convention of the current function being linearized - current_calling_conv: CallingConv, + pub(crate) current_calling_conv: CallingConv, /// Scope stack for locals: each entry records (sym, previous_value) pairs /// for undoing inserts when a scope exits. - local_scope_stack: Vec)>>, + pub(crate) local_scope_stack: Vec)>>, } impl<'a> Linearizer<'a> { @@ -214,7 +214,7 @@ impl<'a> Linearizer<'a> { target: &'a Target, ) -> Self { Self { - module: Module::new(), + module: Module::default(), current_func: None, current_bb: None, next_pseudo: 0, @@ -262,12 +262,12 @@ impl<'a> Linearizer<'a> { /// Push a new local scope. Subsequent `insert_local` calls will record /// the previous value so `pop_scope` can restore it. - fn push_scope(&mut self) { + pub(crate) fn push_scope(&mut self) { self.local_scope_stack.push(Vec::new()); } /// Pop the current local scope, restoring all locals to their pre-scope values. - fn pop_scope(&mut self) { + pub(crate) fn pop_scope(&mut self) { if let Some(entries) = self.local_scope_stack.pop() { for (sym, prev) in entries.into_iter().rev() { match prev { @@ -283,7 +283,7 @@ impl<'a> Linearizer<'a> { } /// Insert a local variable, recording the previous value for scope restoration. - fn insert_local(&mut self, sym: SymbolId, info: LocalVarInfo) { + pub(crate) fn insert_local(&mut self, sym: SymbolId, info: LocalVarInfo) { let prev = self.locals.insert(sym, info); if let Some(scope) = self.local_scope_stack.last_mut() { scope.push((sym, prev)); @@ -292,13 +292,13 @@ impl<'a> Linearizer<'a> { /// Convert a StringId to a &str using the string table #[inline] - fn str(&self, id: StringId) -> &str { + pub(crate) fn str(&self, id: StringId) -> &str { self.strings.get(id) } /// Get the name of a symbol as a String #[inline] - fn symbol_name(&self, id: SymbolId) -> String { + pub(crate) fn symbol_name(&self, id: SymbolId) -> String { self.str(self.symbols.get(id).name).to_string() } @@ -318,14 +318,14 @@ impl<'a> Linearizer<'a> { } /// Allocate a new pseudo ID - fn alloc_pseudo(&mut self) -> PseudoId { + pub(crate) fn alloc_pseudo(&mut self) -> PseudoId { let id = PseudoId(self.next_pseudo); self.next_pseudo += 1; id } /// Allocate a new pseudo and register it in the current function. - fn alloc_reg_pseudo(&mut self) -> PseudoId { + pub(crate) fn alloc_reg_pseudo(&mut self) -> PseudoId { let id = self.alloc_pseudo(); let pseudo = Pseudo::reg(id, id.0); if let Some(func) = &mut self.current_func { @@ -335,7 +335,7 @@ impl<'a> Linearizer<'a> { } /// Map a bitfield storage unit byte-size to the corresponding unsigned type. - fn bitfield_storage_type(&self, storage_size: u32) -> TypeId { + pub(crate) fn bitfield_storage_type(&self, storage_size: u32) -> TypeId { match storage_size { 1 => self.types.uchar_id, 2 => self.types.ushort_id, @@ -346,14 +346,14 @@ impl<'a> Linearizer<'a> { } /// Allocate a new basic block ID - fn alloc_bb(&mut self) -> BasicBlockId { + pub(crate) fn alloc_bb(&mut self) -> BasicBlockId { let id = BasicBlockId(self.next_bb); self.next_bb += 1; id } /// Get or create a basic block - fn get_or_create_bb(&mut self, id: BasicBlockId) -> &mut BasicBlock { + pub(crate) fn get_or_create_bb(&mut self, id: BasicBlockId) -> &mut BasicBlock { let func = self.current_func.as_mut().unwrap(); if func.get_block(id).is_none() { func.add_block(BasicBlock::new(id)); @@ -363,7 +363,7 @@ impl<'a> Linearizer<'a> { /// Emit a PhiSource instruction in a predecessor block. /// Returns the PhiSource target pseudo. - fn emit_phi_source( + pub(crate) fn emit_phi_source( &mut self, pred_bb: BasicBlockId, value: PseudoId, @@ -394,7 +394,7 @@ impl<'a> Linearizer<'a> { } /// Add an instruction to the current basic block - fn emit(&mut self, insn: Instruction) { + pub(crate) fn emit(&mut self, insn: Instruction) { if let Some(bb_id) = self.current_bb { // Attach current source position for debug info let insn = if let Some(pos) = self.current_pos { @@ -409,7 +409,7 @@ impl<'a> Linearizer<'a> { /// Apply C99 integer promotions (6.3.1.1) /// Types smaller than int are promoted to int (or unsigned int if int can't hold all values) - fn integer_promote(&self, typ_id: TypeId) -> TypeId { + pub(crate) fn integer_promote(&self, typ_id: TypeId) -> TypeId { // Integer promotions apply to _Bool, char, short (and their unsigned variants) // They are promoted to int if int can represent all values, otherwise unsigned int match self.types.kind(typ_id) { @@ -429,7 +429,7 @@ impl<'a> Linearizer<'a> { /// Compute the common type for usual arithmetic conversions (C99 6.3.1.8) /// Returns the wider type that both operands should be converted to - fn common_type(&self, left: TypeId, right: TypeId) -> TypeId { + pub(crate) fn common_type(&self, left: TypeId, right: TypeId) -> TypeId { // C99 6.3.1.8 usual arithmetic conversions: // 1. If either is long double, convert to long double // 2. Else if either is double, convert to double @@ -521,7 +521,12 @@ impl<'a> Linearizer<'a> { /// Emit a type conversion if needed /// Returns the (possibly converted) pseudo ID - fn emit_convert(&mut self, val: PseudoId, from_typ: TypeId, to_typ: TypeId) -> PseudoId { + pub(crate) fn emit_convert( + &mut self, + val: PseudoId, + from_typ: TypeId, + to_typ: TypeId, + ) -> PseudoId { let from_size = self.types.size_bits(from_typ); let to_size = self.types.size_bits(to_typ); let from_float = self.types.is_float(from_typ); @@ -638,7 +643,7 @@ impl<'a> Linearizer<'a> { } /// Check if current basic block is terminated - fn is_terminated(&self) -> bool { + pub(crate) fn is_terminated(&self) -> bool { if let Some(bb_id) = self.current_bb { if let Some(func) = &self.current_func { if let Some(bb) = func.get_block(bb_id) { @@ -651,7 +656,7 @@ impl<'a> Linearizer<'a> { /// Link current block to merge block if not terminated. /// Used after linearizing then/else branches to connect to merge block. - fn link_to_merge_if_needed(&mut self, merge_bb: BasicBlockId) { + pub(crate) fn link_to_merge_if_needed(&mut self, merge_bb: BasicBlockId) { if self.is_terminated() { return; } @@ -664,7 +669,7 @@ impl<'a> Linearizer<'a> { } /// Link two basic blocks (parent -> child) - fn link_bb(&mut self, from: BasicBlockId, to: BasicBlockId) { + pub(crate) fn link_bb(&mut self, from: BasicBlockId, to: BasicBlockId) { let func = self.current_func.as_mut().unwrap(); // Add child to parent @@ -683,1964 +688,1443 @@ impl<'a> Linearizer<'a> { } /// Switch to a new basic block - fn switch_bb(&mut self, id: BasicBlockId) { + pub(crate) fn switch_bb(&mut self, id: BasicBlockId) { self.current_bb = Some(id); self.get_or_create_bb(id); } // ======================================================================== - // Global declarations + // Function linearization // ======================================================================== - fn linearize_global_decl(&mut self, decl: &Declaration) { - for declarator in &decl.declarators { - // Use storage_class from declarator (extern, static, _Thread_local, etc.) - // These are NOT stored in the type system - let storage_class = declarator.storage_class; - let name = self.symbol_name(declarator.symbol); + pub(crate) fn linearize_function(&mut self, func: &FunctionDef) { + // Set current position for debug info (function definition location) + self.current_pos = Some(func.pos); - // Skip typedef declarations - they don't define storage - if storage_class.contains(TypeModifiers::TYPEDEF) { - continue; - } + // Reset per-function state + self.next_pseudo = 0; + self.next_bb = 0; + self.var_map.clear(); + self.locals.clear(); + self.local_scope_stack.clear(); + self.push_scope(); // function-level scope + self.label_map.clear(); + self.break_targets.clear(); + self.continue_targets.clear(); + self.struct_return_ptr = None; + self.struct_return_size = 0; + self.two_reg_return_type = None; + self.current_func_name = self.str(func.name).to_string(); + // Remove from extern_symbols since we're defining this function + self.module.extern_symbols.remove(&self.current_func_name); + // Note: static_locals is NOT cleared - it persists across functions - // Function declarations without bodies are external functions - // Track them in extern_symbols so codegen uses GOT access - if self.types.kind(declarator.typ) == TypeKind::Function { - // Check if not defined in this module (forward refs will be cleaned up later) - if !self.module.functions.iter().any(|f| f.name == name) { - self.module.extern_symbols.insert(name); - } - continue; - } + // Create function - use storage class from FunctionDef + let modifiers = self.types.modifiers(func.return_type); + let is_static = func.is_static; + let is_inline = func.is_inline; + let _is_extern = modifiers.contains(TypeModifiers::EXTERN); + let is_noreturn = modifiers.contains(TypeModifiers::NORETURN); - // Skip extern declarations - they don't define storage - // But track them so codegen can use GOT access on macOS - // Only add to extern_symbols if not already defined (handles both cases: - // extern int x; int x = 1; - x is defined, not extern - // int x = 1; extern int x; - x is defined, not extern) - if storage_class.contains(TypeModifiers::EXTERN) { - // Check if this symbol is already defined in globals - if !self.module.globals.iter().any(|g| g.name == name) { - self.module.extern_symbols.insert(name.clone()); - // Track extern thread-local symbols separately for TLS access - if storage_class.contains(TypeModifiers::THREAD_LOCAL) { - self.module.extern_tls_symbols.insert(name); - } - } - continue; - } + // Track non-static inline functions for semantic restriction checks + // C99 6.7.4: non-static inline functions have restrictions on + // static variables they can access + self.current_func_is_non_static_inline = is_inline && !is_static; - let init = declarator.init.as_ref().map_or(Initializer::None, |e| { - self.ast_init_to_ir(e, declarator.typ) - }); + // Store calling convention from function attributes (e.g., __attribute__((sysv_abi))) + self.current_calling_conv = func.calling_conv; - // Track file-scope static variables for inline semantic checks - if storage_class.contains(TypeModifiers::STATIC) { - self.file_scope_statics.insert(name.clone()); - } + let mut ir_func = Function::new(self.str(func.name), func.return_type); + // For linkage: + // - static inline: internal linkage (same as static) + // - inline (without extern): inline definition only, internal linkage + // - extern inline: per C99, provides external definition, but since we + // treat inline functions as internal linkage candidates for inlining, + // avoid duplicate symbol errors when same inline function is defined + // in multiple translation units + ir_func.is_static = is_static || is_inline; + ir_func.is_noreturn = is_noreturn; + ir_func.is_inline = is_inline; - // If this symbol was previously declared extern, remove it from extern_symbols - // (we now have the actual definition) - self.module.extern_symbols.remove(&name); - - // Check for thread-local storage - let is_static = storage_class.contains(TypeModifiers::STATIC); - if storage_class.contains(TypeModifiers::THREAD_LOCAL) { - self.module.add_global_tls_aligned( - &name, - declarator.typ, - init, - declarator.explicit_align, - is_static, - ); - } else { - self.module.add_global_aligned( - &name, - declarator.typ, - init, - declarator.explicit_align, - is_static, - ); - } + let ret_kind = self.types.kind(func.return_type); + // Check if function returns a large struct + // Large structs are returned via a hidden first parameter (sret) + // that points to caller-allocated space + let returns_large_struct = (ret_kind == TypeKind::Struct || ret_kind == TypeKind::Union) + && self.types.size_bits(func.return_type) > self.target.max_aggregate_register_bits; + + // Argument index offset: if returning large struct, first arg is hidden return pointer + let arg_offset: u32 = if returns_large_struct { 1 } else { 0 }; + + // Add hidden return pointer parameter if needed + if returns_large_struct { + let sret_id = self.alloc_pseudo(); + let sret_pseudo = Pseudo::arg(sret_id, 0).with_name("__sret"); + ir_func.add_pseudo(sret_pseudo); + self.struct_return_ptr = Some(sret_id); + self.struct_return_size = self.types.size_bits(func.return_type); } - } - /// Convert an AST initializer expression to an IR Initializer - /// - /// This handles: - /// - Scalar initializers (int, float, char literals) - /// - String literals (for char arrays or char pointers) - /// - Array initializers with designated and positional elements - /// - Struct initializers with designated and positional fields - /// - Address-of expressions (&symbol) - /// - Nested initializers - /// - Compound literals (C99 6.5.2.5) - fn ast_init_to_ir(&mut self, expr: &Expr, typ: TypeId) -> Initializer { - match &expr.kind { - ExprKind::IntLit(v) => Initializer::Int(*v as i128), - ExprKind::Int128Lit(v) => Initializer::Int(*v), - ExprKind::FloatLit(v) => Initializer::Float(*v), - ExprKind::CharLit(c) => Initializer::Int(*c as u8 as i8 as i128), + // Check if function returns a medium struct (9-16 bytes) via two registers + // This is the ABI-compliant way to return structs that fit in two GP registers + let struct_size_bits = self.types.size_bits(func.return_type); + let returns_two_reg_struct = (ret_kind == TypeKind::Struct || ret_kind == TypeKind::Union) + && struct_size_bits > 64 + && struct_size_bits <= 128 + && !returns_large_struct; // Only if not using sret + if returns_two_reg_struct { + self.two_reg_return_type = Some(func.return_type); + } - // String literal - for arrays, store as String; for pointers, create label reference - ExprKind::StringLit(s) => { - let type_kind = self.types.kind(typ); - if type_kind == TypeKind::Array { - // char array - embed the string directly - Initializer::String(s.clone()) - } else { - // Pointer - create a string constant and reference it - let label = format!(".LC{}", self.module.strings.len()); - self.module.strings.push((label.clone(), s.clone())); - Initializer::SymAddr(label) - } - } + // Add parameters + // For struct/union parameters, we need to copy them to local storage + // so member access works properly + // Tuple: (name_string, symbol_id_option, type, pseudo_id) + let mut struct_params: Vec<(String, Option, TypeId, PseudoId)> = + Vec::with_capacity(func.params.len()); + // Complex parameters also need local storage for real/imag access + // Tuple: (name, symbol_id, type, arg_pseudo, arg_index_with_offset) + let mut complex_params: Vec<(String, Option, TypeId, PseudoId, u32)> = + Vec::with_capacity(func.params.len()); + // Scalar parameters need local storage for SSA-correct reassignment handling + let mut scalar_params: Vec<(String, Option, TypeId, PseudoId)> = + Vec::with_capacity(func.params.len()); + // va_list parameters need special handling (pointer storage) + let mut valist_params: Vec<(String, Option, TypeId, PseudoId)> = + Vec::with_capacity(func.params.len()); - // Wide string literal - for arrays, store as WideString; for pointers, create label reference - ExprKind::WideStringLit(s) => { - let type_kind = self.types.kind(typ); - if type_kind == TypeKind::Array { - // wchar_t array - embed the wide string directly - Initializer::WideString(s.clone()) - } else { - // Pointer - create a wide string constant and reference it - // Use .LWC prefix to avoid collision with regular .LC string labels - let label = format!(".LWC{}", self.module.wide_strings.len()); - self.module.wide_strings.push((label.clone(), s.clone())); - Initializer::SymAddr(label) - } - } + for (i, param) in func.params.iter().enumerate() { + let name = param + .symbol + .map(|id| self.symbol_name(id)) + .unwrap_or_else(|| format!("arg{}", i)); + ir_func.add_param(&name, param.typ); - // Negative literal (fast path for simple cases) - ExprKind::Unary { - op: UnaryOp::Neg, - operand, - } => match &operand.kind { - ExprKind::IntLit(v) => Initializer::Int(-(*v as i128)), - ExprKind::Int128Lit(v) => Initializer::Int(v.wrapping_neg()), - ExprKind::FloatLit(v) => Initializer::Float(-*v), - // For more complex expressions like -(1+2), try constant evaluation - _ => { - if let Some(val) = self.eval_const_expr(expr) { - Initializer::Int(val) - } else { - Initializer::None - } - } - }, + // Create argument pseudo (offset by 1 if there's a hidden return pointer) + let pseudo_id = self.alloc_pseudo(); + let pseudo = Pseudo::arg(pseudo_id, i as u32 + arg_offset).with_name(&name); + ir_func.add_pseudo(pseudo); - // Address-of expression - ExprKind::Unary { - op: UnaryOp::AddrOf, - operand, - } => { - // Try to compute the address as symbol + offset - if let Some((name, offset)) = self.eval_static_address(operand) { - if offset == 0 { - Initializer::SymAddr(name) - } else { - Initializer::SymAddrOffset(name, offset) - } + // For struct/union types, we'll copy to a local later + // so member access works properly + let param_kind = self.types.kind(param.typ); + if param_kind == TypeKind::VaList { + // va_list parameters are special: due to array-to-pointer decay at call site, + // the actual value passed is a pointer to the va_list struct, not the struct itself. + // We'll handle this after function setup. + valist_params.push((name, param.symbol, param.typ, pseudo_id)); + } else if param_kind == TypeKind::Struct || param_kind == TypeKind::Union { + // Medium structs (9-16 bytes) with all-SSE classification are + // passed like complex types (in two XMM registers). Route them + // through complex_params so the codegen handles the register split. + let size = self.types.size_bits(param.typ); + let is_two_fp_regs = size > 64 && size <= 128 && { + let abi = get_abi_for_conv(self.current_calling_conv, self.target); + let class = abi.classify_param(param.typ, self.types); + matches!( + class, + crate::abi::ArgClass::Direct { ref classes, .. } + if classes.len() == 2 + && classes.iter().all(|c| *c == crate::abi::RegClass::Sse) + ) || matches!(class, crate::abi::ArgClass::Hfa { count: 2, .. }) + }; + if is_two_fp_regs { + complex_params.push(( + name, + param.symbol, + param.typ, + pseudo_id, + i as u32 + arg_offset, + )); } else { - Initializer::None + struct_params.push((name, param.symbol, param.typ, pseudo_id)); } + } else if self.types.is_complex(param.typ) { + // Complex parameters: copy to local storage so real/imag access works + // Unlike structs, complex types are passed in FP registers per ABI, + // so we create local storage and the codegen handles the register split + complex_params.push(( + name, + param.symbol, + param.typ, + pseudo_id, + i as u32 + arg_offset, + )); + } else { + // Store all scalar parameters to locals so SSA conversion can properly + // handle reassignment with phi nodes. If the parameter is never modified, + // SSA will optimize away the redundant load/store. + scalar_params.push((name, param.symbol, param.typ, pseudo_id)); } + } - // Cast expression - evaluate the inner expression - ExprKind::Cast { expr: inner, .. } => self.ast_init_to_ir(inner, typ), + self.current_func = Some(ir_func); - // Initializer list for arrays/structs - ExprKind::InitList { elements } => self.ast_init_list_to_ir(elements, typ), + // Create entry block + let entry_bb = self.alloc_bb(); + self.switch_bb(entry_bb); - // Compound literal in initializer context (C99 6.5.2.5) - ExprKind::CompoundLiteral { - typ: cl_type, - elements, - } => { - // Check if compound literal type matches target type - if *cl_type == typ { - // Direct value - treat like InitList - self.ast_init_list_to_ir(elements, typ) - } else if self.types.kind(typ) == TypeKind::Pointer { - // Pointer initialization - create anonymous static global - // and return its address - let anon_name = format!(".CL{}", self.compound_literal_counter); - self.compound_literal_counter += 1; - - // Create the anonymous global - let init = self.ast_init_list_to_ir(elements, *cl_type); - self.module.add_global(&anon_name, *cl_type, init); - - // Return address of the anonymous global - Initializer::SymAddr(anon_name) - } else { - // Type mismatch - use the compound literal's own type - self.ast_init_list_to_ir(elements, *cl_type) - } - } + // Entry instruction + self.emit(Instruction::new(Opcode::Entry)); - // Identifier - for constant addresses (function pointers, array decay, etc.) - // or enum constants - ExprKind::Ident(symbol_id) => { - let type_kind = self.types.kind(typ); - // For pointer types, this is likely a function address or array decay - if type_kind == TypeKind::Pointer { - let name_str = self.symbol_name(*symbol_id); - // Check if this is a static local variable - // Static locals have mangled names like "func_name.var_name.N" - let key = format!("{}.{}", self.current_func_name, name_str); - if let Some(static_info) = self.static_locals.get(&key) { - Initializer::SymAddr(static_info.global_name.clone()) - } else { - Initializer::SymAddr(name_str) - } - } else { - // Check if it's an enum constant - let sym = self.symbols.get(*symbol_id); - if let Some(val) = sym.enum_value { - Initializer::Int(val as i128) - } else { - Initializer::None - } - } + // Handle va_list parameters: store the pointer value (not the struct) + for (name, symbol_id_opt, typ, arg_pseudo) in valist_params { + // va_list params are passed as pointers due to array decay at call site. + // Store the pointer value (8 bytes) to a local. + let ptr_type = self.types.pointer_to(typ); + let local_sym = self.alloc_pseudo(); + let sym = Pseudo::sym(local_sym, name.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(sym); + func.add_local(&name, local_sym, ptr_type, false, false, None, None); } - - // Binary add/sub with pointer operand → SymAddrOffset - ExprKind::Binary { - op: op @ (BinaryOp::Add | BinaryOp::Sub), - left, - right, - } => { - // Try pointer/array + int or int + pointer/array → symbol address with offset - let is_ptr_or_array = - |t: TypeId| matches!(self.types.kind(t), TypeKind::Pointer | TypeKind::Array); - let (ptr_expr, int_expr, is_sub) = if left.typ.is_some_and(is_ptr_or_array) { - (left.as_ref(), right.as_ref(), *op == BinaryOp::Sub) - } else if right.typ.is_some_and(is_ptr_or_array) && *op == BinaryOp::Add { - (right.as_ref(), left.as_ref(), false) - } else { - // Neither operand is pointer — try as integer constant - if let Some(val) = self.eval_const_expr(expr) { - return Initializer::Int(val); - } - error( - self.current_pos.unwrap_or_default(), - &format!( - "unsupported expression in global initializer: {:?}", - expr.kind - ), - ); - return Initializer::None; - }; - - // Evaluate the pointer side as a static address - if let Some((name, base_off)) = self.eval_static_address(ptr_expr) { - // Evaluate the integer side as a constant - if let Some(int_val) = self.eval_const_expr(int_expr) { - // Get the pointee size for pointer arithmetic scaling - let pointee_size = ptr_expr - .typ - .and_then(|t| self.types.base_type(t)) - .map(|t| self.types.size_bytes(t) as i64) - .unwrap_or(1); - let byte_offset = if is_sub { - base_off - int_val as i64 * pointee_size - } else { - base_off + int_val as i64 * pointee_size - }; - if byte_offset == 0 { - Initializer::SymAddr(name) - } else { - Initializer::SymAddrOffset(name, byte_offset) - } - } else if let Some(val) = self.eval_const_expr(expr) { - Initializer::Int(val) - } else { - error( - self.current_pos.unwrap_or_default(), - "non-constant offset in pointer arithmetic initializer", - ); - Initializer::None - } - } else if let Some(val) = self.eval_const_expr(expr) { - Initializer::Int(val) - } else { - error( - self.current_pos.unwrap_or_default(), - "non-constant pointer expression in global initializer", - ); - Initializer::None - } + let ptr_size = self.types.size_bits(ptr_type); + self.emit(Instruction::store( + arg_pseudo, local_sym, 0, ptr_type, ptr_size, + )); + if let Some(symbol_id) = symbol_id_opt { + self.insert_local( + symbol_id, + LocalVarInfo { + sym: local_sym, + typ, // Keep original va_list type for type checking + vla_size_sym: None, + vla_elem_type: None, + vla_dim_syms: vec![], + is_indirect: true, // va_list param: local holds a pointer + }, + ); } + } - // Compile-time ternary: cond ? then_expr : else_expr - // Used in CPython's _Py_LATIN1_CHR() macro for static initializers - ExprKind::Conditional { - cond, - then_expr, - else_expr, - } => { - if let Some(cond_val) = self.eval_const_expr(cond) { - if cond_val != 0 { - return self.ast_init_to_ir(then_expr, typ); - } else { - return self.ast_init_to_ir(else_expr, typ); - } - } - // If condition isn't constant, fall through to error - error( - self.current_pos.unwrap_or_default(), - &format!( - "non-constant condition in global initializer ternary: {:?}", - cond.kind - ), - ); - Initializer::None + // Copy struct parameters to local storage so member access works + for (name, symbol_id_opt, typ, arg_pseudo) in struct_params { + // Create a symbol pseudo for this local variable (its address) + let local_sym = self.alloc_pseudo(); + let sym = Pseudo::sym(local_sym, name.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(sym); + let mods = self.types.modifiers(typ); + let is_volatile = mods.contains(TypeModifiers::VOLATILE); + let is_atomic = mods.contains(TypeModifiers::ATOMIC); + func.add_local(&name, local_sym, typ, is_volatile, is_atomic, None, None); } - // Other constant expressions - // Try to evaluate as integer or float constant expression - _ => { - if let Some(val) = self.eval_const_expr(expr) { - Initializer::Int(val) - } else if let Some(val) = self.eval_const_float_expr(expr) { - Initializer::Float(val) - } else if let Some((name, offset)) = self.eval_static_address(expr) { - // Try as a static address (e.g., &global.field->subfield chains) - if offset != 0 { - Initializer::SymAddrOffset(name, offset) - } else { - Initializer::SymAddr(name) - } - } else { - // Hard error for non-empty expressions we can't evaluate - error( - self.current_pos.unwrap_or_default(), - &format!( - "unsupported expression in global initializer: {:?}", - expr.kind - ), - ); - Initializer::None - } - } - } - } + let typ_size = self.types.size_bits(typ); + let is_aarch64 = self.target.arch == crate::target::Arch::Aarch64; + if typ_size > 128 && !is_aarch64 { + // x86-64: Large struct (> 16 bytes) passed by value on the stack. + // arg_pseudo is an IncomingArg pointing to the struct data on the stack. + // Use SymAddr to get the base address, then copy each 8-byte chunk. + let ptr_type = self.types.pointer_to(typ); + let addr_pseudo = self.alloc_reg_pseudo(); + self.emit(Instruction::sym_addr(addr_pseudo, arg_pseudo, ptr_type)); - /// Count the number of scalar fields needed to fill an aggregate type - /// (for brace elision per C99 6.7.8p17-20). - fn count_scalar_fields(&self, typ: TypeId) -> usize { - match self.types.kind(typ) { - TypeKind::Array => { - let elem_type = self.types.base_type(typ).unwrap_or(self.types.int_id); - let count = self.types.get(typ).array_size.unwrap_or(0); - count * self.count_scalar_fields(elem_type) - } - TypeKind::Struct => { - if let Some(composite) = self.types.get(typ).composite.as_ref() { - composite - .members - .iter() - // Skip unnamed bitfield padding - .filter(|m| m.name != StringId::EMPTY || m.bit_width.is_none()) - .map(|m| self.count_scalar_fields(m.typ)) - .sum() - } else { - 1 + let struct_size = typ_size / 8; + let mut offset = 0i64; + while offset < struct_size as i64 { + let temp = self.alloc_reg_pseudo(); + self.emit(Instruction::load( + temp, + addr_pseudo, + offset, + self.types.long_id, + 64, + )); + self.emit(Instruction::store( + temp, + local_sym, + offset, + self.types.long_id, + 64, + )); + offset += 8; } - } - TypeKind::Union => { - // Union only initializes first named member - if let Some(composite) = self.types.get(typ).composite.as_ref() { - composite - .members - .iter() - .find(|m| m.name != StringId::EMPTY) - .map(|m| self.count_scalar_fields(m.typ)) - .unwrap_or(1) - } else { - 1 + } else if typ_size > 64 { + // Medium struct (9-16 bytes): arg_pseudo is a pointer (current behavior). + // Copy each 8-byte chunk through pointer dereference. + let struct_size = typ_size / 8; + let mut offset = 0i64; + while offset < struct_size as i64 { + let temp = self.alloc_reg_pseudo(); + self.emit(Instruction::load( + temp, + arg_pseudo, + offset, + self.types.long_id, + 64, + )); + self.emit(Instruction::store( + temp, + local_sym, + offset, + self.types.long_id, + 64, + )); + offset += 8; } + } else { + // Small struct: arg_pseudo contains the value directly + self.emit(Instruction::store(arg_pseudo, local_sym, 0, typ, typ_size)); } - _ => 1, - } - } - - /// Check if brace elision applies: the element is a positional scalar targeting - /// an aggregate member, and is NOT a string literal initializing a char array - /// (C99 6.7.8p14: string literals are a special case for char arrays). - fn is_brace_elision_candidate(&self, element: &InitElement, target_type: TypeId) -> bool { - if !element.designators.is_empty() { - return false; - } - let target_is_aggregate = matches!( - self.types.kind(target_type), - TypeKind::Array | TypeKind::Struct | TypeKind::Union - ); - if !target_is_aggregate { - return false; - } - // String/wide string literals can directly initialize char/wchar_t arrays - // without brace elision (C99 6.7.8p14) - if matches!( - element.value.kind, - ExprKind::InitList { .. } | ExprKind::StringLit(_) | ExprKind::WideStringLit(_) - ) { - return false; - } - true - } - /// Consume elements from `elements[elem_idx..]` via brace elision to fill - /// an aggregate `target_type`. Returns the collected sub-elements. - /// Advances `elem_idx` past the consumed elements. - fn consume_brace_elision( - &self, - elements: &[InitElement], - elem_idx: &mut usize, - target_type: TypeId, - ) -> Vec { - let n = self.count_scalar_fields(target_type); - let mut sub_elements = Vec::new(); - let mut consumed = 0; - while consumed < n && *elem_idx < elements.len() { - let e = &elements[*elem_idx]; - // Stop at designated elements (they apply to the current aggregate level) - if consumed > 0 && !e.designators.is_empty() { - break; + // Register as a local variable (only if named parameter) + if let Some(symbol_id) = symbol_id_opt { + self.insert_local( + symbol_id, + LocalVarInfo { + sym: local_sym, + typ, + vla_size_sym: None, + vla_elem_type: None, + vla_dim_syms: vec![], + is_indirect: false, + }, + ); } - sub_elements.push(InitElement { - designators: vec![], - value: e.value.clone(), - }); - *elem_idx += 1; - consumed += 1; } - sub_elements - } - /// Group array init elements by index, handling designators, brace elision, - /// and nested InitList flattening. Shared between static and runtime paths. - fn group_array_init_elements( - &self, - elements: &[InitElement], - elem_type: TypeId, - ) -> ArrayInitGroups { - let mut element_lists: HashMap> = HashMap::new(); - let mut element_indices: Vec = Vec::new(); - let mut current_idx: i64 = 0; - let mut elem_idx = 0; - - while elem_idx < elements.len() { - let element = &elements[elem_idx]; - let mut index = None; - let mut index_pos = None; - for (pos, designator) in element.designators.iter().enumerate() { - if let Designator::Index(idx) = designator { - index = Some(*idx); - index_pos = Some(pos); - break; - } + // Setup local storage for complex parameters + // Complex types are passed in FP registers per ABI - the prologue codegen + // handles storing from XMM registers to local storage + for (name, symbol_id_opt, typ, _arg_pseudo, arg_idx) in complex_params { + // Create a symbol pseudo for this local variable (its address) + let local_sym = self.alloc_pseudo(); + let sym = Pseudo::sym(local_sym, name.clone()); + let typ_size_bytes = (self.types.size_bits(typ) / 8) as usize; + if let Some(func) = &mut self.current_func { + func.add_pseudo(sym); + let mods = self.types.modifiers(typ); + let is_volatile = mods.contains(TypeModifiers::VOLATILE); + let is_atomic = mods.contains(TypeModifiers::ATOMIC); + func.add_local(&name, local_sym, typ, is_volatile, is_atomic, None, None); + // Record for inliner: the backend prologue fills this local from + // registers; the inliner must generate an explicit copy instead. + func.implicit_param_copies.push(super::ImplicitParamCopy { + arg_index: arg_idx, + local_sym, + size_bytes: typ_size_bytes, + qword_type: self.types.long_id, + }); } - let element_index = if let Some(idx) = index { - current_idx = idx + 1; - idx - } else { - let idx = current_idx; - current_idx += 1; - idx - }; - - let remaining_designators = match index_pos { - Some(pos) => element.designators[pos + 1..].to_vec(), - None => element.designators.clone(), - }; + // Don't emit a store here - the prologue codegen handles storing + // from XMM0+XMM1/XMM2+XMM3/etc to local storage - // Brace elision (C99 6.7.8p20): positional scalar for aggregate element - if remaining_designators.is_empty() - && self.is_brace_elision_candidate(element, elem_type) - { - let sub_elements = self.consume_brace_elision(elements, &mut elem_idx, elem_type); - let entry = element_lists.entry(element_index).or_insert_with(|| { - element_indices.push(element_index); - Vec::new() - }); - entry.extend(sub_elements); - continue; + // Register as a local variable for name lookup (only if named parameter) + if let Some(symbol_id) = symbol_id_opt { + self.insert_local( + symbol_id, + LocalVarInfo { + sym: local_sym, + typ, + vla_size_sym: None, + vla_elem_type: None, + vla_dim_syms: vec![], + is_indirect: false, + }, + ); } + } - let entry = element_lists.entry(element_index).or_insert_with(|| { - element_indices.push(element_index); - Vec::new() - }); - - if remaining_designators.is_empty() { - if let ExprKind::InitList { - elements: nested_elements, - } = &element.value.kind - { - entry.extend(nested_elements.clone()); - elem_idx += 1; - continue; - } + // Store scalar parameters to local storage for SSA-correct reassignment handling + // This ensures that if a parameter is reassigned inside a branch, phi nodes + // are properly inserted at merge points. + for (name, symbol_id_opt, typ, arg_pseudo) in scalar_params { + // Create a symbol pseudo for this local variable (its address) + let local_sym = self.alloc_pseudo(); + let sym = Pseudo::sym(local_sym, name.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(sym); + let mods = self.types.modifiers(typ); + let is_volatile = mods.contains(TypeModifiers::VOLATILE); + let is_atomic = mods.contains(TypeModifiers::ATOMIC); + func.add_local(&name, local_sym, typ, is_volatile, is_atomic, None, None); } - entry.push(InitElement { - designators: remaining_designators, - value: element.value.clone(), - }); - elem_idx += 1; - } + // Store the incoming argument value to the local + let typ_size = self.types.size_bits(typ); + self.emit(Instruction::store(arg_pseudo, local_sym, 0, typ, typ_size)); - element_indices.sort(); - ArrayInitGroups { - element_lists, - indices: element_indices, + // Register as a local variable for name lookup (only if named parameter) + if let Some(symbol_id) = symbol_id_opt { + self.insert_local( + symbol_id, + LocalVarInfo { + sym: local_sym, + typ, + vla_size_sym: None, + vla_elem_type: None, + vla_dim_syms: vec![], + is_indirect: false, + }, + ); + } } - } - /// Walk struct/union init elements and produce field visits. - /// Handles positional iteration, anonymous struct continuation, designators, - /// and brace elision. Shared between static and runtime init paths. - fn walk_struct_init_fields( - &self, - resolved_typ: TypeId, - members: &[crate::types::StructMember], - is_union: bool, - elements: &[InitElement], - ) -> Vec { - let mut visits = Vec::new(); - let mut current_field_idx = 0; - let mut anon_cont: Option = None; - let mut elem_idx = 0; - - while elem_idx < elements.len() { - let element = &elements[elem_idx]; - if element.designators.is_empty() { - // Positional: check anonymous struct continuation, then next member - let mut member = None; - if anon_cont.is_some() { - member = self.get_anon_continuation_member( - &mut anon_cont, - members, - &mut current_field_idx, - ); - } - if member.is_none() { - member = self.next_positional_member(members, is_union, &mut current_field_idx); - } - let Some(member) = member else { - elem_idx += 1; - continue; - }; - let field_size = (self.types.size_bits(member.typ) / 8) as usize; + // Linearize body + self.linearize_stmt(&func.body); - // Brace elision: scalar for aggregate member - let kind = if self.is_brace_elision_candidate(element, member.typ) { - let sub_elements = - self.consume_brace_elision(elements, &mut elem_idx, member.typ); - StructFieldVisitKind::BraceElision(sub_elements) - } else { - elem_idx += 1; - StructFieldVisitKind::Expr(element.value.clone()) - }; - visits.push(StructFieldVisit { - offset: member.offset, - typ: member.typ, - field_size, - kind, - bit_offset: member.bit_offset, - bit_width: member.bit_width, - storage_unit_size: member.storage_unit_size, - }); - continue; + // Ensure function ends with a return + if !self.is_terminated() { + if ret_kind == TypeKind::Void { + self.emit(Instruction::ret(None)); + } else { + // Return 0 as default, widened to actual return type + let ret_type = func.return_type; + let ret_size = self.types.size_bits(ret_type).max(32); + let zero = self.emit_const(0, ret_type); + self.emit(Instruction::ret_typed(Some(zero), ret_type, ret_size)); } + } - // Designated path - let resolved = self.resolve_designator_chain(resolved_typ, 0, &element.designators); - let Some(ResolvedDesignator { - offset, - typ: field_type, - bit_offset, - bit_width, - storage_unit_size, - }) = resolved - else { - elem_idx += 1; - continue; - }; - if let Some(Designator::Field(name)) = element.designators.first() { - if let Some(result) = self.member_index_for_designator(members, *name) { - match result { - MemberDesignatorResult::Direct(next_idx) => { - current_field_idx = next_idx; - anon_cont = None; - } - MemberDesignatorResult::Anonymous { outer_idx, levels } => { - current_field_idx = outer_idx; - anon_cont = Some(AnonContinuation { outer_idx, levels }); - } - } - } + // Run SSA conversion if enabled + if self.run_ssa { + if let Some(ref mut ir_func) = self.current_func { + ssa_convert(ir_func, self.types); + // Note: ssa_convert sets ir_func.next_pseudo to account for phi nodes + } + } else { + // Only set next_pseudo if SSA was NOT run (SSA sets its own) + if let Some(ref mut ir_func) = self.current_func { + ir_func.next_pseudo = self.next_pseudo; } - let field_size = (self.types.size_bits(field_type) / 8) as usize; - visits.push(StructFieldVisit { - offset, - typ: field_type, - field_size, - kind: StructFieldVisitKind::Expr(element.value.clone()), - bit_offset, - bit_width, - storage_unit_size, - }); - elem_idx += 1; } - visits - } - - /// Convert an AST initializer list to an IR Initializer - fn ast_init_list_to_ir(&mut self, elements: &[InitElement], typ: TypeId) -> Initializer { - let type_kind = self.types.kind(typ); - let total_size = (self.types.size_bits(typ) / 8) as usize; + // Pop function-level scope + self.pop_scope(); - match type_kind { - TypeKind::Array => { - let elem_type = self.types.base_type(typ).unwrap_or(self.types.int_id); - let elem_size = (self.types.size_bits(elem_type) / 8) as usize; - let elem_is_aggregate = matches!( - self.types.kind(elem_type), - TypeKind::Array | TypeKind::Struct | TypeKind::Union - ); + // Add function to module + if let Some(ir_func) = self.current_func.take() { + self.module.add_function(ir_func); + } + } - let groups = self.group_array_init_elements(elements, elem_type); - let mut init_elements = Vec::new(); - for element_index in groups.indices { - let Some(list) = groups.element_lists.get(&element_index) else { - continue; - }; - let offset = (element_index as usize) * elem_size; - // When a string literal initializes a char/wchar_t array element - // (e.g., char names[3][4] = {"Sun", "Mon", "Tue"}), handle it - // directly with the ARRAY type. Otherwise ast_init_list_to_ir - // recurses and passes elem_type=char, causing the string to be - // stored as a pointer instead of inline char data. - let is_string_for_char_array = elem_is_aggregate - && list.len() == 1 - && matches!( - list[0].value.kind, - ExprKind::StringLit(_) | ExprKind::WideStringLit(_) - ) - && self.types.kind(elem_type) == TypeKind::Array; - let elem_init = if is_string_for_char_array { - self.ast_init_to_ir(&list[0].value, elem_type) - } else if elem_is_aggregate { - self.ast_init_list_to_ir(list, elem_type) - } else if let Some(last) = list.last() { - self.ast_init_to_ir(&last.value, elem_type) - } else { - Initializer::None - }; - init_elements.push((offset, elem_init)); - } + // ======================================================================== + // Statement linearization + // ======================================================================== - init_elements.sort_by_key(|(offset, _)| *offset); + /// Emit large struct return via hidden pointer (sret) + pub(crate) fn emit_sret_return(&mut self, e: &Expr, sret_ptr: PseudoId, struct_size: u32) { + let src_addr = self.linearize_lvalue(e); + let struct_bytes = struct_size as i64 / 8; + let mut byte_offset = 0i64; - Initializer::Array { - elem_size, - total_size, - elements: init_elements, - } - } + while byte_offset < struct_bytes { + let temp = self.alloc_reg_pseudo(); + self.emit(Instruction::load( + temp, + src_addr, + byte_offset, + self.types.long_id, + 64, + )); + self.emit(Instruction::store( + temp, + sret_ptr, + byte_offset, + self.types.long_id, + 64, + )); + byte_offset += 8; + } - TypeKind::Struct | TypeKind::Union => { - let resolved_typ = self.resolve_struct_type(typ); - let resolved_size = (self.types.size_bits(resolved_typ) / 8) as usize; - if let Some(composite) = self.types.get(resolved_typ).composite.as_ref() { - let members: Vec<_> = composite.members.clone(); - let is_union = self.types.kind(resolved_typ) == TypeKind::Union; - - let visits = - self.walk_struct_init_fields(resolved_typ, &members, is_union, elements); - - // Convert field visits to RawFieldInit by evaluating expressions - let mut raw_fields: Vec = Vec::new(); - for visit in visits { - let field_init = match visit.kind { - StructFieldVisitKind::BraceElision(sub_elements) => { - self.ast_init_list_to_ir(&sub_elements, visit.typ) - } - StructFieldVisitKind::Expr(expr) => { - self.ast_init_to_ir(&expr, visit.typ) - } - }; - raw_fields.push(RawFieldInit { - offset: visit.offset, - field_size: visit.field_size, - init: field_init, - bit_offset: visit.bit_offset, - bit_width: visit.bit_width, - storage_unit_size: visit.storage_unit_size, - }); - } + self.emit(Instruction::ret_typed( + Some(sret_ptr), + self.types.void_ptr_id, + 64, + )); + } - // Sort fields by offset to ensure proper emission order - // (designated initializers can be in any order) - // For bitfields, also sort by bit_offset to keep them together - raw_fields.sort_by(|a, b| { - a.offset - .cmp(&b.offset) - .then_with(|| a.bit_offset.unwrap_or(0).cmp(&b.bit_offset.unwrap_or(0))) - }); - - // Remove duplicate initializations (later one wins, per C semantics) - let mut idx = 0; - while idx + 1 < raw_fields.len() { - let same_offset = raw_fields[idx].offset == raw_fields[idx + 1].offset; - let both_bitfields = raw_fields[idx].bit_offset.is_some() - && raw_fields[idx + 1].bit_offset.is_some(); - let same_bitfield = both_bitfields - && raw_fields[idx].bit_offset == raw_fields[idx + 1].bit_offset; - - if same_offset && (!both_bitfields || same_bitfield) { - raw_fields.remove(idx); - } else { - idx += 1; - } - } + /// Emit two-register struct return (9-16 bytes) + pub(crate) fn emit_two_reg_return(&mut self, e: &Expr, ret_type: TypeId) { + let src_addr = self.linearize_lvalue(e); + let struct_size = self.types.size_bits(ret_type); - // Pack bitfields that share the same storage unit - let mut init_fields: Vec<(usize, usize, Initializer)> = Vec::new(); - let mut i = 0; - while i < raw_fields.len() { - let RawFieldInit { - offset, - field_size, - init, - bit_offset, - bit_width, - storage_unit_size, - } = &raw_fields[i]; - - if let (Some(bit_off), Some(bit_w), Some(storage_size)) = - (bit_offset, bit_width, storage_unit_size) - { - let mut packed_value: u64 = 0; - if let Initializer::Int(v) = init { - let mask = (1u64 << bit_w) - 1; - packed_value |= ((*v as u64) & mask) << bit_off; - } + // Load first 8 bytes + let low_temp = self.alloc_reg_pseudo(); + self.emit(Instruction::load( + low_temp, + src_addr, + 0, + self.types.long_id, + 64, + )); - let mut j = i + 1; - while j < raw_fields.len() { - let RawFieldInit { - offset: next_off, - init: next_init, - bit_offset: next_bit_off, - bit_width: next_bit_w, - .. - } = &raw_fields[j]; - if *next_off != *offset { - break; - } - if let (Some(nb_off), Some(nb_w)) = (next_bit_off, next_bit_w) { - if let Initializer::Int(v) = next_init { - let mask = (1u64 << nb_w) - 1; - packed_value |= ((*v as u64) & mask) << nb_off; - } - } - j += 1; - } + // Load second portion (remaining bytes, up to 8) + let high_temp = self.alloc_reg_pseudo(); + let high_size = std::cmp::min(64, struct_size - 64); + self.emit(Instruction::load( + high_temp, + src_addr, + 8, + self.types.long_id, + high_size, + )); - init_fields.push(( - *offset, - *storage_size as usize, - Initializer::Int(packed_value as i128), - )); - i = j; - } else { - init_fields.push((*offset, *field_size, init.clone())); - i += 1; - } - } + // Emit return with both values and ABI info for two-register return + let mut ret_insn = Instruction::ret_typed(Some(low_temp), ret_type, struct_size); + ret_insn.src.push(high_temp); + let abi = get_abi_for_conv(self.current_calling_conv, self.target); + let ret_class = abi.classify_return(ret_type, self.types); + ret_insn.abi_info = Some(Box::new(CallAbiInfo::new(vec![], ret_class))); + self.emit(ret_insn); + } - Initializer::Struct { - total_size: resolved_size, - fields: init_fields, - } - } else { - Initializer::None - } - } + // ======================================================================== + // Expression linearization + // ======================================================================== - _ => { - if let Some(element) = elements.first() { - self.ast_init_to_ir(&element.value, typ) - } else { - Initializer::None - } - } - } + /// Get the type of an expression. + /// PANICS if expression has no type - type evaluation pass must run first. + /// The IR requires fully typed input from the type evaluation pass. + pub(crate) fn expr_type(&self, expr: &Expr) -> TypeId { + expr.typ.expect( + "BUG: expression has no type. Type evaluation pass must run before linearization.", + ) } - fn resolve_designator_chain( - &self, - base_type: TypeId, - base_offset: usize, - designators: &[Designator], - ) -> Option { - let mut offset = base_offset; - let mut typ = base_type; - let mut bit_offset = None; - let mut bit_width = None; - let mut storage_unit_size = None; - - for (idx, designator) in designators.iter().enumerate() { - match designator { - Designator::Field(name) => { - let mut resolved = typ; - if self.types.kind(resolved) == TypeKind::Array { - resolved = self.types.base_type(resolved)?; - } - resolved = self.resolve_struct_type(resolved); - let member = self.types.find_member(resolved, *name)?; - offset += member.offset; - typ = member.typ; - if idx + 1 == designators.len() { - bit_offset = member.bit_offset; - bit_width = member.bit_width; - storage_unit_size = member.storage_unit_size; - } else { - bit_offset = None; - bit_width = None; - storage_unit_size = None; - } - } - Designator::Index(index) => { - if self.types.kind(typ) != TypeKind::Array { - return None; - } - let elem_type = self.types.base_type(typ)?; - let elem_size = self.types.size_bits(elem_type) / 8; - offset += (*index as usize) * (elem_size as usize); - typ = elem_type; - bit_offset = None; - bit_width = None; - storage_unit_size = None; + /// Check if an expression is "pure" (side-effect-free). + /// Pure expressions can be speculatively evaluated, enabling cmov/csel codegen. + /// + /// An expression is pure if it contains NO: + /// - Function calls + /// - Volatile accesses + /// - Pre/post increment/decrement (++, --) + /// - Assignments (=, +=, -=, etc.) + /// - Statement expressions (GNU extension with potential side effects) + pub(crate) fn is_pure_expr(&self, expr: &Expr) -> bool { + match &expr.kind { + // Literals are always pure + ExprKind::IntLit(_) + | ExprKind::Int128Lit(_) + | ExprKind::FloatLit(_) + | ExprKind::CharLit(_) + | ExprKind::StringLit(_) + | ExprKind::WideStringLit(_) => true, + + // Identifiers are pure unless volatile + ExprKind::Ident(_) => { + if let Some(typ) = expr.typ { + !self.types.modifiers(typ).contains(TypeModifiers::VOLATILE) + } else { + true } } - } - Some(ResolvedDesignator { - offset, - typ, - bit_offset, - bit_width, - storage_unit_size, - }) - } + // __func__ is a pure string-like value + ExprKind::FuncName => true, - fn next_positional_member( - &self, - members: &[crate::types::StructMember], - is_union: bool, - current_field_idx: &mut usize, - ) -> Option { - if is_union { - if *current_field_idx > 0 { - return None; + // Binary ops are pure if both operands are pure AND the + // operator can't trap. Division and modulo cause SIGFPE + // on division by zero, so they're never pure. + ExprKind::Binary { + op, left, right, .. + } => { + !matches!(op, BinaryOp::Div | BinaryOp::Mod) + && self.is_pure_expr(left) + && self.is_pure_expr(right) } - let member = members.iter().find(|m| m.name != StringId::EMPTY)?; - *current_field_idx = members.len(); - return Some(MemberInfo { - offset: member.offset, - typ: member.typ, - bit_offset: member.bit_offset, - bit_width: member.bit_width, - storage_unit_size: member.storage_unit_size, - }); - } - while *current_field_idx < members.len() { - let member = &members[*current_field_idx]; - if member.name == StringId::EMPTY && member.bit_width.is_some() { - *current_field_idx += 1; - continue; - } - if member.name != StringId::EMPTY || member.bit_width.is_none() { - *current_field_idx += 1; - return Some(MemberInfo { - offset: member.offset, - typ: member.typ, - bit_offset: member.bit_offset, - bit_width: member.bit_width, - storage_unit_size: member.storage_unit_size, - }); - } - *current_field_idx += 1; - } + // Unary ops are pure if operand is pure, except for pre-inc/dec and dereference. + // Dereference (*ptr) can cause UB/crash if the pointer is NULL or invalid, + // so we must not eagerly evaluate it in conditional expressions. + ExprKind::Unary { op, operand, .. } => match op { + UnaryOp::PreInc | UnaryOp::PreDec | UnaryOp::Deref => false, + _ => self.is_pure_expr(operand), + }, - None - } + // Post-increment/decrement have side effects + ExprKind::PostInc(_) | ExprKind::PostDec(_) => false, - /// Get the next positional member from an anonymous struct continuation. - /// Walks the stack of anonymous struct levels from innermost to outermost. - /// If the innermost level is exhausted, pops it and tries the next outer level. - /// When all levels are exhausted, clears the continuation and returns None. - fn get_anon_continuation_member( - &self, - cont: &mut Option, - _outer_members: &[crate::types::StructMember], - current_field_idx: &mut usize, - ) -> Option { - let c = cont.as_mut()?; - - loop { - let Some(level) = c.levels.last() else { - // All levels exhausted - *current_field_idx = c.outer_idx + 1; - *cont = None; - return None; - }; - let anon_type_id = level.anon_type; - let base_offset = level.base_offset; - let mut idx = level.inner_next_idx; - - let anon_type = self.types.get(anon_type_id); - let Some(composite) = anon_type.composite.as_ref() else { - c.levels.pop(); - continue; - }; - let members = composite.members.clone(); - - // Scan members at this level - let mut found_member = None; - let mut descend_into = None; - - while idx < members.len() { - let inner = &members[idx]; - // Skip unnamed bitfield padding - if inner.name == StringId::EMPTY && inner.bit_width.is_some() { - idx += 1; - continue; - } - // Nested anonymous aggregate — descend into it - if inner.name == StringId::EMPTY && inner.bit_width.is_none() { - let inner_type = self.types.get(inner.typ); - let is_nested_anon = - matches!(inner_type.kind, TypeKind::Struct | TypeKind::Union) - && inner_type - .composite - .as_ref() - .is_some_and(|comp| comp.tag.is_none()); - if is_nested_anon { - descend_into = Some((inner.typ, base_offset + inner.offset, idx + 1)); - break; - } - } - // Found a valid named member - found_member = Some((idx + 1, inner.clone())); - break; + // Ternary is pure if all parts are pure + ExprKind::Conditional { + cond, + then_expr, + else_expr, + } => { + self.is_pure_expr(cond) + && self.is_pure_expr(then_expr) + && self.is_pure_expr(else_expr) } - if let Some((next_idx, inner)) = found_member { - // Update the current level's index - c.levels.last_mut().unwrap().inner_next_idx = next_idx; - return Some(MemberInfo { - offset: base_offset + inner.offset, - typ: inner.typ, - bit_offset: inner.bit_offset, - bit_width: inner.bit_width, - storage_unit_size: inner.storage_unit_size, - }); - } + // Function calls are never pure (may have side effects) + ExprKind::Call { .. } => false, - if let Some((nested_type, nested_offset, next_idx)) = descend_into { - // Advance past the anon struct at this level, then descend - c.levels.last_mut().unwrap().inner_next_idx = next_idx; - c.levels.push(AnonLevel { - anon_type: nested_type, - base_offset: nested_offset, - inner_next_idx: 0, - }); - continue; - } + // Member access through struct value (.) is pure if the base is pure. + ExprKind::Member { expr, .. } => self.is_pure_expr(expr), - // This level is exhausted — pop it - c.levels.pop(); - } - } + // Arrow access (ptr->member) can cause UB/crash if ptr is NULL, + // so we must not eagerly evaluate it in conditional expressions. + ExprKind::Arrow { .. } => false, - fn member_index_for_designator( - &self, - members: &[crate::types::StructMember], - name: StringId, - ) -> Option { - for (idx, member) in members.iter().enumerate() { - if member.name == name { - return Some(MemberDesignatorResult::Direct(idx + 1)); - } - if member.name == StringId::EMPTY { - let member_type = self.types.get(member.typ); - let is_anon_aggregate = - matches!(member_type.kind, TypeKind::Struct | TypeKind::Union) - && member_type - .composite - .as_ref() - .is_some_and(|composite| composite.tag.is_none()); - if is_anon_aggregate { - // Recursively search for the field, building the nesting path - let mut path = Vec::new(); - if self.find_anon_field_path(member.typ, member.offset, name, &mut path) { - return Some(MemberDesignatorResult::Anonymous { - outer_idx: idx, - levels: path, - }); - } - } - } - } + // Array indexing can cause UB/crash if the pointer is invalid, + // so we must not eagerly evaluate it in conditional expressions. + ExprKind::Index { .. } => false, - None - } + // Casts are pure if the operand is pure + ExprKind::Cast { expr, .. } => self.is_pure_expr(expr), - /// Recursively search for `name` inside an anonymous aggregate, building - /// the path of `AnonLevel`s needed for positional continuation. - /// Returns true if the field was found. - fn find_anon_field_path( - &self, - anon_type: TypeId, - base_offset: usize, - name: StringId, - path: &mut Vec, - ) -> bool { - let typ = self.types.get(anon_type); - let Some(composite) = typ.composite.as_ref() else { - return false; - }; - for (inner_idx, inner_member) in composite.members.iter().enumerate() { - if inner_member.name == name { - // Found it directly at this level - path.push(AnonLevel { - anon_type, - base_offset, - inner_next_idx: inner_idx + 1, - }); - return true; - } - // Check if this is a nested anonymous aggregate - if inner_member.name == StringId::EMPTY { - let inner_type = self.types.get(inner_member.typ); - let is_nested_anon = matches!(inner_type.kind, TypeKind::Struct | TypeKind::Union) - && inner_type - .composite - .as_ref() - .is_some_and(|c| c.tag.is_none()); - if is_nested_anon { - // Push this level pointing PAST the nested anon struct. - // The inner level handles continuation within the nested anon; - // when it's exhausted, this level continues from the next member. - path.push(AnonLevel { - anon_type, - base_offset, - inner_next_idx: inner_idx + 1, - }); - if self.find_anon_field_path( - inner_member.typ, - base_offset + inner_member.offset, - name, - path, - ) { - return true; - } - path.pop(); // not found in this branch - } - } - } - false - } + // Assignments have side effects + ExprKind::Assign { .. } => false, - // ======================================================================== - // Function linearization - // ======================================================================== + // Sizeof and _Alignof are always pure (compile-time constant) + ExprKind::SizeofType(_) + | ExprKind::SizeofExpr(_) + | ExprKind::AlignofType(_) + | ExprKind::AlignofExpr(_) => true, - fn linearize_function(&mut self, func: &FunctionDef) { - // Set current position for debug info (function definition location) - self.current_pos = Some(func.pos); + // Comma expressions: pure if all sub-expressions are pure + ExprKind::Comma(exprs) => exprs.iter().all(|e| self.is_pure_expr(e)), - // Reset per-function state - self.next_pseudo = 0; - self.next_bb = 0; - self.var_map.clear(); - self.locals.clear(); - self.local_scope_stack.clear(); - self.push_scope(); // function-level scope - self.label_map.clear(); - self.break_targets.clear(); - self.continue_targets.clear(); - self.struct_return_ptr = None; - self.struct_return_size = 0; - self.two_reg_return_type = None; - self.current_func_name = self.str(func.name).to_string(); - // Remove from extern_symbols since we're defining this function - self.module.extern_symbols.remove(&self.current_func_name); - // Note: static_locals is NOT cleared - it persists across functions + // Compound literals may have side effects in initializers + ExprKind::CompoundLiteral { .. } => false, - // Create function - use storage class from FunctionDef - let modifiers = self.types.modifiers(func.return_type); - let is_static = func.is_static; - let is_inline = func.is_inline; - let _is_extern = modifiers.contains(TypeModifiers::EXTERN); - let is_noreturn = modifiers.contains(TypeModifiers::NORETURN); + // Init lists may have side effects + ExprKind::InitList { .. } => false, - // Track non-static inline functions for semantic restriction checks - // C99 6.7.4: non-static inline functions have restrictions on - // static variables they can access - self.current_func_is_non_static_inline = is_inline && !is_static; + // Statement expressions have side effects + ExprKind::StmtExpr { .. } => false, - // Store calling convention from function attributes (e.g., __attribute__((sysv_abi))) - self.current_calling_conv = func.calling_conv; + // Variadic builtins have side effects + ExprKind::VaStart { .. } + | ExprKind::VaArg { .. } + | ExprKind::VaEnd { .. } + | ExprKind::VaCopy { .. } => false, - let mut ir_func = Function::new(self.str(func.name), func.return_type); - // For linkage: - // - static inline: internal linkage (same as static) - // - inline (without extern): inline definition only, internal linkage - // - extern inline: per C99, provides external definition, but since we - // treat inline functions as internal linkage candidates for inlining, - // avoid duplicate symbol errors when same inline function is defined - // in multiple translation units - ir_func.is_static = is_static || is_inline; - ir_func.is_noreturn = is_noreturn; - ir_func.is_inline = is_inline; - - let ret_kind = self.types.kind(func.return_type); - // Check if function returns a large struct - // Large structs are returned via a hidden first parameter (sret) - // that points to caller-allocated space - let returns_large_struct = (ret_kind == TypeKind::Struct || ret_kind == TypeKind::Union) - && self.types.size_bits(func.return_type) > self.target.max_aggregate_register_bits; + // Offsetof is always pure (compile-time constant) + ExprKind::OffsetOf { .. } => true, - // Argument index offset: if returning large struct, first arg is hidden return pointer - let arg_offset: u32 = if returns_large_struct { 1 } else { 0 }; + // Builtins: bswap, ctz, clz, popcount are pure + ExprKind::Bswap16 { arg } + | ExprKind::Bswap32 { arg } + | ExprKind::Bswap64 { arg } + | ExprKind::Ctz { arg } + | ExprKind::Ctzl { arg } + | ExprKind::Ctzll { arg } + | ExprKind::Clz { arg } + | ExprKind::Clzl { arg } + | ExprKind::Clzll { arg } + | ExprKind::Popcount { arg } + | ExprKind::Popcountl { arg } + | ExprKind::Popcountll { arg } + | ExprKind::Fabs { arg } + | ExprKind::Fabsf { arg } + | ExprKind::Fabsl { arg } + | ExprKind::Signbit { arg } + | ExprKind::Signbitf { arg } + | ExprKind::Signbitl { arg } => self.is_pure_expr(arg), - // Add hidden return pointer parameter if needed - if returns_large_struct { - let sret_id = self.alloc_pseudo(); - let sret_pseudo = Pseudo::arg(sret_id, 0).with_name("__sret"); - ir_func.add_pseudo(sret_pseudo); - self.struct_return_ptr = Some(sret_id); - self.struct_return_size = self.types.size_bits(func.return_type); - } + // Alloca allocates memory - not pure + ExprKind::Alloca { .. } => false, - // Check if function returns a medium struct (9-16 bytes) via two registers - // This is the ABI-compliant way to return structs that fit in two GP registers - let struct_size_bits = self.types.size_bits(func.return_type); - let returns_two_reg_struct = (ret_kind == TypeKind::Struct || ret_kind == TypeKind::Union) - && struct_size_bits > 64 - && struct_size_bits <= 128 - && !returns_large_struct; // Only if not using sret - if returns_two_reg_struct { - self.two_reg_return_type = Some(func.return_type); - } + // Memory builtins modify memory - not pure + ExprKind::Memset { .. } | ExprKind::Memcpy { .. } | ExprKind::Memmove { .. } => false, - // Add parameters - // For struct/union parameters, we need to copy them to local storage - // so member access works properly - // Tuple: (name_string, symbol_id_option, type, pseudo_id) - let mut struct_params: Vec<(String, Option, TypeId, PseudoId)> = - Vec::with_capacity(func.params.len()); - // Complex parameters also need local storage for real/imag access - // Tuple: (name, symbol_id, type, arg_pseudo, arg_index_with_offset) - let mut complex_params: Vec<(String, Option, TypeId, PseudoId, u32)> = - Vec::with_capacity(func.params.len()); - // Scalar parameters need local storage for SSA-correct reassignment handling - let mut scalar_params: Vec<(String, Option, TypeId, PseudoId)> = - Vec::with_capacity(func.params.len()); - // va_list parameters need special handling (pointer storage) - let mut valist_params: Vec<(String, Option, TypeId, PseudoId)> = - Vec::with_capacity(func.params.len()); + // Unreachable is pure (no side effects, just UB hint) + ExprKind::Unreachable => true, - for (i, param) in func.params.iter().enumerate() { - let name = param - .symbol - .map(|id| self.symbol_name(id)) - .unwrap_or_else(|| format!("arg{}", i)); - ir_func.add_param(&name, param.typ); + // Frame/return address builtins are pure (just read registers) + ExprKind::FrameAddress { .. } | ExprKind::ReturnAddress { .. } => true, - // Create argument pseudo (offset by 1 if there's a hidden return pointer) - let pseudo_id = self.alloc_pseudo(); - let pseudo = Pseudo::arg(pseudo_id, i as u32 + arg_offset).with_name(&name); - ir_func.add_pseudo(pseudo); + // Setjmp/longjmp have control flow side effects + ExprKind::Setjmp { .. } | ExprKind::Longjmp { .. } => false, - // For struct/union types, we'll copy to a local later - // so member access works properly - let param_kind = self.types.kind(param.typ); - if param_kind == TypeKind::VaList { - // va_list parameters are special: due to array-to-pointer decay at call site, - // the actual value passed is a pointer to the va_list struct, not the struct itself. - // We'll handle this after function setup. - valist_params.push((name, param.symbol, param.typ, pseudo_id)); - } else if param_kind == TypeKind::Struct || param_kind == TypeKind::Union { - // Medium structs (9-16 bytes) with all-SSE classification are - // passed like complex types (in two XMM registers). Route them - // through complex_params so the codegen handles the register split. - let size = self.types.size_bits(param.typ); - let is_two_fp_regs = size > 64 && size <= 128 && { - let abi = get_abi_for_conv(self.current_calling_conv, self.target); - let class = abi.classify_param(param.typ, self.types); - matches!( - class, - crate::abi::ArgClass::Direct { ref classes, .. } - if classes.len() == 2 - && classes.iter().all(|c| *c == crate::abi::RegClass::Sse) - ) || matches!(class, crate::abi::ArgClass::Hfa { count: 2, .. }) - }; - if is_two_fp_regs { - complex_params.push(( - name, - param.symbol, - param.typ, - pseudo_id, - i as u32 + arg_offset, - )); - } else { - struct_params.push((name, param.symbol, param.typ, pseudo_id)); - } - } else if self.types.is_complex(param.typ) { - // Complex parameters: copy to local storage so real/imag access works - // Unlike structs, complex types are passed in FP registers per ABI, - // so we create local storage and the codegen handles the register split - complex_params.push(( - name, - param.symbol, - param.typ, - pseudo_id, - i as u32 + arg_offset, - )); - } else { - // Store all scalar parameters to locals so SSA conversion can properly - // handle reassignment with phi nodes. If the parameter is never modified, - // SSA will optimize away the redundant load/store. - scalar_params.push((name, param.symbol, param.typ, pseudo_id)); - } + // Atomic operations have side effects (memory ordering) + ExprKind::C11AtomicInit { .. } + | ExprKind::C11AtomicLoad { .. } + | ExprKind::C11AtomicStore { .. } + | ExprKind::C11AtomicExchange { .. } + | ExprKind::C11AtomicCompareExchangeStrong { .. } + | ExprKind::C11AtomicCompareExchangeWeak { .. } + | ExprKind::C11AtomicFetchAdd { .. } + | ExprKind::C11AtomicFetchSub { .. } + | ExprKind::C11AtomicFetchAnd { .. } + | ExprKind::C11AtomicFetchOr { .. } + | ExprKind::C11AtomicFetchXor { .. } + | ExprKind::C11AtomicThreadFence { .. } + | ExprKind::C11AtomicSignalFence { .. } => false, } + } - self.current_func = Some(ir_func); - - // Create entry block - let entry_bb = self.alloc_bb(); - self.switch_bb(entry_bb); + /// Resolve an incomplete struct/union type to its complete definition. + /// + /// When a struct is forward-declared (e.g., `struct foo;`) and later + /// defined, the forward declaration creates an incomplete TypeId. + /// Pointers to the forward-declared type still reference this incomplete + /// TypeId even after the struct is fully defined with a new TypeId. + /// + /// This method looks up the complete definition in the symbol table + /// using the struct's tag name, returning the complete TypeId if found. + pub(crate) fn resolve_struct_type(&self, type_id: TypeId) -> TypeId { + let typ = self.types.get(type_id); - // Entry instruction - self.emit(Instruction::new(Opcode::Entry)); + // Only try to resolve struct/union types + if typ.kind != TypeKind::Struct && typ.kind != TypeKind::Union { + return type_id; + } - // Handle va_list parameters: store the pointer value (not the struct) - for (name, symbol_id_opt, typ, arg_pseudo) in valist_params { - // va_list params are passed as pointers due to array decay at call site. - // Store the pointer value (8 bytes) to a local. - let ptr_type = self.types.pointer_to(typ); - let local_sym = self.alloc_pseudo(); - let sym = Pseudo::sym(local_sym, name.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(sym); - func.add_local(&name, local_sym, ptr_type, false, false, None, None); + // Check if this is an incomplete type with a tag + if let Some(ref composite) = typ.composite { + if composite.is_complete { + // Already complete, no resolution needed + return type_id; } - let ptr_size = self.types.size_bits(ptr_type); - self.emit(Instruction::store( - arg_pseudo, local_sym, 0, ptr_type, ptr_size, - )); - if let Some(symbol_id) = symbol_id_opt { - self.insert_local( - symbol_id, - LocalVarInfo { - sym: local_sym, - typ, // Keep original va_list type for type checking - vla_size_sym: None, - vla_elem_type: None, - vla_dim_syms: vec![], - is_indirect: true, // va_list param: local holds a pointer - }, - ); + if let Some(tag) = composite.tag { + // Look up the tag in the symbol table to find the complete type + if let Some(symbol) = self.symbols.lookup_tag(tag) { + // Return the complete type from the symbol table + return symbol.typ; + } } } - // Copy struct parameters to local storage so member access works - for (name, symbol_id_opt, typ, arg_pseudo) in struct_params { - // Create a symbol pseudo for this local variable (its address) - let local_sym = self.alloc_pseudo(); - let sym = Pseudo::sym(local_sym, name.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(sym); - let mods = self.types.modifiers(typ); - let is_volatile = mods.contains(TypeModifiers::VOLATILE); - let is_atomic = mods.contains(TypeModifiers::ATOMIC); - func.add_local(&name, local_sym, typ, is_volatile, is_atomic, None, None); - } - - let typ_size = self.types.size_bits(typ); - let is_aarch64 = self.target.arch == crate::target::Arch::Aarch64; - if typ_size > 128 && !is_aarch64 { - // x86-64: Large struct (> 16 bytes) passed by value on the stack. - // arg_pseudo is an IncomingArg pointing to the struct data on the stack. - // Use SymAddr to get the base address, then copy each 8-byte chunk. - let ptr_type = self.types.pointer_to(typ); - let addr_pseudo = self.alloc_reg_pseudo(); - self.emit(Instruction::sym_addr(addr_pseudo, arg_pseudo, ptr_type)); + // Couldn't resolve, return original + type_id + } - let struct_size = typ_size / 8; - let mut offset = 0i64; - while offset < struct_size as i64 { - let temp = self.alloc_reg_pseudo(); - self.emit(Instruction::load( - temp, - addr_pseudo, - offset, - self.types.long_id, - 64, - )); - self.emit(Instruction::store( - temp, - local_sym, - offset, - self.types.long_id, - 64, + /// Linearize an expression as an lvalue (get its address) + pub(crate) fn linearize_lvalue(&mut self, expr: &Expr) -> PseudoId { + match &expr.kind { + ExprKind::Ident(symbol_id) => { + let name_str = self.symbol_name(*symbol_id); + // For local variables, emit SymAddr to get the stack address + if let Some(local) = self.locals.get(symbol_id).cloned() { + // Check if this is a static local (sentinel value) + if local.sym.0 == u32::MAX { + // Static local - look up the global name + let key = format!("{}.{}", self.current_func_name, name_str); + if let Some(static_info) = self.static_locals.get(&key).cloned() { + let sym_id = self.alloc_pseudo(); + let pseudo = Pseudo::sym(sym_id, static_info.global_name); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); + } + let result = self.alloc_pseudo(); + self.emit(Instruction::sym_addr( + result, + sym_id, + self.types.pointer_to(static_info.typ), + )); + return result; + } else { + unreachable!("static local sentinel without static_locals entry"); + } + } + // Check if this local holds a pointer to the actual data + // (e.g., va_list parameters due to array-to-pointer decay at call site). + // If so, load the pointer instead of taking the address. + if local.is_indirect { + // The local stores a pointer; load and return it + let ptr_type = self.types.pointer_to(local.typ); + let result = self.alloc_pseudo(); + let size = self.types.size_bits(ptr_type); + self.emit(Instruction::load(result, local.sym, 0, ptr_type, size)); + return result; + } + + let result = self.alloc_pseudo(); + self.emit(Instruction::sym_addr( + result, + local.sym, + self.types.pointer_to(local.typ), )); - offset += 8; + result + } else if let Some(¶m_pseudo) = self.var_map.get(&name_str) { + // Parameter whose address is taken + let param_type = self.expr_type(expr); + let type_kind = self.types.kind(param_type); + + // va_list parameters are special: the parameter value IS already a pointer + // to the va_list structure (due to array-to-pointer decay at call site). + // Return the pointer value directly instead of spilling. + if type_kind == TypeKind::VaList { + return param_pseudo; + } + + // For other parameters, spill to local storage. + // Parameters are pass-by-value in the IR (Arg pseudos), but if + // their address is taken, we need to copy to a stack slot first. + let size = self.types.size_bits(param_type); + + // Create a local variable to hold the parameter value + let local_sym = self.alloc_pseudo(); + let local_pseudo = Pseudo::sym(local_sym, format!("{}_spill", name_str)); + if let Some(func) = &mut self.current_func { + func.add_pseudo(local_pseudo); + func.locals.insert( + format!("{}_spill", name_str), + super::LocalVar { + sym: local_sym, + typ: param_type, + is_volatile: false, + is_atomic: false, + decl_block: self.current_bb, + explicit_align: None, // parameter spill storage + }, + ); + } + + // Store the parameter value to the local + self.emit(Instruction::store( + param_pseudo, + local_sym, + 0, + param_type, + size, + )); + + // Update locals map so future accesses use the spilled location + self.insert_local( + *symbol_id, + LocalVarInfo { + sym: local_sym, + typ: param_type, + vla_size_sym: None, + vla_elem_type: None, + vla_dim_syms: vec![], + is_indirect: false, + }, + ); + + // Also update var_map to point to the local for future value accesses + // (so reads go through load instead of using the original Arg pseudo) + // Note: We leave var_map unchanged here because reads should use + // the stored value via load from the local. + + // Return address of the local + let result = self.alloc_pseudo(); + self.emit(Instruction::sym_addr( + result, + local_sym, + self.types.pointer_to(param_type), + )); + result + } else { + // Global variable - emit SymAddr to get its address + let sym_id = self.alloc_pseudo(); + let pseudo = Pseudo::sym(sym_id, name_str.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); + } + let result = self.alloc_pseudo(); + let typ = self.expr_type(expr); + self.emit(Instruction::sym_addr( + result, + sym_id, + self.types.pointer_to(typ), + )); + result } - } else if typ_size > 64 { - // Medium struct (9-16 bytes): arg_pseudo is a pointer (current behavior). - // Copy each 8-byte chunk through pointer dereference. - let struct_size = typ_size / 8; - let mut offset = 0i64; - while offset < struct_size as i64 { - let temp = self.alloc_reg_pseudo(); - self.emit(Instruction::load( - temp, - arg_pseudo, - offset, + } + ExprKind::Unary { + op: UnaryOp::Deref, + operand, + } => { + // *ptr as lvalue = ptr itself + self.linearize_expr(operand) + } + ExprKind::Member { + expr: inner, + member, + } => { + // s.m as lvalue = &s + offset(m) + let base = self.linearize_lvalue(inner); + let base_struct_type = self.expr_type(inner); + // Resolve if the struct type is incomplete (forward-declared) + let struct_type = self.resolve_struct_type(base_struct_type); + let member_info = + self.types + .find_member(struct_type, *member) + .unwrap_or_else(|| MemberInfo { + offset: 0, + typ: self.expr_type(expr), + bit_offset: None, + bit_width: None, + storage_unit_size: None, + }); + + if member_info.offset == 0 { + base + } else { + let offset_val = + self.emit_const(member_info.offset as i128, self.types.long_id); + let result = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Add, + result, + base, + offset_val, self.types.long_id, 64, )); - self.emit(Instruction::store( - temp, - local_sym, - offset, + result + } + } + ExprKind::Arrow { + expr: inner, + member, + } => { + // ptr->m as lvalue = ptr + offset(m) + let ptr = self.linearize_expr(inner); + let ptr_type = self.expr_type(inner); + let base_struct_type = self + .types + .base_type(ptr_type) + .unwrap_or_else(|| self.expr_type(expr)); + // Resolve if the struct type is incomplete (forward-declared) + let struct_type = self.resolve_struct_type(base_struct_type); + let member_info = + self.types + .find_member(struct_type, *member) + .unwrap_or_else(|| MemberInfo { + offset: 0, + typ: self.expr_type(expr), + bit_offset: None, + bit_width: None, + storage_unit_size: None, + }); + + if member_info.offset == 0 { + ptr + } else { + let offset_val = + self.emit_const(member_info.offset as i128, self.types.long_id); + let result = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Add, + result, + ptr, + offset_val, self.types.long_id, 64, )); - offset += 8; + result } - } else { - // Small struct: arg_pseudo contains the value directly - self.emit(Instruction::store(arg_pseudo, local_sym, 0, typ, typ_size)); } + ExprKind::Index { array, index } => { + // arr[idx] as lvalue = arr + idx * sizeof(elem) + // Handle commutative form: 0[arr] is equivalent to arr[0] + let array_type = self.expr_type(array); + let index_type = self.expr_type(index); - // Register as a local variable (only if named parameter) - if let Some(symbol_id) = symbol_id_opt { - self.insert_local( - symbol_id, - LocalVarInfo { - sym: local_sym, - typ, - vla_size_sym: None, - vla_elem_type: None, - vla_dim_syms: vec![], - is_indirect: false, - }, - ); - } - } + let array_kind = self.types.kind(array_type); + let (ptr_expr, idx_expr, idx_type) = + if array_kind == TypeKind::Pointer || array_kind == TypeKind::Array { + (array, index, index_type) + } else { + // Swap: index is actually the pointer/array + (index, array, array_type) + }; - // Setup local storage for complex parameters - // Complex types are passed in FP registers per ABI - the prologue codegen - // handles storing from XMM registers to local storage - for (name, symbol_id_opt, typ, _arg_pseudo, arg_idx) in complex_params { - // Create a symbol pseudo for this local variable (its address) - let local_sym = self.alloc_pseudo(); - let sym = Pseudo::sym(local_sym, name.clone()); - let typ_size_bytes = (self.types.size_bits(typ) / 8) as usize; - if let Some(func) = &mut self.current_func { - func.add_pseudo(sym); - let mods = self.types.modifiers(typ); - let is_volatile = mods.contains(TypeModifiers::VOLATILE); - let is_atomic = mods.contains(TypeModifiers::ATOMIC); - func.add_local(&name, local_sym, typ, is_volatile, is_atomic, None, None); - // Record for inliner: the backend prologue fills this local from - // registers; the inliner must generate an explicit copy instead. - func.implicit_param_copies.push(super::ImplicitParamCopy { - arg_index: arg_idx, - local_sym, - size_bytes: typ_size_bytes, - qword_type: self.types.long_id, - }); - } + let arr = self.linearize_expr(ptr_expr); + let idx = self.linearize_expr(idx_expr); + let elem_type = self.expr_type(expr); + let elem_size = self.types.size_bits(elem_type) / 8; + let elem_size_val = self.emit_const(elem_size as i128, self.types.long_id); - // Don't emit a store here - the prologue codegen handles storing - // from XMM0+XMM1/XMM2+XMM3/etc to local storage + // Sign-extend index to 64-bit for proper pointer arithmetic (negative indices) + let idx_extended = self.emit_convert(idx, idx_type, self.types.long_id); - // Register as a local variable for name lookup (only if named parameter) - if let Some(symbol_id) = symbol_id_opt { - self.insert_local( - symbol_id, - LocalVarInfo { - sym: local_sym, - typ, - vla_size_sym: None, - vla_elem_type: None, - vla_dim_syms: vec![], - is_indirect: false, - }, - ); - } - } + let offset = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Mul, + offset, + idx_extended, + elem_size_val, + self.types.long_id, + 64, + )); - // Store scalar parameters to local storage for SSA-correct reassignment handling - // This ensures that if a parameter is reassigned inside a branch, phi nodes - // are properly inserted at merge points. - for (name, symbol_id_opt, typ, arg_pseudo) in scalar_params { - // Create a symbol pseudo for this local variable (its address) - let local_sym = self.alloc_pseudo(); - let sym = Pseudo::sym(local_sym, name.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(sym); - let mods = self.types.modifiers(typ); - let is_volatile = mods.contains(TypeModifiers::VOLATILE); - let is_atomic = mods.contains(TypeModifiers::ATOMIC); - func.add_local(&name, local_sym, typ, is_volatile, is_atomic, None, None); + let addr = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Add, + addr, + arr, + offset, + self.types.long_id, + 64, + )); + addr } + ExprKind::CompoundLiteral { typ, elements } => { + // Compound literal as lvalue: create it and return its address + // This is used for &(struct S){...} and large struct assignment like *p = (struct S){...} + let sym_id = self.alloc_pseudo(); + let unique_name = format!(".compound_literal.{}", sym_id.0); + let sym = Pseudo::sym(sym_id, unique_name.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(sym); + func.add_local( + &unique_name, + sym_id, + *typ, + false, + false, + self.current_bb, + None, + ); + } - // Store the incoming argument value to the local - let typ_size = self.types.size_bits(typ); - self.emit(Instruction::store(arg_pseudo, local_sym, 0, typ, typ_size)); + // For compound literals with partial initialization, C99 6.7.8p21 requires + // zero-initialization of all subobjects not explicitly initialized. + // Zero the entire compound literal first, then initialize specific members. + let type_kind = self.types.kind(*typ); + if type_kind == TypeKind::Struct + || type_kind == TypeKind::Union + || type_kind == TypeKind::Array + { + self.emit_aggregate_zero(sym_id, *typ); + } - // Register as a local variable for name lookup (only if named parameter) - if let Some(symbol_id) = symbol_id_opt { - self.insert_local( - symbol_id, - LocalVarInfo { - sym: local_sym, - typ, - vla_size_sym: None, - vla_elem_type: None, - vla_dim_syms: vec![], - is_indirect: false, - }, - ); + self.linearize_init_list(sym_id, *typ, elements); + + // Return address of the compound literal + let result = self.alloc_reg_pseudo(); + let ptr_type = self.types.pointer_to(*typ); + self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); + result + } + _ => { + // Fallback: just evaluate the expression (shouldn't happen for valid lvalues) + self.linearize_expr(expr) } } + } - // Linearize body - self.linearize_stmt(&func.body); + /// Linearize a type cast expression + pub(crate) fn linearize_cast(&mut self, inner_expr: &Expr, cast_type: TypeId) -> PseudoId { + let src = self.linearize_expr(inner_expr); + let src_type = self.expr_type(inner_expr); - // Ensure function ends with a return - if !self.is_terminated() { - if ret_kind == TypeKind::Void { - self.emit(Instruction::ret(None)); - } else { - // Return 0 as default, widened to actual return type - let ret_type = func.return_type; - let ret_size = self.types.size_bits(ret_type).max(32); - let zero = self.emit_const(0, ret_type); - self.emit(Instruction::ret_typed(Some(zero), ret_type, ret_size)); - } - } + // Emit conversion if needed + let src_is_float = self.types.is_float(src_type); + let dst_is_float = self.types.is_float(cast_type); - // Run SSA conversion if enabled - if self.run_ssa { - if let Some(ref mut ir_func) = self.current_func { - ssa_convert(ir_func, self.types); - // Note: ssa_convert sets ir_func.next_pseudo to account for phi nodes + if src_is_float && !dst_is_float { + // Float to integer conversion + let result = self.alloc_reg_pseudo(); + // FCvtS for signed int, FCvtU for unsigned + let opcode = if self.types.is_unsigned(cast_type) { + Opcode::FCvtU + } else { + Opcode::FCvtS + }; + let dst_size = self.types.size_bits(cast_type); + let mut insn = Instruction::new(opcode) + .with_target(result) + .with_src(src) + .with_type_and_size(cast_type, dst_size); + insn.src_size = self.types.size_bits(src_type); + insn.src_typ = Some(src_type); + self.emit(insn); + result + } else if !src_is_float && dst_is_float { + // Integer to float conversion + let result = self.alloc_reg_pseudo(); + // SCvtF for signed int, UCvtF for unsigned + let opcode = if self.types.is_unsigned(src_type) { + Opcode::UCvtF + } else { + Opcode::SCvtF + }; + let dst_size = self.types.size_bits(cast_type); + let mut insn = Instruction::new(opcode) + .with_target(result) + .with_src(src) + .with_type_and_size(cast_type, dst_size); + insn.src_size = self.types.size_bits(src_type); + insn.src_typ = Some(src_type); + self.emit(insn); + result + } else if src_is_float && dst_is_float { + // Float to float conversion (e.g., float to double) + let src_size = self.types.size_bits(src_type); + let dst_size = self.types.size_bits(cast_type); + if src_size != dst_size { + let result = self.alloc_reg_pseudo(); + let mut insn = Instruction::new(Opcode::FCvtF) + .with_target(result) + .with_src(src) + .with_type_and_size(cast_type, dst_size); + insn.src_size = src_size; + insn.src_typ = Some(src_type); + self.emit(insn); + result + } else { + src // Same size, no conversion needed } } else { - // Only set next_pseudo if SSA was NOT run (SSA sets its own) - if let Some(ref mut ir_func) = self.current_func { - ir_func.next_pseudo = self.next_pseudo; - } + // Integer to integer conversion + // Use emit_convert for proper type conversions including _Bool + self.emit_convert(src, src_type, cast_type) } + } - // Pop function-level scope - self.pop_scope(); - - // Add function to module - if let Some(ir_func) = self.current_func.take() { - self.module.add_function(ir_func); - } + /// Linearize a struct member access expression (e.g., s.member) + pub(crate) fn linearize_member( + &mut self, + expr: &Expr, + inner_expr: &Expr, + member: StringId, + ) -> PseudoId { + let base = self.linearize_lvalue(inner_expr); + let base_struct_type = self.expr_type(inner_expr); + let struct_type = self.resolve_struct_type(base_struct_type); + self.emit_member_access(base, struct_type, member, self.expr_type(expr)) } - // ======================================================================== - // Statement linearization - // ======================================================================== + /// Linearize a pointer member access expression (e.g., p->member) + pub(crate) fn linearize_arrow( + &mut self, + expr: &Expr, + inner_expr: &Expr, + member: StringId, + ) -> PseudoId { + let ptr = self.linearize_expr(inner_expr); + let ptr_type = self.expr_type(inner_expr); + let base_struct_type = self + .types + .base_type(ptr_type) + .unwrap_or_else(|| self.expr_type(expr)); + let struct_type = self.resolve_struct_type(base_struct_type); + self.emit_member_access(ptr, struct_type, member, self.expr_type(expr)) + } - /// Emit large struct return via hidden pointer (sret) - fn emit_sret_return(&mut self, e: &Expr, sret_ptr: PseudoId, struct_size: u32) { - let src_addr = self.linearize_lvalue(e); - let struct_bytes = struct_size as i64 / 8; - let mut byte_offset = 0i64; + /// Shared logic for member access (both `.` and `->`). + /// `base` is the address of the struct (for `.`) or the pointer value (for `->`). + pub(crate) fn emit_member_access( + &mut self, + base: PseudoId, + struct_type: TypeId, + member: StringId, + fallback_type: TypeId, + ) -> PseudoId { + let member_info = self + .types + .find_member(struct_type, member) + .unwrap_or(MemberInfo { + offset: 0, + typ: fallback_type, + bit_offset: None, + bit_width: None, + storage_unit_size: None, + }); - while byte_offset < struct_bytes { - let temp = self.alloc_reg_pseudo(); - self.emit(Instruction::load( - temp, - src_addr, - byte_offset, - self.types.long_id, - 64, - )); - self.emit(Instruction::store( - temp, - sret_ptr, - byte_offset, - self.types.long_id, - 64, - )); - byte_offset += 8; - } + // If member type is an array, return the address (arrays decay to pointers) + if self.types.kind(member_info.typ) == TypeKind::Array { + if member_info.offset == 0 { + base + } else { + let result = self.alloc_pseudo(); + let offset_val = self.emit_const(member_info.offset as i128, self.types.long_id); + self.emit(Instruction::binop( + Opcode::Add, + result, + base, + offset_val, + self.types.long_id, + 64, + )); + result + } + } else if let (Some(bit_offset), Some(bit_width), Some(storage_size)) = ( + member_info.bit_offset, + member_info.bit_width, + member_info.storage_unit_size, + ) { + // Bitfield read + self.emit_bitfield_load( + base, + member_info.offset, + bit_offset, + bit_width, + storage_size, + member_info.typ, + ) + } else { + let size = self.types.size_bits(member_info.typ); + let member_kind = self.types.kind(member_info.typ); - self.emit(Instruction::ret_typed( - Some(sret_ptr), - self.types.void_ptr_id, - 64, - )); + // Large structs (size > 64) can't be loaded into registers - return address + if (member_kind == TypeKind::Struct || member_kind == TypeKind::Union) && size > 64 { + if member_info.offset == 0 { + base + } else { + let result = self.alloc_pseudo(); + let offset_val = + self.emit_const(member_info.offset as i128, self.types.long_id); + self.emit(Instruction::binop( + Opcode::Add, + result, + base, + offset_val, + self.types.long_id, + 64, + )); + result + } + } else { + let result = self.alloc_pseudo(); + self.emit(Instruction::load( + result, + base, + member_info.offset as i64, + member_info.typ, + size, + )); + result + } + } } - /// Emit two-register struct return (9-16 bytes) - fn emit_two_reg_return(&mut self, e: &Expr, ret_type: TypeId) { - let src_addr = self.linearize_lvalue(e); - let struct_size = self.types.size_bits(ret_type); - - // Load first 8 bytes - let low_temp = self.alloc_reg_pseudo(); - self.emit(Instruction::load( - low_temp, - src_addr, - 0, - self.types.long_id, - 64, - )); - - // Load second portion (remaining bytes, up to 8) - let high_temp = self.alloc_reg_pseudo(); - let high_size = std::cmp::min(64, struct_size - 64); - self.emit(Instruction::load( - high_temp, - src_addr, - 8, - self.types.long_id, - high_size, - )); + /// Linearize an array index expression (e.g., arr[i]) + pub(crate) fn linearize_index(&mut self, expr: &Expr, array: &Expr, index: &Expr) -> PseudoId { + // In C, a[b] is defined as *(a + b), so either operand can be the pointer + // Handle commutative form: 0[arr] is equivalent to arr[0] + let array_type = self.expr_type(array); + let index_type = self.expr_type(index); - // Emit return with both values and ABI info for two-register return - let mut ret_insn = Instruction::ret_typed(Some(low_temp), ret_type, struct_size); - ret_insn.src.push(high_temp); - let abi = get_abi_for_conv(self.current_calling_conv, self.target); - let ret_class = abi.classify_return(ret_type, self.types); - ret_insn.abi_info = Some(Box::new(CallAbiInfo::new(vec![], ret_class))); - self.emit(ret_insn); - } + let array_kind = self.types.kind(array_type); + let (ptr_expr, idx_expr, idx_type) = + if array_kind == TypeKind::Pointer || array_kind == TypeKind::Array { + (array, index, index_type) + } else { + // Swap: index is actually the pointer/array + (index, array, array_type) + }; - fn linearize_stmt(&mut self, stmt: &Stmt) { - match stmt { - Stmt::Empty => {} + let arr = self.linearize_expr(ptr_expr); + let idx = self.linearize_expr(idx_expr); - Stmt::Expr(expr) => { - self.linearize_expr(expr); - } + // Get element type from the expression type + let elem_type = self.expr_type(expr); + let ptr_typ = self.types.long_id; - Stmt::Block(items) => { - self.push_scope(); + // Check if we're indexing a VLA's outer dimension + // This requires runtime stride computation. + // For int arr[n][m], accessing arr[i] needs stride = m * sizeof(int) + let elem_size_val = if let ExprKind::Ident(symbol_id) = &ptr_expr.kind { + if let Some(info) = self.locals.get(symbol_id).cloned() { + // Check if this is a multi-dimensional VLA AND elem_type is an array + // (meaning we're accessing an outer dimension, not the innermost) + if info.vla_dim_syms.len() > 1 && self.types.kind(elem_type) == TypeKind::Array { + // Compute runtime stride: + // stride = (product of inner dimensions) * sizeof(innermost element) + // For int arr[n][m] accessing arr[i]: stride = m * sizeof(int) + // For int arr[n][m][k] accessing arr[i]: stride = m * k * sizeof(int) - for item in items { - match item { - BlockItem::Declaration(decl) => self.linearize_local_decl(decl), - BlockItem::Statement(s) => self.linearize_stmt(s), + // Get innermost element type and its size + let mut innermost = elem_type; + while self.types.kind(innermost) == TypeKind::Array { + innermost = self.types.base_type(innermost).unwrap_or(self.types.int_id); } - } - - self.pop_scope(); - } - - Stmt::If { - cond, - then_stmt, - else_stmt, - } => { - self.linearize_if(cond, then_stmt, else_stmt.as_deref()); - } - - Stmt::While { cond, body } => { - self.linearize_while(cond, body); - } + let innermost_size = self.types.size_bytes(innermost) as i64; - Stmt::DoWhile { body, cond } => { - self.linearize_do_while(body, cond); - } + // Load all dimension sizes except the first (outermost we're indexing) + // and multiply them together + let mut stride: Option = None; + for dim_sym in info.vla_dim_syms.iter().skip(1) { + // Load the dimension size + let dim_val = self.alloc_pseudo(); + let load_insn = + Instruction::load(dim_val, *dim_sym, 0, self.types.ulong_id, 64); + self.emit(load_insn); - Stmt::For { - init, - cond, - post, - body, - } => { - self.linearize_for(init.as_ref(), cond.as_ref(), post.as_ref(), body); - } + stride = Some(match stride { + None => dim_val, + Some(prev) => { + let result = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Mul, + result, + prev, + dim_val, + ptr_typ, + 64, + )); + result + } + }); + } - Stmt::Return(expr) => { - if let Some(e) = expr { - let expr_typ = self.expr_type(e); - // Get the function's actual return type for proper conversion - let func_ret_type = self - .current_func - .as_ref() - .map(|f| f.return_type) - .unwrap_or(expr_typ); - - if let Some(sret_ptr) = self.struct_return_ptr { - self.emit_sret_return(e, sret_ptr, self.struct_return_size); - } else if let Some(ret_type) = self.two_reg_return_type { - self.emit_two_reg_return(e, ret_type); - } else if self.types.is_complex(expr_typ) { - let addr = self.linearize_lvalue(e); - let typ_size = self.types.size_bits(func_ret_type); - self.emit(Instruction::ret_typed(Some(addr), func_ret_type, typ_size)); - } else { - let val = self.linearize_expr(e); - // Convert expression value to function's return type if needed - let converted_val = if expr_typ != func_ret_type - && self.types.kind(func_ret_type) != TypeKind::Void - { - self.emit_convert(val, expr_typ, func_ret_type) - } else { - val - }; - // Function types decay to pointers when returned - let typ_size = if self.types.kind(func_ret_type) == TypeKind::Function { - self.target.pointer_width - } else { - self.types.size_bits(func_ret_type) - }; - self.emit(Instruction::ret_typed( - Some(converted_val), - func_ret_type, - typ_size, - )); + // Multiply by sizeof(innermost element) + let innermost_size_val = + self.emit_const(innermost_size as i128, self.types.long_id); + match stride { + Some(s) => { + let result = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Mul, + result, + s, + innermost_size_val, + ptr_typ, + 64, + )); + result + } + None => innermost_size_val, } } else { - self.emit(Instruction::ret(None)); + // Not a multi-dimensional VLA or accessing innermost dimension + // Use compile-time size + let elem_size = self.types.size_bits(elem_type) / 8; + self.emit_const(elem_size as i128, self.types.long_id) } + } else { + // Variable not found in locals (global or something else) + let elem_size = self.types.size_bits(elem_type) / 8; + self.emit_const(elem_size as i128, self.types.long_id) } + } else { + // Not indexing an identifier directly (e.g., arr[i][j] where arr[i] is an Index) + // Use compile-time size + let elem_size = self.types.size_bits(elem_type) / 8; + self.emit_const(elem_size as i128, self.types.long_id) + }; - Stmt::Break => { - if let Some(&target) = self.break_targets.last() { - if let Some(current) = self.current_bb { - self.emit(Instruction::br(target)); - self.link_bb(current, target); - } - } - } - - Stmt::Continue => { - if let Some(&target) = self.continue_targets.last() { - if let Some(current) = self.current_bb { - self.emit(Instruction::br(target)); - self.link_bb(current, target); - } - } - } - - Stmt::Goto(label) => { - let label_str = self.str(*label).to_string(); - let target = self.get_or_create_label(&label_str); - if let Some(current) = self.current_bb { - self.emit(Instruction::br(target)); - self.link_bb(current, target); - } - - // Set current_bb to None - any subsequent code until a label is dead - // emit() will safely skip when current_bb is None - self.current_bb = None; - } - - Stmt::Label { name, stmt } => { - let name_str = self.str(*name).to_string(); - let label_bb = self.get_or_create_label(&name_str); - - // If current block is not terminated, branch to label - if !self.is_terminated() { - if let Some(current) = self.current_bb { - self.emit(Instruction::br(label_bb)); - self.link_bb(current, label_bb); - } - } - - self.switch_bb(label_bb); - self.linearize_stmt(stmt); - } + // Sign-extend index to 64-bit for proper pointer arithmetic (negative indices) + let idx_extended = self.emit_convert(idx, idx_type, self.types.long_id); - Stmt::Switch { expr, body } => { - self.linearize_switch(expr, body); - } + let offset = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Mul, + offset, + idx_extended, + elem_size_val, + ptr_typ, + 64, + )); - Stmt::Case(_) | Stmt::Default => { - // Case/Default labels are handled by linearize_switch - // If we encounter them outside a switch, ignore them - } + let addr = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Add, + addr, + arr, + offset, + ptr_typ, + 64, + )); - Stmt::Asm { - template, - outputs, - inputs, - clobbers, - goto_labels, - } => { - self.linearize_asm(template, outputs, inputs, clobbers, goto_labels); + // If element type is an array, just return the address (arrays decay to pointers) + let elem_kind = self.types.kind(elem_type); + if elem_kind == TypeKind::Array { + addr + } else { + let size = self.types.size_bits(elem_type); + // Large structs/unions (> 64 bits) can't be loaded into registers - return address + // Assignment will handle the actual copy via emit_assign's large struct handling + if (elem_kind == TypeKind::Struct || elem_kind == TypeKind::Union) && size > 64 { + addr + } else { + let result = self.alloc_pseudo(); + self.emit(Instruction::load(result, addr, 0, elem_type, size)); + result } } } - fn linearize_local_decl(&mut self, decl: &Declaration) { - for declarator in &decl.declarators { - let typ = declarator.typ; - - // Check if this is a static local variable - if declarator.storage_class.contains(TypeModifiers::STATIC) { - // Static local: create a global with unique name - self.linearize_static_local(declarator); - continue; - } - - // Check if this is a VLA (Variable Length Array) - if !declarator.vla_sizes.is_empty() { - // C99 6.7.8: VLAs cannot have initializers - if declarator.init.is_some() { - if let Some(pos) = self.current_pos { - error(pos, "variable length arrays cannot have initializers"); - } - } - self.linearize_vla_decl(declarator); - continue; - } + /// Linearize a function call expression + pub(crate) fn linearize_call( + &mut self, + expr: &Expr, + func_expr: &Expr, + args: &[Expr], + ) -> PseudoId { + // Determine if this is a direct or indirect call. + // We need to check the TYPE of the function expression: + // - If it's TypeKind::Function, it's a direct call to a function + // - If it's TypeKind::Pointer to Function, it's an indirect call through function pointer + let is_function_pointer = func_expr.typ.is_some_and(|t| { + let typ = self.types.get(t); + typ.kind == TypeKind::Pointer + }); - // Create a symbol pseudo for this local variable (its address) - // Use unique name (name.id) to distinguish shadowed variables - let sym_id = self.alloc_pseudo(); - let name_str = self.symbol_name(declarator.symbol); - let unique_name = format!("{}.{}", name_str, sym_id.0); - let sym = Pseudo::sym(sym_id, unique_name.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(sym); - // Register with function's local variable tracking for SSA - // Pass the current basic block as the declaration block for scope-aware phi placement - let mods = self.types.modifiers(typ); - let is_volatile = mods.contains(TypeModifiers::VOLATILE); - let is_atomic = mods.contains(TypeModifiers::ATOMIC); - func.add_local( - &unique_name, - sym_id, - typ, - is_volatile, - is_atomic, - self.current_bb, - declarator.explicit_align, - ); + let (func_name, indirect_target) = match &func_expr.kind { + ExprKind::Ident(symbol_id) if !is_function_pointer => { + // Direct call to named function (not a function pointer variable) + (self.symbol_name(*symbol_id), None) } - - // Track in linearizer's locals map using SymbolId as key - self.insert_local( - declarator.symbol, - LocalVarInfo { - sym: sym_id, - typ, - vla_size_sym: None, - vla_elem_type: None, - vla_dim_syms: vec![], - is_indirect: false, - }, - ); - - // If there's an initializer, emit Store(s) - if let Some(init) = &declarator.init { - if let ExprKind::InitList { elements } = &init.kind { - // Handle initializer list for arrays and structs - // C99 6.7.8p19: uninitialized members must be zero-initialized - // Zero the entire aggregate first, then apply explicit initializers - let type_kind = self.types.kind(typ); - if type_kind == TypeKind::Struct - || type_kind == TypeKind::Union - || type_kind == TypeKind::Array - { - self.emit_aggregate_zero(sym_id, typ); - } - self.linearize_init_list(sym_id, typ, elements); - } else if let ExprKind::StringLit(s) = &init.kind { - // String literal initialization of char array - // Copy the string bytes to the local array - if self.types.kind(typ) == TypeKind::Array { - let elem_type = self.types.base_type(typ).unwrap_or(self.types.char_id); - let elem_size = self.types.size_bits(elem_type); - - // Copy each byte from string literal to local array - for (i, byte) in s.bytes().enumerate() { - let byte_val = self.emit_const(byte as i128, elem_type); - self.emit(Instruction::store( - byte_val, sym_id, i as i64, elem_type, elem_size, - )); - } - // Store null terminator - let null_val = self.emit_const(0, elem_type); - self.emit(Instruction::store( - null_val, - sym_id, - s.chars().count() as i64, - elem_type, - elem_size, - )); - } else { - // Pointer initialized with string literal - store the address - let val = self.linearize_expr(init); - let init_type = self.expr_type(init); - let converted = self.emit_convert(val, init_type, typ); - let size = self.types.size_bits(typ); - self.emit(Instruction::store(converted, sym_id, 0, typ, size)); - } - } else if let ExprKind::WideStringLit(s) = &init.kind { - // Wide string literal initialization of wchar_t array - // Copy the wide string chars to the local array (4 bytes each) - if self.types.kind(typ) == TypeKind::Array { - let elem_type = self.types.base_type(typ).unwrap_or(self.types.int_id); - let elem_size = self.types.size_bits(elem_type); - let elem_bytes = (elem_size / 8) as i64; - - // Copy each wchar_t from wide string literal to local array - for (i, ch) in s.chars().enumerate() { - let ch_val = self.emit_const(ch as i128, elem_type); - self.emit(Instruction::store( - ch_val, - sym_id, - (i as i64) * elem_bytes, - elem_type, - elem_size, - )); - } - // Store null terminator - let null_val = self.emit_const(0, elem_type); - self.emit(Instruction::store( - null_val, - sym_id, - (s.chars().count() as i64) * elem_bytes, - elem_type, - elem_size, - )); + ExprKind::Unary { + op: UnaryOp::Deref, + operand, + } => { + // Explicit dereference form: (*fp)(args) or (*fpp)(args) + // Check the type of the operand to determine behavior: + // - If operand is function pointer (*fn): call through operand value + // - If operand is pointer-to-function-pointer (**fn): dereference first + let operand_type = self.expr_type(operand); + let operand_kind = self.types.kind(operand_type); + if operand_kind == TypeKind::Pointer { + // Check what it points to + let base_type = self.types.base_type(operand_type); + let base_kind = base_type.map(|t| self.types.kind(t)); + if base_kind == Some(TypeKind::Function) { + // Operand is function pointer - use its value directly + let func_addr = self.linearize_expr(operand); + ("".to_string(), Some(func_addr)) } else { - // Pointer initialized with wide string literal - store the address - let val = self.linearize_expr(init); - let init_type = self.expr_type(init); - let converted = self.emit_convert(val, init_type, typ); - let size = self.types.size_bits(typ); - self.emit(Instruction::store(converted, sym_id, 0, typ, size)); + // Operand is pointer-to-pointer - dereference to get function pointer + let func_addr = self.linearize_expr(func_expr); + ("".to_string(), Some(func_addr)) } - } else if self.types.is_complex(typ) { - // Complex type initialization - linearize_expr returns an address - // to a temp containing the complex value. Copy from temp to local. - let value_addr = self.linearize_expr(init); - let base_typ = self.types.complex_base(typ); - let base_bits = self.types.size_bits(base_typ); - let base_bytes = (base_bits / 8) as i64; - - // Load real and imag parts from source temp - let val_real = self.alloc_pseudo(); - let val_imag = self.alloc_pseudo(); - self.emit(Instruction::load( - val_real, value_addr, 0, base_typ, base_bits, - )); - self.emit(Instruction::load( - val_imag, value_addr, base_bytes, base_typ, base_bits, - )); - - // Store to local variable - self.emit(Instruction::store(val_real, sym_id, 0, base_typ, base_bits)); - self.emit(Instruction::store( - val_imag, sym_id, base_bytes, base_typ, base_bits, - )); } else { - // Check for large struct/union initialization (> 64 bits) - // linearize_expr returns an address for large aggregates - let type_kind = self.types.kind(typ); - let type_size = self.types.size_bits(typ); - if (type_kind == TypeKind::Struct || type_kind == TypeKind::Union) - && type_size > 64 - { - // Large struct/union init - source is an address, do block copy - let value_addr = self.linearize_expr(init); - let type_size_bytes = type_size / 8; - - self.emit_block_copy(sym_id, value_addr, type_size_bytes as i64); - } else { - // Simple scalar initializer - let val = self.linearize_expr(init); - // Convert the value to the target type (important for _Bool normalization) - let init_type = self.expr_type(init); - let converted = self.emit_convert(val, init_type, typ); - let size = self.types.size_bits(typ); - self.emit(Instruction::store(converted, sym_id, 0, typ, size)); - } + // Unknown case - try to linearize the full expression + let func_addr = self.linearize_expr(func_expr); + ("".to_string(), Some(func_addr)) } } - } - } - - /// Linearize a VLA (Variable Length Array) declaration - /// - /// VLAs are allocated on the stack at runtime using Alloca. - /// The size is computed as: product of all dimension sizes * sizeof(element_type) - /// - /// Unlike regular arrays where the symbol is the address of stack memory, - /// for VLAs we store the Alloca result (pointer) and then access it through - /// a pointer load. We create a pointer variable to hold the VLA address. - /// - /// We also create hidden local variables to store the dimension sizes - /// so that sizeof(vla) can be computed at runtime. - fn linearize_vla_decl(&mut self, declarator: &crate::parse::ast::InitDeclarator) { - let typ = declarator.typ; - - // Get the element type by stripping only VLA dimensions from the array type. - // For int arr[n][4], vla_sizes has 1 element, so we strip 1 dimension to get int[4]. - // For int arr[n][m], vla_sizes has 2 elements, so we strip 2 dimensions to get int. - let num_vla_dims = declarator.vla_sizes.len(); - let mut elem_type = typ; - for _ in 0..num_vla_dims { - if self.types.kind(elem_type) == TypeKind::Array { - elem_type = self.types.base_type(elem_type).unwrap_or(self.types.int_id); + _ => { + // Indirect call through function pointer variable: fp(args) + // This includes identifiers that are function pointer variables + let func_addr = self.linearize_expr(func_expr); + ("".to_string(), Some(func_addr)) } - } - let elem_size = self.types.size_bytes(elem_type) as i64; - - let ulong_type = self.types.ulong_id; + }; - // Evaluate all VLA size expressions, store each in a hidden local, - // and compute total element count. - // For int arr[n][m], we store n and m separately (for stride computation) - // and compute total_count = n * m. - let mut vla_dim_syms: Vec = Vec::new(); - let mut total_count: Option = None; + let typ = self.expr_type(expr); // Use evaluated type (function return type) - for (dim_idx, vla_size_expr) in declarator.vla_sizes.iter().enumerate() { - let dim_size = self.linearize_expr(vla_size_expr); + // Check if this is a variadic function call and if it's noreturn + // If the function expression has a type, check its variadic and noreturn flags + let (variadic_arg_start, is_noreturn_call) = if let Some(func_type) = func_expr.typ { + let ft = self.types.get(func_type); + let variadic = if ft.variadic { + // Variadic args start after the fixed parameters + ft.params.as_ref().map(|p| p.len()) + } else { + None + }; + (variadic, ft.noreturn) + } else { + (None, false) // No type info, assume non-variadic and returns + }; - // Create a hidden local to store this dimension's size - // This is needed for runtime stride computation in multi-dimensional VLAs - let dim_sym_id = self.alloc_pseudo(); - let decl_name = self.symbol_name(declarator.symbol); - let dim_var_name = format!("__vla_dim{}_{}.{}", dim_idx, decl_name, dim_sym_id.0); - let dim_sym = Pseudo::sym(dim_sym_id, dim_var_name.clone()); + // Check if function returns a large struct or complex type + // Large structs: allocate space and pass address as hidden first argument + // Complex types: allocate local storage for result (needs stack for 16-byte value) + // Two-register structs (9-16 bytes): allocate local storage, codegen stores two regs + let typ_kind = self.types.kind(typ); + let struct_size_bits = self.types.size_bits(typ); + let returns_large_struct = (typ_kind == TypeKind::Struct || typ_kind == TypeKind::Union) + && struct_size_bits > self.target.max_aggregate_register_bits; + let returns_two_reg_struct = (typ_kind == TypeKind::Struct || typ_kind == TypeKind::Union) + && struct_size_bits > 64 + && struct_size_bits <= 128 + && !returns_large_struct; + let returns_complex = self.types.is_complex(typ); + let (result_sym, mut arg_vals, mut arg_types_vec) = if returns_large_struct { + // Allocate local storage for the return value + let sret_sym = self.alloc_pseudo(); + let sret_pseudo = Pseudo::sym(sret_sym, format!("__sret_{}", sret_sym.0)); if let Some(func) = &mut self.current_func { - func.add_pseudo(dim_sym); + func.add_pseudo(sret_pseudo); + // Internal sret storage is never volatile or atomic func.add_local( - &dim_var_name, - dim_sym_id, - ulong_type, + format!("__sret_{}", sret_sym.0), + sret_sym, + typ, false, // not volatile false, // not atomic self.current_bb, @@ -2648,5846 +2132,2114 @@ impl<'a> Linearizer<'a> { ); } - // Widen dimension size to 64-bit before storing - let dim_expr_typ = vla_size_expr.typ.unwrap_or(self.types.int_id); - let dim_size = self.emit_convert(dim_size, dim_expr_typ, ulong_type); - let store_dim_insn = Instruction::store(dim_size, dim_sym_id, 0, ulong_type, 64); - self.emit(store_dim_insn); - vla_dim_syms.push(dim_sym_id); - - // Update running total count - total_count = Some(match total_count { - None => dim_size, - Some(prev) => { - // Multiply: prev * dim_size - let result = self.alloc_pseudo(); - let mul_insn = Instruction::new(Opcode::Mul) - .with_target(result) - .with_src(prev) - .with_src(dim_size) - .with_size(64) - .with_type(self.types.ulong_id); - self.emit(mul_insn); - result - } - }); - } - let num_elements = total_count.expect("VLA must have at least one size expression"); - - // Create a hidden local variable to store the total number of elements - // This is needed for sizeof(vla) to work at runtime - let size_sym_id = self.alloc_pseudo(); - let vla_name = self.symbol_name(declarator.symbol); - let size_var_name = format!("__vla_size_{}.{}", vla_name, size_sym_id.0); - let size_sym = Pseudo::sym(size_sym_id, size_var_name.clone()); - - if let Some(func) = &mut self.current_func { - func.add_pseudo(size_sym); - func.add_local( - &size_var_name, - size_sym_id, - ulong_type, - false, // not volatile - false, // not atomic - self.current_bb, - None, // no explicit alignment - ); - } - - // Store num_elements into the hidden size variable - let store_size_insn = Instruction::store(num_elements, size_sym_id, 0, ulong_type, 64); - self.emit(store_size_insn); - - // Compute total size in bytes: num_elements * sizeof(element) - let elem_size_const = self.emit_const(elem_size as i128, self.types.long_id); - let total_size = self.alloc_pseudo(); - let mul_insn = Instruction::new(Opcode::Mul) - .with_target(total_size) - .with_src(num_elements) - .with_src(elem_size_const) - .with_size(64) - .with_type(self.types.ulong_id); - self.emit(mul_insn); - - // Emit Alloca instruction to allocate stack space - let alloca_result = self.alloc_pseudo(); - let alloca_insn = Instruction::new(Opcode::Alloca) - .with_target(alloca_result) - .with_src(total_size) - .with_type_and_size(self.types.void_ptr_id, 64); - self.emit(alloca_insn); - - // Create a symbol pseudo for the VLA pointer variable - // This symbol stores the pointer to the VLA memory (like a pointer variable) - let sym_id = self.alloc_pseudo(); - let sym_name = self.symbol_name(declarator.symbol); - let unique_name = format!("{}.{}", sym_name, sym_id.0); - let sym = Pseudo::sym(sym_id, unique_name.clone()); - - // Create a pointer type for the VLA (pointer to element type) - let ptr_type = self.types.pointer_to(elem_type); - - if let Some(func) = &mut self.current_func { - func.add_pseudo(sym); - // Register as a pointer variable, not as the array type - let mods = self.types.modifiers(typ); - let is_volatile = mods.contains(TypeModifiers::VOLATILE); - let is_atomic = mods.contains(TypeModifiers::ATOMIC); - func.add_local( - &unique_name, - sym_id, - ptr_type, - is_volatile, - is_atomic, - self.current_bb, - declarator.explicit_align, // VLA explicit alignment - ); - } - - // Store the Alloca result (pointer) into the VLA symbol - let store_insn = Instruction::store(alloca_result, sym_id, 0, ptr_type, 64); - self.emit(store_insn); - - // Track in linearizer's locals map with pointer type and VLA size info - // This makes arr[i] behave like ptr[i] - load ptr, then offset - self.insert_local( - declarator.symbol, - LocalVarInfo { - sym: sym_id, - typ: ptr_type, - vla_size_sym: Some(size_sym_id), - vla_elem_type: Some(elem_type), - vla_dim_syms, - is_indirect: false, - }, - ); - } + // Get address of the allocated space + let sret_addr = self.alloc_reg_pseudo(); + self.emit(Instruction::sym_addr( + sret_addr, + sret_sym, + self.types.pointer_to(typ), + )); - /// Linearize a static local variable declaration - /// - /// Static locals have static storage duration but no linkage. - /// They are implemented as globals with unique names like `funcname.varname.N`. - /// Initialization happens once at program start (compile-time). - fn linearize_static_local(&mut self, declarator: &crate::parse::ast::InitDeclarator) { - let name_str = self.symbol_name(declarator.symbol); - - // C99 6.7.4p3: A non-static inline function cannot define a non-const - // function-local static variable - if self.current_func_is_non_static_inline { - let is_const = self - .types - .modifiers(declarator.typ) - .contains(TypeModifiers::CONST); - if !is_const { - if let Some(pos) = self.current_pos { - error( - pos, - &format!( - "non-static inline function '{}' cannot define non-const static variable '{}'", - self.current_func_name, name_str - ), - ); - } + // Hidden return pointer is the first argument (pointer type) + (sret_sym, vec![sret_addr], vec![self.types.pointer_to(typ)]) + } else if returns_two_reg_struct { + // Two-register struct returns: allocate local storage for the result + // Codegen will store RAX+RDX (x86-64) or X0+X1 (AArch64) to this location + let local_sym = self.alloc_pseudo(); + let unique_name = format!("__2reg_{}", local_sym.0); + let local_pseudo = Pseudo::sym(local_sym, unique_name.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(local_pseudo); + func.add_local( + &unique_name, + local_sym, + typ, + false, + false, + self.current_bb, + None, + ); } - } - - // Generate unique global name: funcname.varname.counter - let global_name = format!( - "{}.{}.{}", - self.current_func_name, name_str, self.static_local_counter - ); - self.static_local_counter += 1; - - // Track mapping from local name to global name for this function's scope - // Use a key that includes function name to handle same-named statics in different functions - let key = format!("{}.{}", self.current_func_name, name_str); - self.static_locals.insert( - key, - StaticLocalInfo { - global_name: global_name.clone(), - typ: declarator.typ, - }, - ); - - // Also insert with the SymbolId for the current function scope - // This is used during expression linearization - self.insert_local( - declarator.symbol, - LocalVarInfo { - // Use a sentinel value - we'll handle static locals specially - sym: PseudoId(u32::MAX), - typ: declarator.typ, - vla_size_sym: None, - vla_elem_type: None, - vla_dim_syms: vec![], - is_indirect: false, - }, - ); - - // Determine initializer (static locals are initialized at compile time) - let init = declarator.init.as_ref().map_or(Initializer::None, |e| { - self.ast_init_to_ir(e, declarator.typ) - }); - - // Add as a global - static locals always have internal linkage - // Check for thread-local storage - let modifiers = self.types.modifiers(declarator.typ); - if modifiers.contains(TypeModifiers::THREAD_LOCAL) { - self.module.add_global_tls_aligned( - &global_name, - declarator.typ, - init, - declarator.explicit_align, - true, // static locals always have internal linkage - ); + (local_sym, Vec::new(), Vec::new()) + } else if returns_complex { + // Complex returns: allocate local storage for the result + // Complex values are 16 bytes and need stack storage + let local_sym = self.alloc_pseudo(); + let unique_name = format!("__cret_{}", local_sym.0); + let local_pseudo = Pseudo::sym(local_sym, unique_name.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(local_pseudo); + func.add_local( + &unique_name, + local_sym, + typ, + false, + false, + self.current_bb, + None, + ); + } + (local_sym, Vec::new(), Vec::new()) + } else if (typ_kind == TypeKind::Struct || typ_kind == TypeKind::Union) + && struct_size_bits > 0 + && struct_size_bits <= 64 + { + // Small struct/union returns (<=64 bits, single register): + // Allocate local storage so the result has a stable address. + // The codegen stores RAX (or XMM0) to this location. + // Without this, the result pseudo holds a raw value which + // emit_assign's block_copy would incorrectly dereference as a pointer. + let local_sym = self.alloc_pseudo(); + let unique_name = format!("__sret1_{}", local_sym.0); + let local_pseudo = Pseudo::sym(local_sym, unique_name.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(local_pseudo); + func.add_local( + &unique_name, + local_sym, + typ, + false, + false, + self.current_bb, + None, + ); + } + (local_sym, Vec::new(), Vec::new()) } else { - self.module.add_global_aligned( - &global_name, - declarator.typ, - init, - declarator.explicit_align, - true, // static locals always have internal linkage - ); - } - } - - /// Linearize an initializer list for arrays or structs - fn linearize_init_list(&mut self, base_sym: PseudoId, typ: TypeId, elements: &[InitElement]) { - self.linearize_init_list_at_offset(base_sym, 0, typ, elements); - } + let result = self.alloc_pseudo(); + (result, Vec::new(), Vec::new()) + }; - /// Linearize an initializer list at a given base offset - fn linearize_init_list_at_offset( - &mut self, - base_sym: PseudoId, - base_offset: i64, - typ: TypeId, - elements: &[InitElement], - ) { - match self.types.kind(typ) { - TypeKind::Array => { - let elem_type = self.types.base_type(typ).unwrap_or(self.types.int_id); - let elem_size = self.types.size_bits(elem_type) / 8; - let elem_is_aggregate = matches!( - self.types.kind(elem_type), - TypeKind::Array | TypeKind::Struct | TypeKind::Union - ); + // Get formal parameter types for implicit widening conversions. + // When a narrow int (e.g., int) is passed to a wider parameter (e.g., long), + // C requires implicit promotion. This is transparent for non-inlined calls + // (the ABI handles it), but inlining exposes the mismatch since the argument + // pseudo is used directly without conversion. + let formal_param_types: Option> = func_expr.typ.and_then(|ft_id| { + let ft = self.types.get(ft_id); + ft.params.clone() + }); - let groups = self.group_array_init_elements(elements, elem_type); - for element_index in groups.indices { - let Some(list) = groups.element_lists.get(&element_index) else { - continue; - }; - let offset = base_offset + element_index * elem_size as i64; - // When a string literal initializes a char array element - // (e.g., char arr[3][4] = {"Sun", "Mon", "Tue"}), handle - // it as a string copy rather than recursing into individual - // char stores. The recursion would treat the string as a - // pointer instead of inline data. - let is_string_for_char_array = elem_is_aggregate - && list.len() == 1 - && matches!( - list[0].value.kind, - ExprKind::StringLit(_) | ExprKind::WideStringLit(_) - ) - && self.types.kind(elem_type) == TypeKind::Array; - if is_string_for_char_array { - // Emit byte-by-byte stores for the string content - if let ExprKind::StringLit(s) = &list[0].value.kind { - let char_type = self - .types - .base_type(elem_type) - .unwrap_or(self.types.char_id); - let char_bits = self.types.size_bits(char_type); - for (i, ch) in s.chars().enumerate() { - let byte_val = self.emit_const(ch as u8 as i128, self.types.int_id); - self.emit(Instruction::store( - byte_val, - base_sym, - offset + i as i64, - char_type, - char_bits, - )); - } - // Null terminator + zero fill - let arr_bytes = (self.types.size_bits(elem_type) / 8) as usize; - let str_len = s.chars().count(); - for i in str_len..arr_bytes { - let zero = self.emit_const(0, self.types.int_id); - self.emit(Instruction::store( - zero, - base_sym, - offset + i as i64, - char_type, - char_bits, - )); - } - } - continue; - } - if elem_is_aggregate { - self.linearize_init_list_at_offset(base_sym, offset, elem_type, list); - continue; + // Linearize regular arguments + // For large structs, pass by reference (address) instead of by value + // Note: We pass structs > 64 bits by reference. While the ABI allows + // two-register passing for 9-16 byte structs, we don't implement that yet. + // For complex types, pass address so codegen can load real/imag into XMM registers + // For arrays (including VLAs), decay to pointer + for (arg_idx, a) in args.iter().enumerate() { + let mut arg_type = self.expr_type(a); + let arg_kind = self.types.kind(arg_type); + let arg_val = if (arg_kind == TypeKind::Struct || arg_kind == TypeKind::Union) + && self.types.size_bits(arg_type) > 64 + { + let size_bits = self.types.size_bits(arg_type); + if size_bits > 128 { + // Large struct (> 16 bytes): keep struct type so ABI classifies as + // Indirect/MEMORY. The pseudo is still the struct's address; + // codegen will copy bytes to the stack. + arg_types_vec.push(arg_type); + } else { + // Medium struct (9-16 bytes): check ABI classification + let abi = get_abi_for_conv(self.current_calling_conv, self.target); + let class = abi.classify_param(arg_type, self.types); + let is_two_fp_regs = + matches!( + class, + crate::abi::ArgClass::Direct { ref classes, .. } + if classes.len() == 2 + && classes.iter().all(|c| *c == crate::abi::RegClass::Sse) + ) || matches!(class, crate::abi::ArgClass::Hfa { count: 2, .. }); + if is_two_fp_regs { + // All-SSE struct: keep struct type for 2-XMM passing + arg_types_vec.push(arg_type); + } else { + // Integer or mixed struct: pass as pointer (existing behavior) + arg_types_vec.push(self.types.pointer_to(arg_type)); } - let Some(last) = list.last() else { - continue; - }; - let val = self.linearize_expr(&last.value); - let val_type = self.expr_type(&last.value); - let converted = self.emit_convert(val, val_type, elem_type); - let elem_size = self.types.size_bits(elem_type); - self.emit(Instruction::store( - converted, base_sym, offset, elem_type, elem_size, - )); } - } - TypeKind::Struct | TypeKind::Union => { - // If the initializer is a single expression of the same struct - // type (e.g., `Py_complex in[1] = {a}` where `a` is Py_complex), - // do a block copy instead of field-by-field initialization. - if elements.len() == 1 - && elements[0].designators.is_empty() - && elements[0].value.typ.is_some() - { - let expr_type = self.expr_type(&elements[0].value); - let expr_kind = self.types.kind(expr_type); - if (expr_kind == TypeKind::Struct || expr_kind == TypeKind::Union) - && self.types.size_bits(expr_type) == self.types.size_bits(typ) - { - let src_addr = self.linearize_lvalue(&elements[0].value); - let target_size_bytes = self.types.size_bits(typ) / 8; - self.emit_block_copy_at_offset( - base_sym, - base_offset, - src_addr, - target_size_bytes as i64, - ); - return; - } + // For lvalue expressions (identifiers, member access), linearize_lvalue + // returns a symaddr (pointer). For rvalue expressions (call results), + // linearize_lvalue falls through to linearize_expr which returns the + // data pseudo, not its address. In that case we must emit a symaddr + // to get the address for pointer-based struct passing. + let is_lvalue = matches!( + a.kind, + ExprKind::Ident(_) + | ExprKind::Member { .. } + | ExprKind::Arrow { .. } + | ExprKind::Index { .. } + | ExprKind::Unary { + op: crate::parse::ast::UnaryOp::Deref, + .. + } + | ExprKind::CompoundLiteral { .. } + ); + if is_lvalue { + self.linearize_lvalue(a) + } else { + // Rvalue (e.g., function call returning struct): evaluate, + // then take address of the result local + let val = self.linearize_expr(a); + let result = self.alloc_reg_pseudo(); + let ptr_type = self.types.pointer_to(arg_type); + self.emit(Instruction::sym_addr(result, val, ptr_type)); + result } + } else if self.types.is_complex(arg_type) { + // Complex types: pass address, codegen loads real/imag into XMM registers + // Type stays as complex (not pointer) so codegen knows it's complex + arg_types_vec.push(arg_type); + self.linearize_lvalue(a) + } else if arg_kind == TypeKind::Array { + // Array decay to pointer (C99 6.3.2.1) + // This applies to both fixed-size arrays and VLAs + let elem_type = self.types.base_type(arg_type).unwrap_or(self.types.int_id); + arg_types_vec.push(self.types.pointer_to(elem_type)); + self.linearize_expr(a) + } else if arg_kind == TypeKind::VaList { + // va_list decay to pointer (C99 7.15.1) + // va_list is defined as __va_list_tag[1] (an array), so it decays to + // a pointer when passed to a function taking va_list parameter + arg_types_vec.push(self.types.pointer_to(arg_type)); + self.linearize_lvalue(a) + } else if arg_kind == TypeKind::Function { + // Function decay to pointer (C99 6.3.2.1) + // Function names passed as arguments decay to function pointers + arg_types_vec.push(self.types.pointer_to(arg_type)); + self.linearize_expr(a) + } else { + let mut val = self.linearize_expr(a); - let resolved_typ = self.resolve_struct_type(typ); - if let Some(composite) = self.types.get(resolved_typ).composite.as_ref() { - let members: Vec<_> = composite.members.clone(); - let is_union = self.types.kind(resolved_typ) == TypeKind::Union; - - let visits = - self.walk_struct_init_fields(resolved_typ, &members, is_union, elements); - - for visit in visits { - let offset = base_offset + visit.offset as i64; - let field_type = visit.typ; - - match visit.kind { - StructFieldVisitKind::BraceElision(sub_elements) => { - self.linearize_init_list_at_offset( - base_sym, - offset, - field_type, - &sub_elements, - ); - } - StructFieldVisitKind::Expr(expr) => { - if let (Some(bit_off), Some(bit_w), Some(storage_size)) = - (visit.bit_offset, visit.bit_width, visit.storage_unit_size) - { - let val = self.linearize_expr(&expr); - let val_type = self.expr_type(&expr); - let storage_type = self.bitfield_storage_type(storage_size); - let converted = self.emit_convert(val, val_type, storage_type); - self.emit_bitfield_store( - base_sym, - offset as usize, - bit_off, - bit_w, - storage_size, - converted, - ); - } else { - self.linearize_struct_field_init( - base_sym, offset, field_type, &expr, - ); - } - } + // Implicit argument conversion when actual type differs from + // formal parameter type. Covers: + // - Integer widening: int→long (sign/zero extend) + // - FP widening/narrowing: float↔double↔long double + // - Int→FP: uint32_t→double (e.g., log10(uint32_t_val)) + // - FP→Int: rare but legal + if let Some(ref params) = formal_param_types { + if arg_idx < params.len() { + let param_type = params[arg_idx]; + let arg_size = self.types.size_bits(arg_type); + let param_size = self.types.size_bits(param_type); + let arg_is_int = self.types.is_integer(arg_type); + let param_is_int = self.types.is_integer(param_type); + let arg_is_fp = self.types.is_float(arg_type); + let param_is_fp = self.types.is_float(param_type); + + let needs_convert = + // Integer widening (e.g., int→long) + (arg_is_int && param_is_int && arg_size < param_size + && arg_size <= 32 && param_size <= 64) + // FP size mismatch (float→double, long double→double, etc.) + || (arg_is_fp && param_is_fp && arg_size != param_size) + // Integer to FP (uint32_t→double, int→float, etc.) + || (arg_is_int && param_is_fp) + // FP to integer (rare but legal) + || (arg_is_fp && param_is_int); + + if needs_convert { + val = self.emit_convert(val, arg_type, param_type); + arg_type = param_type; } } } - } - _ => { - if let Some(element) = elements.first() { - let val = self.linearize_expr(&element.value); - let val_type = self.expr_type(&element.value); - let converted = self.emit_convert(val, val_type, typ); - let typ_size = self.types.size_bits(typ); - self.emit(Instruction::store( - converted, - base_sym, - base_offset, - typ, - typ_size, - )); - } - } - } - } - - fn linearize_struct_field_init( - &mut self, - base_sym: PseudoId, - offset: i64, - field_type: TypeId, - value: &Expr, - ) { - if let ExprKind::InitList { - elements: nested_elems, - } = &value.kind - { - self.linearize_init_list_at_offset(base_sym, offset, field_type, nested_elems); - } else if let ExprKind::StringLit(s) = &value.kind { - if self.types.kind(field_type) == TypeKind::Array { - let elem_type = self - .types - .base_type(field_type) - .unwrap_or(self.types.char_id); - let elem_size = self.types.size_bits(elem_type); - for (i, byte) in s.bytes().enumerate() { - let byte_val = self.emit_const(byte as i128, elem_type); - self.emit(Instruction::store( - byte_val, - base_sym, - offset + i as i64, - elem_type, - elem_size, - )); + // C99 6.5.2.2p7: Default argument promotions for variadic args. + // float/_Float16 are promoted to double; integer promotions + // also apply (already handled above for integers). + if let Some(va_start) = variadic_arg_start { + if arg_idx >= va_start + && matches!( + self.types.kind(arg_type), + TypeKind::Float | TypeKind::Float16 + ) + { + val = self.emit_convert(val, arg_type, self.types.double_id); + arg_type = self.types.double_id; + } } - let null_val = self.emit_const(0, elem_type); - self.emit(Instruction::store( - null_val, - base_sym, - offset + s.chars().count() as i64, - elem_type, - elem_size, - )); - } else { - let val = self.linearize_expr(value); - let val_type = self.expr_type(value); - let converted = self.emit_convert(val, val_type, field_type); - let size = self.types.size_bits(field_type); - self.emit(Instruction::store( - converted, base_sym, offset, field_type, size, - )); - } - } else { - let (actual_type, actual_size) = if self.types.kind(field_type) == TypeKind::Array { - let elem_type = self.types.base_type(field_type).unwrap_or(field_type); - (elem_type, self.types.size_bits(elem_type)) - } else { - (field_type, self.types.size_bits(field_type)) + + arg_types_vec.push(arg_type); + val }; - let val = self.linearize_expr(value); - let val_type = self.expr_type(value); - let converted = self.emit_convert(val, val_type, actual_type); - self.emit(Instruction::store( - converted, - base_sym, - offset, - actual_type, - actual_size, - )); + arg_vals.push(arg_val); } - } - fn linearize_if(&mut self, cond: &Expr, then_stmt: &Stmt, else_stmt: Option<&Stmt>) { - let cond_val = self.linearize_expr(cond); + // Compute ABI classification for parameters and return value. + // This provides rich metadata for the backend to generate correct calling code. + // Use the current function's calling convention (which may be overridden via attributes) + let abi = get_abi_for_conv(self.current_calling_conv, self.target); + let param_classes: Vec<_> = arg_types_vec + .iter() + .map(|&t| abi.classify_param(t, self.types)) + .collect(); + let ret_class = abi.classify_return(typ, self.types); + let call_abi_info = Box::new(CallAbiInfo::new(param_classes, ret_class)); - let then_bb = self.alloc_bb(); - let else_bb = self.alloc_bb(); - let merge_bb = self.alloc_bb(); - - // Conditional branch - if let Some(current) = self.current_bb { - if else_stmt.is_some() { - self.emit(Instruction::cbr(cond_val, then_bb, else_bb)); + if returns_large_struct { + // For large struct returns, the return value is the address + // stored in result_sym (which is a local symbol containing the struct) + let result = self.alloc_reg_pseudo(); + let ptr_typ = self.types.pointer_to(typ); + let mut call_insn = if let Some(func_addr) = indirect_target { + // Indirect call through function pointer + Instruction::call_indirect( + Some(result), + func_addr, + arg_vals, + arg_types_vec, + ptr_typ, + 64, // pointers are 64-bit + ) } else { - self.emit(Instruction::cbr(cond_val, then_bb, merge_bb)); - } - self.link_bb(current, then_bb); - if else_stmt.is_some() { - self.link_bb(current, else_bb); + // Direct call + Instruction::call( + Some(result), + &func_name, + arg_vals, + arg_types_vec, + ptr_typ, + 64, // pointers are 64-bit + ) + }; + call_insn.variadic_arg_start = variadic_arg_start; + call_insn.is_noreturn_call = is_noreturn_call; + call_insn.abi_info = Some(call_abi_info); + self.emit(call_insn); + // Return the symbol (address) where struct is stored + result_sym + } else { + let ret_size = self.types.size_bits(typ); + let mut call_insn = if let Some(func_addr) = indirect_target { + // Indirect call through function pointer + Instruction::call_indirect( + Some(result_sym), + func_addr, + arg_vals, + arg_types_vec, + typ, + ret_size, + ) } else { - self.link_bb(current, merge_bb); - } - } - - // Then block - self.switch_bb(then_bb); - self.linearize_stmt(then_stmt); - self.link_to_merge_if_needed(merge_bb); - - // Else block - if let Some(else_s) = else_stmt { - self.switch_bb(else_bb); - self.linearize_stmt(else_s); - self.link_to_merge_if_needed(merge_bb); - } - - // Merge block - self.switch_bb(merge_bb); - } - - fn linearize_while(&mut self, cond: &Expr, body: &Stmt) { - let cond_bb = self.alloc_bb(); - let body_bb = self.alloc_bb(); - let exit_bb = self.alloc_bb(); - - // Jump to condition - if let Some(current) = self.current_bb { - if !self.is_terminated() { - self.emit(Instruction::br(cond_bb)); - self.link_bb(current, cond_bb); - } - } - - // Condition block - self.switch_bb(cond_bb); - let cond_val = self.linearize_expr(cond); - // After linearizing condition, current_bb may be different from cond_bb - // (e.g., if condition contains short-circuit operators like && or ||). - // Link the CURRENT block to body_bb and exit_bb. - if let Some(cond_end_bb) = self.current_bb { - self.emit(Instruction::cbr(cond_val, body_bb, exit_bb)); - self.link_bb(cond_end_bb, body_bb); - self.link_bb(cond_end_bb, exit_bb); - } - - // Body block - self.break_targets.push(exit_bb); - self.continue_targets.push(cond_bb); - - self.switch_bb(body_bb); - self.linearize_stmt(body); - if !self.is_terminated() { - // After linearizing body, current_bb may be different from body_bb - // (e.g., if body contains nested loops). Link the CURRENT block to cond_bb. - if let Some(current) = self.current_bb { - self.emit(Instruction::br(cond_bb)); - self.link_bb(current, cond_bb); - } - } - - self.break_targets.pop(); - self.continue_targets.pop(); - - // Exit block - self.switch_bb(exit_bb); - } - - fn linearize_do_while(&mut self, body: &Stmt, cond: &Expr) { - let body_bb = self.alloc_bb(); - let cond_bb = self.alloc_bb(); - let exit_bb = self.alloc_bb(); - - // Jump to body - if let Some(current) = self.current_bb { - if !self.is_terminated() { - self.emit(Instruction::br(body_bb)); - self.link_bb(current, body_bb); - } - } - - // Body block - self.break_targets.push(exit_bb); - self.continue_targets.push(cond_bb); - - self.switch_bb(body_bb); - self.linearize_stmt(body); - if !self.is_terminated() { - // After linearizing body, current_bb may be different from body_bb - if let Some(current) = self.current_bb { - self.emit(Instruction::br(cond_bb)); - self.link_bb(current, cond_bb); - } - } - - self.break_targets.pop(); - self.continue_targets.pop(); - - // Condition block - self.switch_bb(cond_bb); - let cond_val = self.linearize_expr(cond); - // After linearizing condition, current_bb may be different from cond_bb - // (e.g., if condition contains short-circuit operators like && or ||). - // Link the CURRENT block to body_bb and exit_bb. - if let Some(cond_end_bb) = self.current_bb { - self.emit(Instruction::cbr(cond_val, body_bb, exit_bb)); - self.link_bb(cond_end_bb, body_bb); - self.link_bb(cond_end_bb, exit_bb); + // Direct call + Instruction::call( + Some(result_sym), + &func_name, + arg_vals, + arg_types_vec, + typ, + ret_size, + ) + }; + call_insn.variadic_arg_start = variadic_arg_start; + call_insn.is_noreturn_call = is_noreturn_call; + call_insn.abi_info = Some(call_abi_info); + self.emit(call_insn); + result_sym } - - // Exit block - self.switch_bb(exit_bb); } - fn linearize_for( - &mut self, - init: Option<&ForInit>, - cond: Option<&Expr>, - post: Option<&Expr>, - body: &Stmt, - ) { - // C99 for-loop declarations (e.g., for (int i = 0; ...)) are scoped to the loop. - self.push_scope(); - - // Init - if let Some(init) = init { - match init { - ForInit::Declaration(decl) => self.linearize_local_decl(decl), - ForInit::Expression(expr) => { - self.linearize_expr(expr); - } - } - } - - let cond_bb = self.alloc_bb(); - let body_bb = self.alloc_bb(); - let post_bb = self.alloc_bb(); - let exit_bb = self.alloc_bb(); - - // Jump to condition - if let Some(current) = self.current_bb { - if !self.is_terminated() { - self.emit(Instruction::br(cond_bb)); - self.link_bb(current, cond_bb); - } - } + /// Linearize a post-increment or post-decrement expression + pub(crate) fn linearize_postop(&mut self, operand: &Expr, is_inc: bool) -> PseudoId { + let val = self.linearize_expr(operand); + let typ = self.expr_type(operand); + let is_float = self.types.is_float(typ); + let is_ptr = self.types.kind(typ) == TypeKind::Pointer; - // Condition block - self.switch_bb(cond_bb); - if let Some(cond_expr) = cond { - let cond_val = self.linearize_expr(cond_expr); - // After linearizing condition, current_bb may be different from cond_bb - // (e.g., if condition contains short-circuit operators like && or ||). - // Link the CURRENT block to body_bb and exit_bb. - if let Some(cond_end_bb) = self.current_bb { - self.emit(Instruction::cbr(cond_val, body_bb, exit_bb)); - self.link_bb(cond_end_bb, body_bb); - self.link_bb(cond_end_bb, exit_bb); - } + // For locals, we need to save the old value before updating + // because the pseudo will be reloaded from stack which gets overwritten + let is_local = if let ExprKind::Ident(symbol_id) = &operand.kind { + self.locals.contains_key(symbol_id) } else { - // No condition = always true - self.emit(Instruction::br(body_bb)); - self.link_bb(cond_bb, body_bb); - // No link to exit_bb since we always enter the body - } + false + }; - // Body block - self.break_targets.push(exit_bb); - self.continue_targets.push(post_bb); + let old_val = if is_local { + // Copy the old value to a temp + let temp = self.alloc_reg_pseudo(); + self.emit( + Instruction::new(Opcode::Copy) + .with_target(temp) + .with_src(val) + .with_type(typ) + .with_size(self.types.size_bits(typ)), + ); + temp + } else { + val + }; - self.switch_bb(body_bb); - self.linearize_stmt(body); - if !self.is_terminated() { - // After linearizing body, current_bb may be different from body_bb - if let Some(current) = self.current_bb { - self.emit(Instruction::br(post_bb)); - self.link_bb(current, post_bb); + // For pointers, increment/decrement by element size; for others, by 1 + let delta = if is_ptr { + let elem_type = self.types.base_type(typ).unwrap_or(self.types.char_id); + let elem_size = self.types.size_bits(elem_type) / 8; + self.emit_const(elem_size as i128, self.types.long_id) + } else if is_float { + self.emit_fconst(1.0, typ) + } else { + self.emit_const(1, typ) + }; + let result = self.alloc_reg_pseudo(); + let opcode = if is_float { + if is_inc { + Opcode::FAdd + } else { + Opcode::FSub } - } - - self.break_targets.pop(); - self.continue_targets.pop(); - - // Post block - self.switch_bb(post_bb); - if let Some(post_expr) = post { - self.linearize_expr(post_expr); - } - self.emit(Instruction::br(cond_bb)); - self.link_bb(post_bb, cond_bb); - - // Exit block - self.switch_bb(exit_bb); - - // Restore locals to remove for-loop-scoped declarations - self.pop_scope(); - } - - fn linearize_switch(&mut self, expr: &Expr, body: &Stmt) { - // Linearize the switch expression - let switch_val = self.linearize_expr(expr); - let expr_type = self.expr_type(expr); - let size = self.types.size_bits(expr_type); - - let exit_bb = self.alloc_bb(); - - // Push exit block for break handling - self.break_targets.push(exit_bb); - - // Collect case labels and create basic blocks for each - let (case_values, has_default) = self.collect_switch_cases(body); - let case_bbs: Vec = case_values.iter().map(|_| self.alloc_bb()).collect(); - let default_bb = if has_default { - Some(self.alloc_bb()) + } else if is_inc { + Opcode::Add } else { - None + Opcode::Sub }; - - // Build switch instruction with case -> block mapping - let switch_cases: Vec<(i64, BasicBlockId)> = case_values - .iter() - .zip(case_bbs.iter()) - .map(|(val, bb)| (*val, *bb)) - .collect(); - - // Default goes to default_bb if present, otherwise exit_bb - let default_target = default_bb.unwrap_or(exit_bb); - - // Emit switch instruction - self.emit(Instruction::switch_insn( - switch_val, - switch_cases.clone(), - Some(default_target), - size, + let arith_type = if is_ptr { self.types.long_id } else { typ }; + let arith_size = self.types.size_bits(arith_type); + self.emit(Instruction::binop( + opcode, result, val, delta, arith_type, arith_size, )); - // Link CFG edges from current block to all case/default/exit blocks - if let Some(current) = self.current_bb { - for &(_, bb) in &switch_cases { - self.link_bb(current, bb); - } - self.link_bb(current, default_target); - if default_bb.is_none() { - self.link_bb(current, exit_bb); - } - } - - // Linearize body with case block switching - self.linearize_switch_body(body, &case_values, &case_bbs, default_bb); - - // If not terminated after body, jump to exit - if !self.is_terminated() { - if let Some(current) = self.current_bb { - self.emit(Instruction::br(exit_bb)); - self.link_bb(current, exit_bb); - } - } - - self.break_targets.pop(); - - // Exit block - self.switch_bb(exit_bb); - } - - /// Collect case values from switch body (must be a Block) - /// Returns (case_values, has_default) - fn collect_switch_cases(&self, body: &Stmt) -> (Vec, bool) { - let mut case_values = Vec::new(); - let mut has_default = false; - - if let Stmt::Block(items) = body { - for item in items { - if let BlockItem::Statement(stmt) = item { - self.collect_cases_from_stmt(stmt, &mut case_values, &mut has_default); - } - } - } - - (case_values, has_default) - } - - fn collect_cases_from_stmt( - &self, - stmt: &Stmt, - case_values: &mut Vec, - has_default: &mut bool, - ) { - match stmt { - Stmt::Case(expr) => { - // Extract constant value from case expression - if let Some(val) = self.eval_const_expr(expr) { - case_values.push(val as i64); // switch cases truncated to i64 - } - } - Stmt::Default => { - *has_default = true; - } - // Recursively check labeled statements - Stmt::Label { stmt, .. } => { - self.collect_cases_from_stmt(stmt, case_values, has_default); - } - _ => {} - } - } - - /// Evaluate a constant expression (for case labels, static initializers) - /// - /// C99 6.6 defines integer constant expressions. This function evaluates - /// expressions that can be computed at compile time. - fn eval_const_expr(&self, expr: &Expr) -> Option { - match &expr.kind { - ExprKind::IntLit(val) => Some(*val as i128), - ExprKind::Int128Lit(val) => Some(*val), - ExprKind::CharLit(c) => Some(*c as u8 as i8 as i128), + // For _Bool, normalize the result (any non-zero -> 1) + let final_result = if self.types.kind(typ) == TypeKind::Bool { + self.emit_convert(result, self.types.int_id, typ) + } else { + result + }; + // Store to local, update parameter mapping, or store through pointer + let store_size = self.types.size_bits(typ); + match &operand.kind { ExprKind::Ident(symbol_id) => { - // Check if it's an enum constant - let sym = self.symbols.get(*symbol_id); - if sym.is_enum_constant() { - sym.enum_value.map(|v| v as i128) - } else { - None - } - } - - ExprKind::Unary { op, operand } => { - let val = self.eval_const_expr(operand)?; - match op { - UnaryOp::Neg => Some(val.wrapping_neg()), - UnaryOp::Not => Some(if val == 0 { 1 } else { 0 }), - UnaryOp::BitNot => Some(!val), - _ => None, - } - } - - ExprKind::Binary { op, left, right } => { - let l = self.eval_const_expr(left)?; - let r = self.eval_const_expr(right)?; - match op { - BinaryOp::Add => Some(l.wrapping_add(r)), - BinaryOp::Sub => Some(l.wrapping_sub(r)), - BinaryOp::Mul => Some(l.wrapping_mul(r)), - BinaryOp::Div => { - if r != 0 { - Some(l / r) - } else { - None - } - } - BinaryOp::Mod => { - if r != 0 { - Some(l % r) - } else { - None - } - } - BinaryOp::BitAnd => Some(l & r), - BinaryOp::BitOr => Some(l | r), - BinaryOp::BitXor => Some(l ^ r), - BinaryOp::Shl | BinaryOp::Shr => { - // Mask shift amount to match C semantics and target hardware behavior. - // 8/16/32-bit: mask 31, 64-bit: mask 63, 128-bit: mask 127. - let size_bits = left.typ.map(|t| self.types.size_bits(t)).unwrap_or(32); - let mask: i128 = if size_bits > 64 { - 127 - } else if size_bits > 32 { - 63 - } else { - 31 - }; - let shift_amt = (r & mask) as u32; - match op { - BinaryOp::Shl => Some(l << shift_amt), - BinaryOp::Shr => Some(l >> shift_amt), - _ => unreachable!(), - } + let name_str = self.symbol_name(*symbol_id); + if let Some(local) = self.locals.get(symbol_id).cloned() { + // Check if this is a static local (sentinel value) + if local.sym.0 == u32::MAX { + self.emit_static_local_store(&name_str, final_result, typ, store_size); + } else { + // Regular local variable + self.emit(Instruction::store( + final_result, + local.sym, + 0, + typ, + store_size, + )); } - BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge => { - // Use unsigned comparison when either operand is unsigned. - // C promotes both to the common unsigned type for comparison. - let left_unsigned = left.typ.is_some_and(|t| { - self.types.modifiers(t).contains(TypeModifiers::UNSIGNED) - }); - let right_unsigned = right.typ.is_some_and(|t| { - self.types.modifiers(t).contains(TypeModifiers::UNSIGNED) - }); - let use_unsigned = left_unsigned || right_unsigned; - if use_unsigned { - let lu = l as u128; - let ru = r as u128; - Some(match op { - BinaryOp::Lt => { - if lu < ru { - 1 - } else { - 0 - } - } - BinaryOp::Le => { - if lu <= ru { - 1 - } else { - 0 - } - } - BinaryOp::Gt => { - if lu > ru { - 1 - } else { - 0 - } - } - BinaryOp::Ge => { - if lu >= ru { - 1 - } else { - 0 - } - } - _ => unreachable!(), - }) - } else { - Some(match op { - BinaryOp::Lt => { - if l < r { - 1 - } else { - 0 - } - } - BinaryOp::Le => { - if l <= r { - 1 - } else { - 0 - } - } - BinaryOp::Gt => { - if l > r { - 1 - } else { - 0 - } - } - BinaryOp::Ge => { - if l >= r { - 1 - } else { - 0 - } - } - _ => unreachable!(), - }) - } + } else if self.var_map.contains_key(&name_str) { + self.var_map.insert(name_str.clone(), final_result); + } else { + // Global variable - emit store + let sym_id = self.alloc_pseudo(); + let pseudo = Pseudo::sym(sym_id, name_str.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); } - BinaryOp::Eq => Some(if l == r { 1 } else { 0 }), - BinaryOp::Ne => Some(if l != r { 1 } else { 0 }), - BinaryOp::LogAnd => Some(if l != 0 && r != 0 { 1 } else { 0 }), - BinaryOp::LogOr => Some(if l != 0 || r != 0 { 1 } else { 0 }), + self.emit(Instruction::store(final_result, sym_id, 0, typ, store_size)); } } - - ExprKind::Conditional { - cond, - then_expr, - else_expr, - } => { - let cond_val = self.eval_const_expr(cond)?; - if cond_val != 0 { - self.eval_const_expr(then_expr) - } else { - self.eval_const_expr(else_expr) - } - } - - // sizeof(type) - constant for complete types - ExprKind::SizeofType(type_id) => { - let size_bits = self.types.size_bits(*type_id); - Some((size_bits / 8) as i128) - } - - // sizeof(expr) - constant if expr type is complete - ExprKind::SizeofExpr(inner_expr) => { - if let Some(typ) = inner_expr.typ { - let size_bits = self.types.size_bits(typ); - Some((size_bits / 8) as i128) - } else { - None - } - } - - // _Alignof(type) - constant for complete types - ExprKind::AlignofType(type_id) => { - let align = self.types.alignment(*type_id); - Some(align as i128) - } - - // _Alignof(expr) - constant if expr type is complete - ExprKind::AlignofExpr(inner_expr) => { - if let Some(typ) = inner_expr.typ { - let align = self.types.alignment(typ); - Some(align as i128) - } else { - None - } - } - - // Cast to integer type - evaluate inner expression - ExprKind::Cast { expr: inner, .. } => self.eval_const_expr(inner), - - // __builtin_offsetof(type, member-designator) - compile-time constant - ExprKind::OffsetOf { type_id, path } => { - let mut offset: u64 = 0; - let mut current_type = *type_id; - - for element in path { - match element { - OffsetOfPath::Field(field_id) => { - let struct_type = self.resolve_struct_type(current_type); - if let Some(member_info) = - self.types.find_member(struct_type, *field_id) - { - offset += member_info.offset as u64; - current_type = member_info.typ; - } else { - return None; // Field not found - } - } - OffsetOfPath::Index(index) => { - if let Some(elem_type) = self.types.base_type(current_type) { - let elem_size = self.types.size_bytes(elem_type); - offset += (*index as u64) * (elem_size as u64); - current_type = elem_type; - } else { - return None; // Not an array type - } - } - } - } - - Some(offset as i128) - } - - _ => None, - } - } - - /// Evaluate a constant floating-point expression at compile time. - /// Handles float literals, negation, and binary arithmetic on floats. - fn eval_const_float_expr(&self, expr: &Expr) -> Option { - match &expr.kind { - ExprKind::FloatLit(v) => Some(*v), - ExprKind::IntLit(v) => Some(*v as f64), - ExprKind::CharLit(c) => Some(*c as i64 as f64), - - ExprKind::Unary { op, operand } => { - let val = self.eval_const_float_expr(operand)?; - match op { - UnaryOp::Neg => Some(-val), - _ => None, - } - } - - ExprKind::Binary { op, left, right } => { - let l = self.eval_const_float_expr(left)?; - let r = self.eval_const_float_expr(right)?; - match op { - BinaryOp::Add => Some(l + r), - BinaryOp::Sub => Some(l - r), - BinaryOp::Mul => Some(l * r), - BinaryOp::Div => Some(l / r), - _ => None, - } - } - - ExprKind::Cast { expr: inner, .. } => self.eval_const_float_expr(inner), - - _ => None, - } - } - - /// Evaluate a static address expression (for initializers like `&symbol.field`) - /// - /// Returns Some((symbol_name, offset)) if the expression is a valid static address, - /// or None if it can't be computed at compile time. - fn eval_static_address(&self, expr: &Expr) -> Option<(String, i64)> { - match &expr.kind { - // Simple identifier: &symbol - ExprKind::Ident(symbol_id) => { - let name_str = self.symbol_name(*symbol_id); - // Check if this is a static local variable - let key = format!("{}.{}", self.current_func_name, name_str); - if let Some(static_info) = self.static_locals.get(&key) { - Some((static_info.global_name.clone(), 0)) - } else { - Some((name_str, 0)) - } - } - - // Member access: expr.member - ExprKind::Member { expr: base, member } => { - // Recursively evaluate the base address - let (name, base_offset) = self.eval_static_address(base)?; - - // Get the offset of the member in the struct - let base_type = base.typ?; - let struct_type = self.resolve_struct_type(base_type); - let member_info = self.types.find_member(struct_type, *member)?; - - Some((name, base_offset + member_info.offset as i64)) - } - - // Arrow access: expr->member (pointer dereference + member access) - // Can be a static address when the pointer is a static address-of expression - // (e.g., (&static_struct.field)->subfield in CPython macros) - ExprKind::Arrow { expr: base, member } => { - let (name, base_offset) = self.eval_static_address(base)?; - let ptr_type = base.typ?; - let pointee_type = self.types.base_type(ptr_type)?; - let struct_type = self.resolve_struct_type(pointee_type); - let member_info = self.types.find_member(struct_type, *member)?; - Some((name, base_offset + member_info.offset as i64)) - } - - // Array subscript: array[index] - ExprKind::Index { array, index } => { - // Recursively evaluate the base address - let (name, base_offset) = self.eval_static_address(array)?; - - // Get the index as a constant - let idx = self.eval_const_expr(index)?; - - // Get the element size - let array_type = array.typ?; - let elem_type = self.types.base_type(array_type)?; - let elem_size = self.types.size_bytes(elem_type) as i64; - - Some((name, base_offset + idx as i64 * elem_size)) - } - - // Address-of: &expr → same as evaluating expr as static address ExprKind::Unary { - op: UnaryOp::AddrOf, - operand, - } => self.eval_static_address(operand), - - // Cast - evaluate the inner expression - ExprKind::Cast { expr: inner, .. } => self.eval_static_address(inner), - - // Binary add/sub with pointer operand: ptr + int or ptr - int - ExprKind::Binary { - op: op @ (BinaryOp::Add | BinaryOp::Sub), - left, - right, + op: UnaryOp::Deref, + operand: ptr_expr, } => { - // Determine which side is the pointer and which is the integer - let (ptr_expr, int_expr, is_sub) = if left.typ.is_some_and(|t| { - self.types.kind(t) == TypeKind::Pointer || self.types.kind(t) == TypeKind::Array - }) { - (left.as_ref(), right.as_ref(), *op == BinaryOp::Sub) - } else if right.typ.is_some_and(|t| { - self.types.kind(t) == TypeKind::Pointer || self.types.kind(t) == TypeKind::Array - }) && *op == BinaryOp::Add - { - (right.as_ref(), left.as_ref(), false) - } else { - return None; - }; - - let (name, base_offset) = self.eval_static_address(ptr_expr)?; - let int_val = self.eval_const_expr(int_expr)?; - - // Scale by pointee size for pointer arithmetic - let pointee_size = ptr_expr - .typ - .and_then(|t| self.types.base_type(t)) - .map(|t| self.types.size_bytes(t) as i64) - .unwrap_or(1); - let byte_offset = if is_sub { - base_offset - int_val as i64 * pointee_size - } else { - base_offset + int_val as i64 * pointee_size - }; - - Some((name, byte_offset)) - } - - _ => None, - } - } - - /// Linearize switch body, switching basic blocks at case/default labels - fn linearize_switch_body( - &mut self, - body: &Stmt, - case_values: &[i64], - case_bbs: &[BasicBlockId], - default_bb: Option, - ) { - let mut case_idx = 0; - - if let Stmt::Block(items) = body { - for item in items { - match item { - BlockItem::Declaration(decl) => self.linearize_local_decl(decl), - BlockItem::Statement(stmt) => { - self.linearize_switch_stmt( - stmt, - case_values, - case_bbs, - default_bb, - &mut case_idx, - ); - } - } - } - } - } - - fn linearize_switch_stmt( - &mut self, - stmt: &Stmt, - case_values: &[i64], - case_bbs: &[BasicBlockId], - default_bb: Option, - case_idx: &mut usize, - ) { - match stmt { - Stmt::Case(expr) => { - // Find the matching case block - if let Some(val) = self.eval_const_expr(expr) { - if let Some(idx) = case_values.iter().position(|v| *v as i128 == val) { - let case_bb = case_bbs[idx]; - - // Fall through from previous case if not terminated - if !self.is_terminated() { - if let Some(current) = self.current_bb { - self.emit(Instruction::br(case_bb)); - self.link_bb(current, case_bb); - } - } - - self.switch_bb(case_bb); - *case_idx = idx + 1; - } - } - } - Stmt::Default => { - if let Some(def_bb) = default_bb { - // Fall through from previous case if not terminated - if !self.is_terminated() { - if let Some(current) = self.current_bb { - self.emit(Instruction::br(def_bb)); - self.link_bb(current, def_bb); - } - } - - self.switch_bb(def_bb); - } - } - _ => { - // Regular statement - linearize it - self.linearize_stmt(stmt); - } - } - } - - // ======================================================================== - // Inline assembly linearization - // ======================================================================== - - /// Linearize an inline assembly statement - /// Check if an expression is a simple identifier that's a parameter (in var_map) - /// Returns Some((name, pseudo)) if it is, None otherwise - fn get_param_if_ident(&self, expr: &Expr) -> Option<(String, PseudoId)> { - if let ExprKind::Ident(symbol_id) = &expr.kind { - let name_str = self.symbol_name(*symbol_id); - if let Some(&pseudo) = self.var_map.get(&name_str) { - return Some((name_str, pseudo)); - } - } - None - } - - fn linearize_asm( - &mut self, - template: &str, - outputs: &[AsmOperand], - inputs: &[AsmOperand], - clobbers: &[String], - goto_labels: &[StringId], - ) { - let mut ir_outputs = Vec::new(); - let mut ir_inputs = Vec::new(); - // Track which outputs are parameters (need var_map update instead of store) - let mut param_outputs: Vec> = Vec::new(); - - // Process output operands - for op in outputs { - // Create a pseudo for the output - let pseudo = self.alloc_pseudo(); - - // Parse constraint to get flags - let (_is_memory, is_readwrite, _matching) = self.parse_asm_constraint(&op.constraint); - - // Get symbolic name if present - let name = op.name.map(|n| self.str(n).to_string()); - - // Check if this output is a parameter (SSA value, not memory location) - let param_info = self.get_param_if_ident(&op.expr); - - let typ = self.expr_type(&op.expr); - let size = self.types.size_bits(typ); - - // For read-write outputs ("+r"), load the initial value into the SAME pseudo - // so that input and output use the same register - if is_readwrite { - if let Some((_, param_pseudo)) = ¶m_info { - // Parameter: copy value directly (no memory address) - self.emit( - Instruction::new(Opcode::Copy) - .with_target(pseudo) - .with_src(*param_pseudo) - .with_type(typ) - .with_size(size), - ); - } else { - // Local or global: load from memory address - let addr = self.linearize_lvalue(&op.expr); - self.emit(Instruction::load(pseudo, addr, 0, typ, size)); - } - - // Also add as input, using the SAME pseudo and marking as matching - // the output. Per GCC convention, '+' creates one operand number - // shared by both output and input. - ir_inputs.push(AsmConstraint { - pseudo, // Same pseudo as output - ensures same register - name: name.clone(), - matching_output: Some(ir_outputs.len()), // matches the output about to be pushed - constraint: op.constraint.clone(), - size, - }); - } - - ir_outputs.push(AsmConstraint { - pseudo, - name, - matching_output: None, - constraint: op.constraint.clone(), - size, - }); - - // Track if this is a parameter output - param_outputs.push(param_info.map(|(name, _)| name)); - } - - // Process input operands - for op in inputs { - let (is_memory, _, matching) = self.parse_asm_constraint(&op.constraint); - - // Get symbolic name if present - let name = op.name.map(|n| self.str(n).to_string()); - - let typ = self.expr_type(&op.expr); - let size = self.types.size_bits(typ); - - // For matching constraints (like "0"), we need to load the input value - // into the matched output's pseudo so they use the same register - let pseudo = if let Some(match_idx) = matching { - if match_idx < ir_outputs.len() { - // Use the matched output's pseudo - let out_pseudo = ir_outputs[match_idx].pseudo; - // Load the input value into the output's pseudo - let val = self.linearize_expr(&op.expr); - // Copy val to out_pseudo so they share the same register - self.emit( - Instruction::new(Opcode::Copy) - .with_target(out_pseudo) - .with_src(val) - .with_type(typ) - .with_size(size), - ); - out_pseudo - } else { - self.linearize_expr(&op.expr) - } - } else if is_memory { - // For memory operands, get the address - self.linearize_lvalue(&op.expr) - } else { - // For register operands, evaluate the expression - self.linearize_expr(&op.expr) - }; - - ir_inputs.push(AsmConstraint { - pseudo, - name, - matching_output: matching, - constraint: op.constraint.clone(), - size, - }); - } - - // Process goto labels - map label names to BasicBlockIds - let ir_goto_labels: Vec<(BasicBlockId, String)> = goto_labels - .iter() - .map(|label_id| { - let label_name = self.str(*label_id).to_string(); - let bb = self.get_or_create_label(&label_name); - (bb, label_name) - }) - .collect(); - - // Create the asm data - let asm_data = AsmData { - template: template.to_string(), - outputs: ir_outputs.clone(), - inputs: ir_inputs, - clobbers: clobbers.to_vec(), - goto_labels: ir_goto_labels.clone(), - }; - - // Emit the asm instruction - self.emit(Instruction::asm(asm_data)); - - // For asm goto: add edges to all possible label targets - // The asm may jump to any of these labels, so control flow can go there - if !ir_goto_labels.is_empty() { - if let Some(current) = self.current_bb { - // After the asm instruction, we need a basic block for fall-through - // and edges to all goto targets - let fall_through = self.alloc_bb(); - - // Add edges to all goto label targets - for (target_bb, _) in &ir_goto_labels { - self.link_bb(current, *target_bb); - } - - // Add edge to fall-through (normal case when asm doesn't jump) - self.link_bb(current, fall_through); - - // Emit an explicit branch to the fallthrough block - // This is necessary because the asm goto acts as a conditional terminator - // Without this, code would fall through to whatever block comes next in layout - self.emit(Instruction::br(fall_through)); - - // Switch to fall-through block for subsequent instructions - self.current_bb = Some(fall_through); - } - } - - // Store outputs back to their destinations - // store(value, addr, ...) - value first, then address - for (i, op) in outputs.iter().enumerate() { - let out_pseudo = ir_outputs[i].pseudo; - - // Check if this output is a parameter (update var_map instead of memory store) - if let Some(param_name) = ¶m_outputs[i] { - // Parameter: update var_map with the new SSA value - self.var_map.insert(param_name.clone(), out_pseudo); - } else { - // Local or global: store to memory address - let addr = self.linearize_lvalue(&op.expr); - let typ = self.expr_type(&op.expr); - let size = self.types.size_bits(typ); - self.emit(Instruction::store(out_pseudo, addr, 0, typ, size)); - } - } - } - - /// Parse an asm constraint string to extract flags - /// Returns (is_memory, is_readwrite, matching_output) - /// Note: Early clobber (&) is parsed but not used since our simple register - /// allocator doesn't share registers between inputs and outputs anyway - fn parse_asm_constraint(&self, constraint: &str) -> (bool, bool, Option) { - let mut is_memory = false; - let mut is_readwrite = false; - let mut matching = None; - - for c in constraint.chars() { - match c { - '+' => is_readwrite = true, - '&' | '=' | '%' => {} // Early clobber, output-only, commutative - 'r' | 'a' | 'b' | 'c' | 'd' | 'S' | 'D' => {} // Register constraints - 'm' | 'o' | 'V' | 'Q' => is_memory = true, - 'i' | 'n' | 'g' | 'X' => {} // Immediate, general - '0'..='9' => matching = Some((c as u8 - b'0') as usize), - _ => {} // Ignore unknown constraints + // (*p)++ or (*p)-- - store back through the pointer + let addr = self.linearize_expr(ptr_expr); + self.emit(Instruction::store(final_result, addr, 0, typ, store_size)); } - } - - (is_memory, is_readwrite, matching) - } - - fn get_or_create_label(&mut self, name: &str) -> BasicBlockId { - if let Some(&bb) = self.label_map.get(name) { - bb - } else { - let bb = self.alloc_bb(); - self.label_map.insert(name.to_string(), bb); - // Set label name on the block - let block = self.get_or_create_bb(bb); - block.label = Some(name.to_string()); - bb - } - } - - // ======================================================================== - // Expression linearization - // ======================================================================== - - /// Get the type of an expression. - /// PANICS if expression has no type - type evaluation pass must run first. - /// The IR requires fully typed input from the type evaluation pass. - fn expr_type(&self, expr: &Expr) -> TypeId { - expr.typ.expect( - "BUG: expression has no type. Type evaluation pass must run before linearization.", - ) - } - - /// Check if an expression is "pure" (side-effect-free). - /// Pure expressions can be speculatively evaluated, enabling cmov/csel codegen. - /// - /// An expression is pure if it contains NO: - /// - Function calls - /// - Volatile accesses - /// - Pre/post increment/decrement (++, --) - /// - Assignments (=, +=, -=, etc.) - /// - Statement expressions (GNU extension with potential side effects) - fn is_pure_expr(&self, expr: &Expr) -> bool { - match &expr.kind { - // Literals are always pure - ExprKind::IntLit(_) - | ExprKind::Int128Lit(_) - | ExprKind::FloatLit(_) - | ExprKind::CharLit(_) - | ExprKind::StringLit(_) - | ExprKind::WideStringLit(_) => true, - - // Identifiers are pure unless volatile - ExprKind::Ident(_) => { - if let Some(typ) = expr.typ { - !self.types.modifiers(typ).contains(TypeModifiers::VOLATILE) - } else { - true + ExprKind::Member { expr, member } => { + // Struct member: get address and store with offset + let base = self.linearize_lvalue(expr); + let base_struct_type = self.expr_type(expr); + let struct_type = self.resolve_struct_type(base_struct_type); + if let Some(member_info) = self.types.find_member(struct_type, *member) { + self.emit(Instruction::store( + final_result, + base, + member_info.offset as i64, + typ, + store_size, + )); } } - - // __func__ is a pure string-like value - ExprKind::FuncName => true, - - // Binary ops are pure if both operands are pure AND the - // operator can't trap. Division and modulo cause SIGFPE - // on division by zero, so they're never pure. - ExprKind::Binary { - op, left, right, .. - } => { - !matches!(op, BinaryOp::Div | BinaryOp::Mod) - && self.is_pure_expr(left) - && self.is_pure_expr(right) - } - - // Unary ops are pure if operand is pure, except for pre-inc/dec and dereference. - // Dereference (*ptr) can cause UB/crash if the pointer is NULL or invalid, - // so we must not eagerly evaluate it in conditional expressions. - ExprKind::Unary { op, operand, .. } => match op { - UnaryOp::PreInc | UnaryOp::PreDec | UnaryOp::Deref => false, - _ => self.is_pure_expr(operand), - }, - - // Post-increment/decrement have side effects - ExprKind::PostInc(_) | ExprKind::PostDec(_) => false, - - // Ternary is pure if all parts are pure - ExprKind::Conditional { - cond, - then_expr, - else_expr, - } => { - self.is_pure_expr(cond) - && self.is_pure_expr(then_expr) - && self.is_pure_expr(else_expr) - } - - // Function calls are never pure (may have side effects) - ExprKind::Call { .. } => false, - - // Member access through struct value (.) is pure if the base is pure. - ExprKind::Member { expr, .. } => self.is_pure_expr(expr), - - // Arrow access (ptr->member) can cause UB/crash if ptr is NULL, - // so we must not eagerly evaluate it in conditional expressions. - ExprKind::Arrow { .. } => false, - - // Array indexing can cause UB/crash if the pointer is invalid, - // so we must not eagerly evaluate it in conditional expressions. - ExprKind::Index { .. } => false, - - // Casts are pure if the operand is pure - ExprKind::Cast { expr, .. } => self.is_pure_expr(expr), - - // Assignments have side effects - ExprKind::Assign { .. } => false, - - // Sizeof and _Alignof are always pure (compile-time constant) - ExprKind::SizeofType(_) - | ExprKind::SizeofExpr(_) - | ExprKind::AlignofType(_) - | ExprKind::AlignofExpr(_) => true, - - // Comma expressions: pure if all sub-expressions are pure - ExprKind::Comma(exprs) => exprs.iter().all(|e| self.is_pure_expr(e)), - - // Compound literals may have side effects in initializers - ExprKind::CompoundLiteral { .. } => false, - - // Init lists may have side effects - ExprKind::InitList { .. } => false, - - // Statement expressions have side effects - ExprKind::StmtExpr { .. } => false, - - // Variadic builtins have side effects - ExprKind::VaStart { .. } - | ExprKind::VaArg { .. } - | ExprKind::VaEnd { .. } - | ExprKind::VaCopy { .. } => false, - - // Offsetof is always pure (compile-time constant) - ExprKind::OffsetOf { .. } => true, - - // Builtins: bswap, ctz, clz, popcount are pure - ExprKind::Bswap16 { arg } - | ExprKind::Bswap32 { arg } - | ExprKind::Bswap64 { arg } - | ExprKind::Ctz { arg } - | ExprKind::Ctzl { arg } - | ExprKind::Ctzll { arg } - | ExprKind::Clz { arg } - | ExprKind::Clzl { arg } - | ExprKind::Clzll { arg } - | ExprKind::Popcount { arg } - | ExprKind::Popcountl { arg } - | ExprKind::Popcountll { arg } - | ExprKind::Fabs { arg } - | ExprKind::Fabsf { arg } - | ExprKind::Fabsl { arg } - | ExprKind::Signbit { arg } - | ExprKind::Signbitf { arg } - | ExprKind::Signbitl { arg } => self.is_pure_expr(arg), - - // Alloca allocates memory - not pure - ExprKind::Alloca { .. } => false, - - // Memory builtins modify memory - not pure - ExprKind::Memset { .. } | ExprKind::Memcpy { .. } | ExprKind::Memmove { .. } => false, - - // Unreachable is pure (no side effects, just UB hint) - ExprKind::Unreachable => true, - - // Frame/return address builtins are pure (just read registers) - ExprKind::FrameAddress { .. } | ExprKind::ReturnAddress { .. } => true, - - // Setjmp/longjmp have control flow side effects - ExprKind::Setjmp { .. } | ExprKind::Longjmp { .. } => false, - - // Atomic operations have side effects (memory ordering) - ExprKind::C11AtomicInit { .. } - | ExprKind::C11AtomicLoad { .. } - | ExprKind::C11AtomicStore { .. } - | ExprKind::C11AtomicExchange { .. } - | ExprKind::C11AtomicCompareExchangeStrong { .. } - | ExprKind::C11AtomicCompareExchangeWeak { .. } - | ExprKind::C11AtomicFetchAdd { .. } - | ExprKind::C11AtomicFetchSub { .. } - | ExprKind::C11AtomicFetchAnd { .. } - | ExprKind::C11AtomicFetchOr { .. } - | ExprKind::C11AtomicFetchXor { .. } - | ExprKind::C11AtomicThreadFence { .. } - | ExprKind::C11AtomicSignalFence { .. } => false, - } - } - - /// Resolve an incomplete struct/union type to its complete definition. - /// - /// When a struct is forward-declared (e.g., `struct foo;`) and later - /// defined, the forward declaration creates an incomplete TypeId. - /// Pointers to the forward-declared type still reference this incomplete - /// TypeId even after the struct is fully defined with a new TypeId. - /// - /// This method looks up the complete definition in the symbol table - /// using the struct's tag name, returning the complete TypeId if found. - fn resolve_struct_type(&self, type_id: TypeId) -> TypeId { - let typ = self.types.get(type_id); - - // Only try to resolve struct/union types - if typ.kind != TypeKind::Struct && typ.kind != TypeKind::Union { - return type_id; - } - - // Check if this is an incomplete type with a tag - if let Some(ref composite) = typ.composite { - if composite.is_complete { - // Already complete, no resolution needed - return type_id; - } - if let Some(tag) = composite.tag { - // Look up the tag in the symbol table to find the complete type - if let Some(symbol) = self.symbols.lookup_tag(tag) { - // Return the complete type from the symbol table - return symbol.typ; - } - } - } - - // Couldn't resolve, return original - type_id - } - - /// Linearize an expression as an lvalue (get its address) - fn linearize_lvalue(&mut self, expr: &Expr) -> PseudoId { - match &expr.kind { - ExprKind::Ident(symbol_id) => { - let name_str = self.symbol_name(*symbol_id); - // For local variables, emit SymAddr to get the stack address - if let Some(local) = self.locals.get(symbol_id).cloned() { - // Check if this is a static local (sentinel value) - if local.sym.0 == u32::MAX { - // Static local - look up the global name - let key = format!("{}.{}", self.current_func_name, name_str); - if let Some(static_info) = self.static_locals.get(&key).cloned() { - let sym_id = self.alloc_pseudo(); - let pseudo = Pseudo::sym(sym_id, static_info.global_name); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - let result = self.alloc_pseudo(); - self.emit(Instruction::sym_addr( - result, - sym_id, - self.types.pointer_to(static_info.typ), - )); - return result; - } else { - unreachable!("static local sentinel without static_locals entry"); - } - } - // Check if this local holds a pointer to the actual data - // (e.g., va_list parameters due to array-to-pointer decay at call site). - // If so, load the pointer instead of taking the address. - if local.is_indirect { - // The local stores a pointer; load and return it - let ptr_type = self.types.pointer_to(local.typ); - let result = self.alloc_pseudo(); - let size = self.types.size_bits(ptr_type); - self.emit(Instruction::load(result, local.sym, 0, ptr_type, size)); - return result; - } - - let result = self.alloc_pseudo(); - self.emit(Instruction::sym_addr( - result, - local.sym, - self.types.pointer_to(local.typ), - )); - result - } else if let Some(¶m_pseudo) = self.var_map.get(&name_str) { - // Parameter whose address is taken - let param_type = self.expr_type(expr); - let type_kind = self.types.kind(param_type); - - // va_list parameters are special: the parameter value IS already a pointer - // to the va_list structure (due to array-to-pointer decay at call site). - // Return the pointer value directly instead of spilling. - if type_kind == TypeKind::VaList { - return param_pseudo; - } - - // For other parameters, spill to local storage. - // Parameters are pass-by-value in the IR (Arg pseudos), but if - // their address is taken, we need to copy to a stack slot first. - let size = self.types.size_bits(param_type); - - // Create a local variable to hold the parameter value - let local_sym = self.alloc_pseudo(); - let local_pseudo = Pseudo::sym(local_sym, format!("{}_spill", name_str)); - if let Some(func) = &mut self.current_func { - func.add_pseudo(local_pseudo); - func.locals.insert( - format!("{}_spill", name_str), - super::LocalVar { - sym: local_sym, - typ: param_type, - is_volatile: false, - is_atomic: false, - decl_block: self.current_bb, - explicit_align: None, // parameter spill storage - }, - ); - } - - // Store the parameter value to the local + ExprKind::Arrow { expr, member } => { + // Pointer member: pointer value is the base address + let ptr = self.linearize_expr(expr); + let ptr_type = self.expr_type(expr); + let base_struct_type = self.types.base_type(ptr_type).unwrap_or(typ); + let struct_type = self.resolve_struct_type(base_struct_type); + if let Some(member_info) = self.types.find_member(struct_type, *member) { self.emit(Instruction::store( - param_pseudo, - local_sym, - 0, - param_type, - size, + final_result, + ptr, + member_info.offset as i64, + typ, + store_size, )); - - // Update locals map so future accesses use the spilled location - self.insert_local( - *symbol_id, - LocalVarInfo { - sym: local_sym, - typ: param_type, - vla_size_sym: None, - vla_elem_type: None, - vla_dim_syms: vec![], - is_indirect: false, - }, - ); - - // Also update var_map to point to the local for future value accesses - // (so reads go through load instead of using the original Arg pseudo) - // Note: We leave var_map unchanged here because reads should use - // the stored value via load from the local. - - // Return address of the local - let result = self.alloc_pseudo(); - self.emit(Instruction::sym_addr( - result, - local_sym, - self.types.pointer_to(param_type), - )); - result - } else { - // Global variable - emit SymAddr to get its address - let sym_id = self.alloc_pseudo(); - let pseudo = Pseudo::sym(sym_id, name_str.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - let result = self.alloc_pseudo(); - let typ = self.expr_type(expr); - self.emit(Instruction::sym_addr( - result, - sym_id, - self.types.pointer_to(typ), - )); - result - } - } - ExprKind::Unary { - op: UnaryOp::Deref, - operand, - } => { - // *ptr as lvalue = ptr itself - self.linearize_expr(operand) - } - ExprKind::Member { - expr: inner, - member, - } => { - // s.m as lvalue = &s + offset(m) - let base = self.linearize_lvalue(inner); - let base_struct_type = self.expr_type(inner); - // Resolve if the struct type is incomplete (forward-declared) - let struct_type = self.resolve_struct_type(base_struct_type); - let member_info = - self.types - .find_member(struct_type, *member) - .unwrap_or_else(|| MemberInfo { - offset: 0, - typ: self.expr_type(expr), - bit_offset: None, - bit_width: None, - storage_unit_size: None, - }); - - if member_info.offset == 0 { - base - } else { - let offset_val = - self.emit_const(member_info.offset as i128, self.types.long_id); - let result = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Add, - result, - base, - offset_val, - self.types.long_id, - 64, - )); - result - } - } - ExprKind::Arrow { - expr: inner, - member, - } => { - // ptr->m as lvalue = ptr + offset(m) - let ptr = self.linearize_expr(inner); - let ptr_type = self.expr_type(inner); - let base_struct_type = self - .types - .base_type(ptr_type) - .unwrap_or_else(|| self.expr_type(expr)); - // Resolve if the struct type is incomplete (forward-declared) - let struct_type = self.resolve_struct_type(base_struct_type); - let member_info = - self.types - .find_member(struct_type, *member) - .unwrap_or_else(|| MemberInfo { - offset: 0, - typ: self.expr_type(expr), - bit_offset: None, - bit_width: None, - storage_unit_size: None, - }); - - if member_info.offset == 0 { - ptr - } else { - let offset_val = - self.emit_const(member_info.offset as i128, self.types.long_id); - let result = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Add, - result, - ptr, - offset_val, - self.types.long_id, - 64, - )); - result - } - } - ExprKind::Index { array, index } => { - // arr[idx] as lvalue = arr + idx * sizeof(elem) - // Handle commutative form: 0[arr] is equivalent to arr[0] - let array_type = self.expr_type(array); - let index_type = self.expr_type(index); - - let array_kind = self.types.kind(array_type); - let (ptr_expr, idx_expr, idx_type) = - if array_kind == TypeKind::Pointer || array_kind == TypeKind::Array { - (array, index, index_type) - } else { - // Swap: index is actually the pointer/array - (index, array, array_type) - }; - - let arr = self.linearize_expr(ptr_expr); - let idx = self.linearize_expr(idx_expr); - let elem_type = self.expr_type(expr); - let elem_size = self.types.size_bits(elem_type) / 8; - let elem_size_val = self.emit_const(elem_size as i128, self.types.long_id); - - // Sign-extend index to 64-bit for proper pointer arithmetic (negative indices) - let idx_extended = self.emit_convert(idx, idx_type, self.types.long_id); - - let offset = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Mul, - offset, - idx_extended, - elem_size_val, - self.types.long_id, - 64, - )); - - let addr = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Add, - addr, - arr, - offset, - self.types.long_id, - 64, - )); - addr - } - ExprKind::CompoundLiteral { typ, elements } => { - // Compound literal as lvalue: create it and return its address - // This is used for &(struct S){...} and large struct assignment like *p = (struct S){...} - let sym_id = self.alloc_pseudo(); - let unique_name = format!(".compound_literal.{}", sym_id.0); - let sym = Pseudo::sym(sym_id, unique_name.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(sym); - func.add_local( - &unique_name, - sym_id, - *typ, - false, - false, - self.current_bb, - None, - ); - } - - // For compound literals with partial initialization, C99 6.7.8p21 requires - // zero-initialization of all subobjects not explicitly initialized. - // Zero the entire compound literal first, then initialize specific members. - let type_kind = self.types.kind(*typ); - if type_kind == TypeKind::Struct - || type_kind == TypeKind::Union - || type_kind == TypeKind::Array - { - self.emit_aggregate_zero(sym_id, *typ); - } - - self.linearize_init_list(sym_id, *typ, elements); - - // Return address of the compound literal - let result = self.alloc_reg_pseudo(); - let ptr_type = self.types.pointer_to(*typ); - self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); - result - } - _ => { - // Fallback: just evaluate the expression (shouldn't happen for valid lvalues) - self.linearize_expr(expr) - } - } - } - - /// Linearize a type cast expression - fn linearize_cast(&mut self, inner_expr: &Expr, cast_type: TypeId) -> PseudoId { - let src = self.linearize_expr(inner_expr); - let src_type = self.expr_type(inner_expr); - - // Emit conversion if needed - let src_is_float = self.types.is_float(src_type); - let dst_is_float = self.types.is_float(cast_type); - - if src_is_float && !dst_is_float { - // Float to integer conversion - let result = self.alloc_reg_pseudo(); - // FCvtS for signed int, FCvtU for unsigned - let opcode = if self.types.is_unsigned(cast_type) { - Opcode::FCvtU - } else { - Opcode::FCvtS - }; - let dst_size = self.types.size_bits(cast_type); - let mut insn = Instruction::new(opcode) - .with_target(result) - .with_src(src) - .with_type_and_size(cast_type, dst_size); - insn.src_size = self.types.size_bits(src_type); - insn.src_typ = Some(src_type); - self.emit(insn); - result - } else if !src_is_float && dst_is_float { - // Integer to float conversion - let result = self.alloc_reg_pseudo(); - // SCvtF for signed int, UCvtF for unsigned - let opcode = if self.types.is_unsigned(src_type) { - Opcode::UCvtF - } else { - Opcode::SCvtF - }; - let dst_size = self.types.size_bits(cast_type); - let mut insn = Instruction::new(opcode) - .with_target(result) - .with_src(src) - .with_type_and_size(cast_type, dst_size); - insn.src_size = self.types.size_bits(src_type); - insn.src_typ = Some(src_type); - self.emit(insn); - result - } else if src_is_float && dst_is_float { - // Float to float conversion (e.g., float to double) - let src_size = self.types.size_bits(src_type); - let dst_size = self.types.size_bits(cast_type); - if src_size != dst_size { - let result = self.alloc_reg_pseudo(); - let mut insn = Instruction::new(Opcode::FCvtF) - .with_target(result) - .with_src(src) - .with_type_and_size(cast_type, dst_size); - insn.src_size = src_size; - insn.src_typ = Some(src_type); - self.emit(insn); - result - } else { - src // Same size, no conversion needed - } - } else { - // Integer to integer conversion - // Use emit_convert for proper type conversions including _Bool - self.emit_convert(src, src_type, cast_type) - } - } - - /// Linearize a struct member access expression (e.g., s.member) - fn linearize_member(&mut self, expr: &Expr, inner_expr: &Expr, member: StringId) -> PseudoId { - let base = self.linearize_lvalue(inner_expr); - let base_struct_type = self.expr_type(inner_expr); - let struct_type = self.resolve_struct_type(base_struct_type); - self.emit_member_access(base, struct_type, member, self.expr_type(expr)) - } - - /// Linearize a pointer member access expression (e.g., p->member) - fn linearize_arrow(&mut self, expr: &Expr, inner_expr: &Expr, member: StringId) -> PseudoId { - let ptr = self.linearize_expr(inner_expr); - let ptr_type = self.expr_type(inner_expr); - let base_struct_type = self - .types - .base_type(ptr_type) - .unwrap_or_else(|| self.expr_type(expr)); - let struct_type = self.resolve_struct_type(base_struct_type); - self.emit_member_access(ptr, struct_type, member, self.expr_type(expr)) - } - - /// Shared logic for member access (both `.` and `->`). - /// `base` is the address of the struct (for `.`) or the pointer value (for `->`). - fn emit_member_access( - &mut self, - base: PseudoId, - struct_type: TypeId, - member: StringId, - fallback_type: TypeId, - ) -> PseudoId { - let member_info = self - .types - .find_member(struct_type, member) - .unwrap_or(MemberInfo { - offset: 0, - typ: fallback_type, - bit_offset: None, - bit_width: None, - storage_unit_size: None, - }); - - // If member type is an array, return the address (arrays decay to pointers) - if self.types.kind(member_info.typ) == TypeKind::Array { - if member_info.offset == 0 { - base - } else { - let result = self.alloc_pseudo(); - let offset_val = self.emit_const(member_info.offset as i128, self.types.long_id); - self.emit(Instruction::binop( - Opcode::Add, - result, - base, - offset_val, - self.types.long_id, - 64, - )); - result - } - } else if let (Some(bit_offset), Some(bit_width), Some(storage_size)) = ( - member_info.bit_offset, - member_info.bit_width, - member_info.storage_unit_size, - ) { - // Bitfield read - self.emit_bitfield_load( - base, - member_info.offset, - bit_offset, - bit_width, - storage_size, - member_info.typ, - ) - } else { - let size = self.types.size_bits(member_info.typ); - let member_kind = self.types.kind(member_info.typ); - - // Large structs (size > 64) can't be loaded into registers - return address - if (member_kind == TypeKind::Struct || member_kind == TypeKind::Union) && size > 64 { - if member_info.offset == 0 { - base - } else { - let result = self.alloc_pseudo(); - let offset_val = - self.emit_const(member_info.offset as i128, self.types.long_id); - self.emit(Instruction::binop( - Opcode::Add, - result, - base, - offset_val, - self.types.long_id, - 64, - )); - result - } - } else { - let result = self.alloc_pseudo(); - self.emit(Instruction::load( - result, - base, - member_info.offset as i64, - member_info.typ, - size, - )); - result - } - } - } - - /// Linearize an array index expression (e.g., arr[i]) - fn linearize_index(&mut self, expr: &Expr, array: &Expr, index: &Expr) -> PseudoId { - // In C, a[b] is defined as *(a + b), so either operand can be the pointer - // Handle commutative form: 0[arr] is equivalent to arr[0] - let array_type = self.expr_type(array); - let index_type = self.expr_type(index); - - let array_kind = self.types.kind(array_type); - let (ptr_expr, idx_expr, idx_type) = - if array_kind == TypeKind::Pointer || array_kind == TypeKind::Array { - (array, index, index_type) - } else { - // Swap: index is actually the pointer/array - (index, array, array_type) - }; - - let arr = self.linearize_expr(ptr_expr); - let idx = self.linearize_expr(idx_expr); - - // Get element type from the expression type - let elem_type = self.expr_type(expr); - let ptr_typ = self.types.long_id; - - // Check if we're indexing a VLA's outer dimension - // This requires runtime stride computation. - // For int arr[n][m], accessing arr[i] needs stride = m * sizeof(int) - let elem_size_val = if let ExprKind::Ident(symbol_id) = &ptr_expr.kind { - if let Some(info) = self.locals.get(symbol_id).cloned() { - // Check if this is a multi-dimensional VLA AND elem_type is an array - // (meaning we're accessing an outer dimension, not the innermost) - if info.vla_dim_syms.len() > 1 && self.types.kind(elem_type) == TypeKind::Array { - // Compute runtime stride: - // stride = (product of inner dimensions) * sizeof(innermost element) - // For int arr[n][m] accessing arr[i]: stride = m * sizeof(int) - // For int arr[n][m][k] accessing arr[i]: stride = m * k * sizeof(int) - - // Get innermost element type and its size - let mut innermost = elem_type; - while self.types.kind(innermost) == TypeKind::Array { - innermost = self.types.base_type(innermost).unwrap_or(self.types.int_id); - } - let innermost_size = self.types.size_bytes(innermost) as i64; - - // Load all dimension sizes except the first (outermost we're indexing) - // and multiply them together - let mut stride: Option = None; - for dim_sym in info.vla_dim_syms.iter().skip(1) { - // Load the dimension size - let dim_val = self.alloc_pseudo(); - let load_insn = - Instruction::load(dim_val, *dim_sym, 0, self.types.ulong_id, 64); - self.emit(load_insn); - - stride = Some(match stride { - None => dim_val, - Some(prev) => { - let result = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Mul, - result, - prev, - dim_val, - ptr_typ, - 64, - )); - result - } - }); - } - - // Multiply by sizeof(innermost element) - let innermost_size_val = - self.emit_const(innermost_size as i128, self.types.long_id); - match stride { - Some(s) => { - let result = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Mul, - result, - s, - innermost_size_val, - ptr_typ, - 64, - )); - result - } - None => innermost_size_val, - } - } else { - // Not a multi-dimensional VLA or accessing innermost dimension - // Use compile-time size - let elem_size = self.types.size_bits(elem_type) / 8; - self.emit_const(elem_size as i128, self.types.long_id) - } - } else { - // Variable not found in locals (global or something else) - let elem_size = self.types.size_bits(elem_type) / 8; - self.emit_const(elem_size as i128, self.types.long_id) - } - } else { - // Not indexing an identifier directly (e.g., arr[i][j] where arr[i] is an Index) - // Use compile-time size - let elem_size = self.types.size_bits(elem_type) / 8; - self.emit_const(elem_size as i128, self.types.long_id) - }; - - // Sign-extend index to 64-bit for proper pointer arithmetic (negative indices) - let idx_extended = self.emit_convert(idx, idx_type, self.types.long_id); - - let offset = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Mul, - offset, - idx_extended, - elem_size_val, - ptr_typ, - 64, - )); - - let addr = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Add, - addr, - arr, - offset, - ptr_typ, - 64, - )); - - // If element type is an array, just return the address (arrays decay to pointers) - let elem_kind = self.types.kind(elem_type); - if elem_kind == TypeKind::Array { - addr - } else { - let size = self.types.size_bits(elem_type); - // Large structs/unions (> 64 bits) can't be loaded into registers - return address - // Assignment will handle the actual copy via emit_assign's large struct handling - if (elem_kind == TypeKind::Struct || elem_kind == TypeKind::Union) && size > 64 { - addr - } else { - let result = self.alloc_pseudo(); - self.emit(Instruction::load(result, addr, 0, elem_type, size)); - result - } - } - } - - /// Linearize a function call expression - fn linearize_call(&mut self, expr: &Expr, func_expr: &Expr, args: &[Expr]) -> PseudoId { - // Determine if this is a direct or indirect call. - // We need to check the TYPE of the function expression: - // - If it's TypeKind::Function, it's a direct call to a function - // - If it's TypeKind::Pointer to Function, it's an indirect call through function pointer - let is_function_pointer = func_expr.typ.is_some_and(|t| { - let typ = self.types.get(t); - typ.kind == TypeKind::Pointer - }); - - let (func_name, indirect_target) = match &func_expr.kind { - ExprKind::Ident(symbol_id) if !is_function_pointer => { - // Direct call to named function (not a function pointer variable) - (self.symbol_name(*symbol_id), None) - } - ExprKind::Unary { - op: UnaryOp::Deref, - operand, - } => { - // Explicit dereference form: (*fp)(args) or (*fpp)(args) - // Check the type of the operand to determine behavior: - // - If operand is function pointer (*fn): call through operand value - // - If operand is pointer-to-function-pointer (**fn): dereference first - let operand_type = self.expr_type(operand); - let operand_kind = self.types.kind(operand_type); - if operand_kind == TypeKind::Pointer { - // Check what it points to - let base_type = self.types.base_type(operand_type); - let base_kind = base_type.map(|t| self.types.kind(t)); - if base_kind == Some(TypeKind::Function) { - // Operand is function pointer - use its value directly - let func_addr = self.linearize_expr(operand); - ("".to_string(), Some(func_addr)) - } else { - // Operand is pointer-to-pointer - dereference to get function pointer - let func_addr = self.linearize_expr(func_expr); - ("".to_string(), Some(func_addr)) - } - } else { - // Unknown case - try to linearize the full expression - let func_addr = self.linearize_expr(func_expr); - ("".to_string(), Some(func_addr)) - } - } - _ => { - // Indirect call through function pointer variable: fp(args) - // This includes identifiers that are function pointer variables - let func_addr = self.linearize_expr(func_expr); - ("".to_string(), Some(func_addr)) - } - }; - - let typ = self.expr_type(expr); // Use evaluated type (function return type) - - // Check if this is a variadic function call and if it's noreturn - // If the function expression has a type, check its variadic and noreturn flags - let (variadic_arg_start, is_noreturn_call) = if let Some(func_type) = func_expr.typ { - let ft = self.types.get(func_type); - let variadic = if ft.variadic { - // Variadic args start after the fixed parameters - ft.params.as_ref().map(|p| p.len()) - } else { - None - }; - (variadic, ft.noreturn) - } else { - (None, false) // No type info, assume non-variadic and returns - }; - - // Check if function returns a large struct or complex type - // Large structs: allocate space and pass address as hidden first argument - // Complex types: allocate local storage for result (needs stack for 16-byte value) - // Two-register structs (9-16 bytes): allocate local storage, codegen stores two regs - let typ_kind = self.types.kind(typ); - let struct_size_bits = self.types.size_bits(typ); - let returns_large_struct = (typ_kind == TypeKind::Struct || typ_kind == TypeKind::Union) - && struct_size_bits > self.target.max_aggregate_register_bits; - let returns_two_reg_struct = (typ_kind == TypeKind::Struct || typ_kind == TypeKind::Union) - && struct_size_bits > 64 - && struct_size_bits <= 128 - && !returns_large_struct; - let returns_complex = self.types.is_complex(typ); - - let (result_sym, mut arg_vals, mut arg_types_vec) = if returns_large_struct { - // Allocate local storage for the return value - let sret_sym = self.alloc_pseudo(); - let sret_pseudo = Pseudo::sym(sret_sym, format!("__sret_{}", sret_sym.0)); - if let Some(func) = &mut self.current_func { - func.add_pseudo(sret_pseudo); - // Internal sret storage is never volatile or atomic - func.add_local( - format!("__sret_{}", sret_sym.0), - sret_sym, - typ, - false, // not volatile - false, // not atomic - self.current_bb, - None, // no explicit alignment - ); - } - - // Get address of the allocated space - let sret_addr = self.alloc_reg_pseudo(); - self.emit(Instruction::sym_addr( - sret_addr, - sret_sym, - self.types.pointer_to(typ), - )); - - // Hidden return pointer is the first argument (pointer type) - (sret_sym, vec![sret_addr], vec![self.types.pointer_to(typ)]) - } else if returns_two_reg_struct { - // Two-register struct returns: allocate local storage for the result - // Codegen will store RAX+RDX (x86-64) or X0+X1 (AArch64) to this location - let local_sym = self.alloc_pseudo(); - let unique_name = format!("__2reg_{}", local_sym.0); - let local_pseudo = Pseudo::sym(local_sym, unique_name.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(local_pseudo); - func.add_local( - &unique_name, - local_sym, - typ, - false, - false, - self.current_bb, - None, - ); - } - (local_sym, Vec::new(), Vec::new()) - } else if returns_complex { - // Complex returns: allocate local storage for the result - // Complex values are 16 bytes and need stack storage - let local_sym = self.alloc_pseudo(); - let unique_name = format!("__cret_{}", local_sym.0); - let local_pseudo = Pseudo::sym(local_sym, unique_name.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(local_pseudo); - func.add_local( - &unique_name, - local_sym, - typ, - false, - false, - self.current_bb, - None, - ); - } - (local_sym, Vec::new(), Vec::new()) - } else if (typ_kind == TypeKind::Struct || typ_kind == TypeKind::Union) - && struct_size_bits > 0 - && struct_size_bits <= 64 - { - // Small struct/union returns (<=64 bits, single register): - // Allocate local storage so the result has a stable address. - // The codegen stores RAX (or XMM0) to this location. - // Without this, the result pseudo holds a raw value which - // emit_assign's block_copy would incorrectly dereference as a pointer. - let local_sym = self.alloc_pseudo(); - let unique_name = format!("__sret1_{}", local_sym.0); - let local_pseudo = Pseudo::sym(local_sym, unique_name.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(local_pseudo); - func.add_local( - &unique_name, - local_sym, - typ, - false, - false, - self.current_bb, - None, - ); - } - (local_sym, Vec::new(), Vec::new()) - } else { - let result = self.alloc_pseudo(); - (result, Vec::new(), Vec::new()) - }; - - // Get formal parameter types for implicit widening conversions. - // When a narrow int (e.g., int) is passed to a wider parameter (e.g., long), - // C requires implicit promotion. This is transparent for non-inlined calls - // (the ABI handles it), but inlining exposes the mismatch since the argument - // pseudo is used directly without conversion. - let formal_param_types: Option> = func_expr.typ.and_then(|ft_id| { - let ft = self.types.get(ft_id); - ft.params.clone() - }); - - // Linearize regular arguments - // For large structs, pass by reference (address) instead of by value - // Note: We pass structs > 64 bits by reference. While the ABI allows - // two-register passing for 9-16 byte structs, we don't implement that yet. - // For complex types, pass address so codegen can load real/imag into XMM registers - // For arrays (including VLAs), decay to pointer - for (arg_idx, a) in args.iter().enumerate() { - let mut arg_type = self.expr_type(a); - let arg_kind = self.types.kind(arg_type); - let arg_val = if (arg_kind == TypeKind::Struct || arg_kind == TypeKind::Union) - && self.types.size_bits(arg_type) > 64 - { - let size_bits = self.types.size_bits(arg_type); - if size_bits > 128 { - // Large struct (> 16 bytes): keep struct type so ABI classifies as - // Indirect/MEMORY. The pseudo is still the struct's address; - // codegen will copy bytes to the stack. - arg_types_vec.push(arg_type); - } else { - // Medium struct (9-16 bytes): check ABI classification - let abi = get_abi_for_conv(self.current_calling_conv, self.target); - let class = abi.classify_param(arg_type, self.types); - let is_two_fp_regs = - matches!( - class, - crate::abi::ArgClass::Direct { ref classes, .. } - if classes.len() == 2 - && classes.iter().all(|c| *c == crate::abi::RegClass::Sse) - ) || matches!(class, crate::abi::ArgClass::Hfa { count: 2, .. }); - if is_two_fp_regs { - // All-SSE struct: keep struct type for 2-XMM passing - arg_types_vec.push(arg_type); - } else { - // Integer or mixed struct: pass as pointer (existing behavior) - arg_types_vec.push(self.types.pointer_to(arg_type)); - } - } - // For lvalue expressions (identifiers, member access), linearize_lvalue - // returns a symaddr (pointer). For rvalue expressions (call results), - // linearize_lvalue falls through to linearize_expr which returns the - // data pseudo, not its address. In that case we must emit a symaddr - // to get the address for pointer-based struct passing. - let is_lvalue = matches!( - a.kind, - ExprKind::Ident(_) - | ExprKind::Member { .. } - | ExprKind::Arrow { .. } - | ExprKind::Index { .. } - | ExprKind::Unary { - op: crate::parse::ast::UnaryOp::Deref, - .. - } - | ExprKind::CompoundLiteral { .. } - ); - if is_lvalue { - self.linearize_lvalue(a) - } else { - // Rvalue (e.g., function call returning struct): evaluate, - // then take address of the result local - let val = self.linearize_expr(a); - let result = self.alloc_reg_pseudo(); - let ptr_type = self.types.pointer_to(arg_type); - self.emit(Instruction::sym_addr(result, val, ptr_type)); - result - } - } else if self.types.is_complex(arg_type) { - // Complex types: pass address, codegen loads real/imag into XMM registers - // Type stays as complex (not pointer) so codegen knows it's complex - arg_types_vec.push(arg_type); - self.linearize_lvalue(a) - } else if arg_kind == TypeKind::Array { - // Array decay to pointer (C99 6.3.2.1) - // This applies to both fixed-size arrays and VLAs - let elem_type = self.types.base_type(arg_type).unwrap_or(self.types.int_id); - arg_types_vec.push(self.types.pointer_to(elem_type)); - self.linearize_expr(a) - } else if arg_kind == TypeKind::VaList { - // va_list decay to pointer (C99 7.15.1) - // va_list is defined as __va_list_tag[1] (an array), so it decays to - // a pointer when passed to a function taking va_list parameter - arg_types_vec.push(self.types.pointer_to(arg_type)); - self.linearize_lvalue(a) - } else if arg_kind == TypeKind::Function { - // Function decay to pointer (C99 6.3.2.1) - // Function names passed as arguments decay to function pointers - arg_types_vec.push(self.types.pointer_to(arg_type)); - self.linearize_expr(a) - } else { - let mut val = self.linearize_expr(a); - - // Implicit argument conversion when actual type differs from - // formal parameter type. Covers: - // - Integer widening: int→long (sign/zero extend) - // - FP widening/narrowing: float↔double↔long double - // - Int→FP: uint32_t→double (e.g., log10(uint32_t_val)) - // - FP→Int: rare but legal - if let Some(ref params) = formal_param_types { - if arg_idx < params.len() { - let param_type = params[arg_idx]; - let arg_size = self.types.size_bits(arg_type); - let param_size = self.types.size_bits(param_type); - let arg_is_int = self.types.is_integer(arg_type); - let param_is_int = self.types.is_integer(param_type); - let arg_is_fp = self.types.is_float(arg_type); - let param_is_fp = self.types.is_float(param_type); - - let needs_convert = - // Integer widening (e.g., int→long) - (arg_is_int && param_is_int && arg_size < param_size - && arg_size <= 32 && param_size <= 64) - // FP size mismatch (float→double, long double→double, etc.) - || (arg_is_fp && param_is_fp && arg_size != param_size) - // Integer to FP (uint32_t→double, int→float, etc.) - || (arg_is_int && param_is_fp) - // FP to integer (rare but legal) - || (arg_is_fp && param_is_int); - - if needs_convert { - val = self.emit_convert(val, arg_type, param_type); - arg_type = param_type; - } - } - } - - // C99 6.5.2.2p7: Default argument promotions for variadic args. - // float/_Float16 are promoted to double; integer promotions - // also apply (already handled above for integers). - if let Some(va_start) = variadic_arg_start { - if arg_idx >= va_start - && matches!( - self.types.kind(arg_type), - TypeKind::Float | TypeKind::Float16 - ) - { - val = self.emit_convert(val, arg_type, self.types.double_id); - arg_type = self.types.double_id; - } - } - - arg_types_vec.push(arg_type); - val - }; - arg_vals.push(arg_val); - } - - // Compute ABI classification for parameters and return value. - // This provides rich metadata for the backend to generate correct calling code. - // Use the current function's calling convention (which may be overridden via attributes) - let abi = get_abi_for_conv(self.current_calling_conv, self.target); - let param_classes: Vec<_> = arg_types_vec - .iter() - .map(|&t| abi.classify_param(t, self.types)) - .collect(); - let ret_class = abi.classify_return(typ, self.types); - let call_abi_info = Box::new(CallAbiInfo::new(param_classes, ret_class)); - - if returns_large_struct { - // For large struct returns, the return value is the address - // stored in result_sym (which is a local symbol containing the struct) - let result = self.alloc_reg_pseudo(); - let ptr_typ = self.types.pointer_to(typ); - let mut call_insn = if let Some(func_addr) = indirect_target { - // Indirect call through function pointer - Instruction::call_indirect( - Some(result), - func_addr, - arg_vals, - arg_types_vec, - ptr_typ, - 64, // pointers are 64-bit - ) - } else { - // Direct call - Instruction::call( - Some(result), - &func_name, - arg_vals, - arg_types_vec, - ptr_typ, - 64, // pointers are 64-bit - ) - }; - call_insn.variadic_arg_start = variadic_arg_start; - call_insn.is_noreturn_call = is_noreturn_call; - call_insn.abi_info = Some(call_abi_info); - self.emit(call_insn); - // Return the symbol (address) where struct is stored - result_sym - } else { - let ret_size = self.types.size_bits(typ); - let mut call_insn = if let Some(func_addr) = indirect_target { - // Indirect call through function pointer - Instruction::call_indirect( - Some(result_sym), - func_addr, - arg_vals, - arg_types_vec, - typ, - ret_size, - ) - } else { - // Direct call - Instruction::call( - Some(result_sym), - &func_name, - arg_vals, - arg_types_vec, - typ, - ret_size, - ) - }; - call_insn.variadic_arg_start = variadic_arg_start; - call_insn.is_noreturn_call = is_noreturn_call; - call_insn.abi_info = Some(call_abi_info); - self.emit(call_insn); - result_sym - } - } - - /// Linearize a post-increment or post-decrement expression - fn linearize_postop(&mut self, operand: &Expr, is_inc: bool) -> PseudoId { - let val = self.linearize_expr(operand); - let typ = self.expr_type(operand); - let is_float = self.types.is_float(typ); - let is_ptr = self.types.kind(typ) == TypeKind::Pointer; - - // For locals, we need to save the old value before updating - // because the pseudo will be reloaded from stack which gets overwritten - let is_local = if let ExprKind::Ident(symbol_id) = &operand.kind { - self.locals.contains_key(symbol_id) - } else { - false - }; - - let old_val = if is_local { - // Copy the old value to a temp - let temp = self.alloc_reg_pseudo(); - self.emit( - Instruction::new(Opcode::Copy) - .with_target(temp) - .with_src(val) - .with_type(typ) - .with_size(self.types.size_bits(typ)), - ); - temp - } else { - val - }; - - // For pointers, increment/decrement by element size; for others, by 1 - let delta = if is_ptr { - let elem_type = self.types.base_type(typ).unwrap_or(self.types.char_id); - let elem_size = self.types.size_bits(elem_type) / 8; - self.emit_const(elem_size as i128, self.types.long_id) - } else if is_float { - self.emit_fconst(1.0, typ) - } else { - self.emit_const(1, typ) - }; - let result = self.alloc_reg_pseudo(); - let opcode = if is_float { - if is_inc { - Opcode::FAdd - } else { - Opcode::FSub - } - } else if is_inc { - Opcode::Add - } else { - Opcode::Sub - }; - let arith_type = if is_ptr { self.types.long_id } else { typ }; - let arith_size = self.types.size_bits(arith_type); - self.emit(Instruction::binop( - opcode, result, val, delta, arith_type, arith_size, - )); - - // For _Bool, normalize the result (any non-zero -> 1) - let final_result = if self.types.kind(typ) == TypeKind::Bool { - self.emit_convert(result, self.types.int_id, typ) - } else { - result - }; - - // Store to local, update parameter mapping, or store through pointer - let store_size = self.types.size_bits(typ); - match &operand.kind { - ExprKind::Ident(symbol_id) => { - let name_str = self.symbol_name(*symbol_id); - if let Some(local) = self.locals.get(symbol_id).cloned() { - // Check if this is a static local (sentinel value) - if local.sym.0 == u32::MAX { - self.emit_static_local_store(&name_str, final_result, typ, store_size); - } else { - // Regular local variable - self.emit(Instruction::store( - final_result, - local.sym, - 0, - typ, - store_size, - )); - } - } else if self.var_map.contains_key(&name_str) { - self.var_map.insert(name_str.clone(), final_result); - } else { - // Global variable - emit store - let sym_id = self.alloc_pseudo(); - let pseudo = Pseudo::sym(sym_id, name_str.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - self.emit(Instruction::store(final_result, sym_id, 0, typ, store_size)); - } - } - ExprKind::Unary { - op: UnaryOp::Deref, - operand: ptr_expr, - } => { - // (*p)++ or (*p)-- - store back through the pointer - let addr = self.linearize_expr(ptr_expr); - self.emit(Instruction::store(final_result, addr, 0, typ, store_size)); - } - ExprKind::Member { expr, member } => { - // Struct member: get address and store with offset - let base = self.linearize_lvalue(expr); - let base_struct_type = self.expr_type(expr); - let struct_type = self.resolve_struct_type(base_struct_type); - if let Some(member_info) = self.types.find_member(struct_type, *member) { - self.emit(Instruction::store( - final_result, - base, - member_info.offset as i64, - typ, - store_size, - )); - } - } - ExprKind::Arrow { expr, member } => { - // Pointer member: pointer value is the base address - let ptr = self.linearize_expr(expr); - let ptr_type = self.expr_type(expr); - let base_struct_type = self.types.base_type(ptr_type).unwrap_or(typ); - let struct_type = self.resolve_struct_type(base_struct_type); - if let Some(member_info) = self.types.find_member(struct_type, *member) { - self.emit(Instruction::store( - final_result, - ptr, - member_info.offset as i64, - typ, - store_size, - )); - } - } - ExprKind::Index { array, index } => { - // Array subscript: compute address and store - let array_type = self.expr_type(array); - let index_type = self.expr_type(index); - let array_kind = self.types.kind(array_type); - let (ptr_expr, idx_expr, idx_type) = - if array_kind == TypeKind::Pointer || array_kind == TypeKind::Array { - (array.as_ref(), index.as_ref(), index_type) - } else { - (index.as_ref(), array.as_ref(), array_type) - }; - let arr = self.linearize_expr(ptr_expr); - let idx = self.linearize_expr(idx_expr); - let elem_size = store_size / 8; - let elem_size_val = self.emit_const(elem_size as i128, self.types.long_id); - let idx_extended = self.emit_convert(idx, idx_type, self.types.long_id); - let offset = self.alloc_pseudo(); - let ptr_typ = self.types.long_id; - self.emit(Instruction::binop( - Opcode::Mul, - offset, - idx_extended, - elem_size_val, - ptr_typ, - 64, - )); - let addr = self.alloc_reg_pseudo(); - self.emit(Instruction::binop( - Opcode::Add, - addr, - arr, - offset, - ptr_typ, - 64, - )); - self.emit(Instruction::store(final_result, addr, 0, typ, store_size)); - } - _ => {} - } - - old_val // Return old value - } - - /// Linearize a binary expression (arithmetic, comparison, logical operators) - fn linearize_binary( - &mut self, - expr: &Expr, - op: BinaryOp, - left: &Expr, - right: &Expr, - ) -> PseudoId { - // Handle short-circuit operators before linearizing both operands - // C99 requires that && and || only evaluate the RHS if needed - if op == BinaryOp::LogAnd { - return self.emit_logical_and(left, right); - } - if op == BinaryOp::LogOr { - return self.emit_logical_or(left, right); - } - - let left_typ = self.expr_type(left); - let right_typ = self.expr_type(right); - let result_typ = self.expr_type(expr); - - // Check for pointer arithmetic: ptr +/- int or int + ptr - let left_kind = self.types.kind(left_typ); - let right_kind = self.types.kind(right_typ); - let left_is_ptr_or_arr = left_kind == TypeKind::Pointer || left_kind == TypeKind::Array; - let right_is_ptr_or_arr = right_kind == TypeKind::Pointer || right_kind == TypeKind::Array; - let is_ptr_arith = (op == BinaryOp::Add || op == BinaryOp::Sub) - && ((left_is_ptr_or_arr && self.types.is_integer(right_typ)) - || (self.types.is_integer(left_typ) && right_is_ptr_or_arr)); - - // Check for pointer difference: ptr - ptr - let is_ptr_diff = op == BinaryOp::Sub && left_is_ptr_or_arr && right_is_ptr_or_arr; - - if is_ptr_diff { - // Pointer difference: (ptr1 - ptr2) / element_size - let left_val = self.linearize_expr(left); - let right_val = self.linearize_expr(right); - - // Compute byte difference - let byte_diff = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Sub, - byte_diff, - left_val, - right_val, - self.types.long_id, - 64, - )); - - // Get element size from the pointer type - let elem_type = self.types.base_type(left_typ).unwrap_or(self.types.char_id); - let elem_size = self.types.size_bits(elem_type) / 8; - - // Divide by element size - let scale = self.emit_const(elem_size as i128, self.types.long_id); - let result = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::DivS, - result, - byte_diff, - scale, - self.types.long_id, - 64, - )); - result - } else if is_ptr_arith { - // Pointer arithmetic: scale integer operand by element size - let (ptr_val, ptr_typ, int_val) = if left_is_ptr_or_arr { - let ptr = self.linearize_expr(left); - let int = self.linearize_expr(right); - (ptr, left_typ, int) - } else { - // int + ptr case - let int = self.linearize_expr(left); - let ptr = self.linearize_expr(right); - (ptr, right_typ, int) - }; - - // Get element size - let elem_type = self.types.base_type(ptr_typ).unwrap_or(self.types.char_id); - let elem_size = self.types.size_bits(elem_type) / 8; - - // Scale the integer by element size - let scale = self.emit_const(elem_size as i128, self.types.long_id); - let scaled_offset = self.alloc_pseudo(); - // Extend int_val to 64-bit for proper address arithmetic - let actual_int_type = if left_is_ptr_or_arr { - right_typ - } else { - left_typ - }; - let int_val_extended = self.emit_convert(int_val, actual_int_type, self.types.long_id); - self.emit(Instruction::binop( - Opcode::Mul, - scaled_offset, - int_val_extended, - scale, - self.types.long_id, - 64, - )); - - // Add (or subtract) to pointer - let result = self.alloc_pseudo(); - let opcode = if op == BinaryOp::Sub { - Opcode::Sub - } else { - Opcode::Add - }; - self.emit(Instruction::binop( - opcode, - result, - ptr_val, - scaled_offset, - self.types.long_id, - 64, - )); - result - } else if self.types.is_complex(result_typ) { - // Complex arithmetic: expand to real/imaginary operations - // For complex types, we need addresses to load real/imag parts - let left_addr = self.linearize_lvalue(left); - let right_addr = self.linearize_lvalue(right); - self.emit_complex_binary(op, left_addr, right_addr, result_typ) - } else { - // For comparisons, compute common type for both operands - // (usual arithmetic conversions) - let operand_typ = if op.is_comparison() { - self.common_type(left_typ, right_typ) - } else { - result_typ - }; - - // Linearize operands - let left_val = self.linearize_expr(left); - let right_val = self.linearize_expr(right); - - // Emit type conversions if needed - let left_val = self.emit_convert(left_val, left_typ, operand_typ); - let right_val = self.emit_convert(right_val, right_typ, operand_typ); - - self.emit_binary(op, left_val, right_val, result_typ, operand_typ) - } - } - - /// Linearize a unary expression (prefix operators, address-of, dereference, etc.) - fn linearize_unary(&mut self, expr: &Expr, op: UnaryOp, operand: &Expr) -> PseudoId { - // Handle AddrOf specially - we need the lvalue address, not the value - if op == UnaryOp::AddrOf { - return self.linearize_lvalue(operand); - } - - // Handle PreInc/PreDec specially - they need store-back - if op == UnaryOp::PreInc || op == UnaryOp::PreDec { - // For deref operands like *s++, compute the lvalue address once - // to avoid re-evaluating side effects (PostInc etc.) when storing back. - let deref_addr = if let ExprKind::Unary { - op: UnaryOp::Deref, .. - } = &operand.kind - { - let addr = self.linearize_lvalue(operand); - Some(addr) - } else { - None - }; - // If we pre-computed the deref address, load from it. - // Otherwise, evaluate normally. - let val = if let Some(addr) = deref_addr { - let deref_typ = self.expr_type(operand); - let deref_size = self.types.size_bits(deref_typ); - let loaded = self.alloc_reg_pseudo(); - self.emit(Instruction::load(loaded, addr, 0, deref_typ, deref_size)); - loaded - } else { - self.linearize_expr(operand) - }; - let typ = self.expr_type(operand); - let is_float = self.types.is_float(typ); - let is_ptr = self.types.kind(typ) == TypeKind::Pointer; - - // Compute new value - for pointers, scale by element size - let increment = if is_ptr { - let elem_type = self.types.base_type(typ).unwrap_or(self.types.char_id); - let elem_size = self.types.size_bits(elem_type) / 8; - self.emit_const(elem_size as i128, self.types.long_id) - } else if is_float { - self.emit_fconst(1.0, typ) - } else { - self.emit_const(1, typ) - }; - let result = self.alloc_reg_pseudo(); - let opcode = if is_float { - if op == UnaryOp::PreInc { - Opcode::FAdd - } else { - Opcode::FSub - } - } else if op == UnaryOp::PreInc { - Opcode::Add - } else { - Opcode::Sub - }; - let size = self.types.size_bits(typ); - self.emit(Instruction::binop( - opcode, result, val, increment, typ, size, - )); - - // For _Bool, normalize the result (any non-zero -> 1) - let final_result = if self.types.kind(typ) == TypeKind::Bool { - self.emit_convert(result, self.types.int_id, typ) - } else { - result - }; - - // Store back to the lvalue - let store_size = self.types.size_bits(typ); - match &operand.kind { - ExprKind::Ident(symbol_id) => { - let name_str = self.symbol_name(*symbol_id); - if let Some(local) = self.locals.get(symbol_id).cloned() { - // Check if this is a static local (sentinel value) - if local.sym.0 == u32::MAX { - self.emit_static_local_store(&name_str, final_result, typ, store_size); - } else { - // Regular local variable - self.emit(Instruction::store( - final_result, - local.sym, - 0, - typ, - store_size, - )); - } - } else if self.var_map.contains_key(&name_str) { - self.var_map.insert(name_str.clone(), final_result); - } else { - // Global variable - emit store - let sym_id = self.alloc_pseudo(); - let pseudo = Pseudo::sym(sym_id, name_str.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - self.emit(Instruction::store(final_result, sym_id, 0, typ, store_size)); - } - } - ExprKind::Member { expr, member } => { - // Struct member: get address and store with offset - let base = self.linearize_lvalue(expr); - let base_struct_type = self.expr_type(expr); - let struct_type = self.resolve_struct_type(base_struct_type); - if let Some(member_info) = self.types.find_member(struct_type, *member) { - self.emit(Instruction::store( - final_result, - base, - member_info.offset as i64, - typ, - store_size, - )); - } - } - ExprKind::Arrow { expr, member } => { - // Pointer member: pointer value is the base address - let ptr = self.linearize_expr(expr); - let ptr_type = self.expr_type(expr); - let base_struct_type = self.types.base_type(ptr_type).unwrap_or(typ); - let struct_type = self.resolve_struct_type(base_struct_type); - if let Some(member_info) = self.types.find_member(struct_type, *member) { - self.emit(Instruction::store( - final_result, - ptr, - member_info.offset as i64, - typ, - store_size, - )); - } - } - ExprKind::Unary { - op: UnaryOp::Deref, .. - } => { - // Dereference: store to the pointer address. - // Use the pre-computed address to avoid re-evaluating - // side effects (e.g., s++ in ++*s++). - let ptr = deref_addr.expect("deref_addr should be set for Deref operand"); - self.emit(Instruction::store(final_result, ptr, 0, typ, store_size)); - } - ExprKind::Index { array, index } => { - // Array subscript: compute address and store - let array_type = self.expr_type(array); - let index_type = self.expr_type(index); - let array_kind = self.types.kind(array_type); - let (ptr_expr, idx_expr, idx_type) = - if array_kind == TypeKind::Pointer || array_kind == TypeKind::Array { - (array.as_ref(), index.as_ref(), index_type) - } else { - (index.as_ref(), array.as_ref(), array_type) - }; - let arr = self.linearize_expr(ptr_expr); - let idx = self.linearize_expr(idx_expr); - let elem_size = store_size / 8; - let elem_size_val = self.emit_const(elem_size as i128, self.types.long_id); - let idx_extended = self.emit_convert(idx, idx_type, self.types.long_id); - let offset = self.alloc_pseudo(); - let ptr_typ = self.types.long_id; - self.emit(Instruction::binop( - Opcode::Mul, - offset, - idx_extended, - elem_size_val, - ptr_typ, - 64, - )); - let addr = self.alloc_reg_pseudo(); - self.emit(Instruction::binop( - Opcode::Add, - addr, - arr, - offset, - ptr_typ, - 64, - )); - self.emit(Instruction::store(final_result, addr, 0, typ, store_size)); - } - _ => { - // Fallback: shouldn't happen for valid lvalues - } - } - - return final_result; - } - - let src = self.linearize_expr(operand); - let result_typ = self.expr_type(expr); - let operand_typ = self.expr_type(operand); - // For logical NOT, use operand type for comparison size - let typ = if op == UnaryOp::Not { - operand_typ - } else { - result_typ - }; - self.emit_unary(op, src, typ) - } - - /// Linearize an identifier expression (variable reference) - /// Handle __func__, __FUNCTION__, __PRETTY_FUNCTION__ builtins - fn linearize_func_name(&mut self) -> PseudoId { - // C99 6.4.2.2: __func__ behaves as if declared: - // static const char __func__[] = "function-name"; - // GCC extensions: __FUNCTION__ and __PRETTY_FUNCTION__ behave the same way - - // Add function name as a string literal to the module - let label = self.module.add_string(self.current_func_name.clone()); - - // Create symbol pseudo for the string label - let sym_id = self.alloc_pseudo(); - let sym_pseudo = Pseudo::sym(sym_id, label); - if let Some(func) = &mut self.current_func { - func.add_pseudo(sym_pseudo); - } - - // Create result pseudo for the address - let result = self.alloc_reg_pseudo(); - - // Type: const char* (pointer to char) - let char_type = self.types.char_id; - let ptr_type = self.types.pointer_to(char_type); - self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); - result - } - - fn linearize_ident(&mut self, expr: &Expr, symbol_id: SymbolId) -> PseudoId { - let sym = self.symbols.get(symbol_id); - let name_str = self.str(sym.name).to_string(); - - // First check if it's an enum constant - if sym.is_enum_constant() { - if let Some(value) = sym.enum_value { - return self.emit_const(value as i128, self.types.int_id); - } - } - - // Check if it's a local variable - if let Some(local) = self.locals.get(&symbol_id).cloned() { - // Check if this is a static local (sentinel value) - if local.sym.0 == u32::MAX { - // Static local - look up the global name and treat as global - let key = format!("{}.{}", self.current_func_name, &name_str); - if let Some(static_info) = self.static_locals.get(&key).cloned() { - let sym_id = self.alloc_pseudo(); - let pseudo = Pseudo::sym(sym_id, static_info.global_name); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - let typ = static_info.typ; - let type_kind = self.types.kind(typ); - let size = self.types.size_bits(typ); - // Arrays decay to pointers - get address, not value - if type_kind == TypeKind::Array { - let result = self.alloc_pseudo(); - let elem_type = self.types.base_type(typ).unwrap_or(self.types.int_id); - let ptr_type = self.types.pointer_to(elem_type); - self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); - return result; - } else if type_kind == TypeKind::VaList { - // va_list is defined as __va_list_tag[1] (an array type), so it decays to - // a pointer when used in expressions (C99 6.3.2.1, 7.15.1) - let result = self.alloc_pseudo(); - let ptr_type = self.types.pointer_to(typ); - self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); - return result; - } else if (type_kind == TypeKind::Struct || type_kind == TypeKind::Union) - && size > 64 - { - // Large structs can't be loaded into registers - return address - let result = self.alloc_pseudo(); - let ptr_type = self.types.pointer_to(typ); - self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); - return result; - } else { - let result = self.alloc_pseudo(); - self.emit(Instruction::load(result, sym_id, 0, typ, size)); - return result; - } - } else { - unreachable!("static local sentinel without static_locals entry"); - } - } - let result = self.alloc_reg_pseudo(); - let type_kind = self.types.kind(local.typ); - let size = self.types.size_bits(local.typ); - // Arrays decay to pointers - get address, not value - if type_kind == TypeKind::Array { - let elem_type = self.types.base_type(local.typ).unwrap_or(self.types.int_id); - let ptr_type = self.types.pointer_to(elem_type); - self.emit(Instruction::sym_addr(result, local.sym, ptr_type)); - } else if type_kind == TypeKind::VaList { - // va_list is defined as __va_list_tag[1] (an array type), so it decays to - // a pointer when used in expressions (C99 6.3.2.1, 7.15.1) - if local.is_indirect { - // va_list parameter: local holds a pointer to the va_list struct - // Load the pointer value (array decay already happened at call site) - let ptr_type = self.types.pointer_to(local.typ); - let ptr_size = self.types.size_bits(ptr_type); - self.emit(Instruction::load(result, local.sym, 0, ptr_type, ptr_size)); - } else { - // Regular va_list local: take address (normal array decay) - let ptr_type = self.types.pointer_to(local.typ); - self.emit(Instruction::sym_addr(result, local.sym, ptr_type)); - } - } else if (type_kind == TypeKind::Struct || type_kind == TypeKind::Union) && size > 64 { - // Large structs can't be loaded into registers - return address - let ptr_type = self.types.pointer_to(local.typ); - self.emit(Instruction::sym_addr(result, local.sym, ptr_type)); - } else { - self.emit(Instruction::load(result, local.sym, 0, local.typ, size)); - } - result - } - // Check if it's a parameter (already SSA value) - else if let Some(&pseudo) = self.var_map.get(&name_str) { - pseudo - } - // Global variable - create symbol reference and load - else { - // C99 6.7.4p3: A non-static inline function cannot refer to - // a file-scope static variable - if self.current_func_is_non_static_inline && self.file_scope_statics.contains(&name_str) - { - if let Some(pos) = self.current_pos { - error( - pos, - &format!( - "non-static inline function '{}' cannot reference file-scope static variable '{}'", - self.current_func_name, name_str - ), - ); - } - } - - let sym_id = self.alloc_pseudo(); - let pseudo = Pseudo::sym(sym_id, name_str.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - let typ = self.expr_type(expr); - let type_kind = self.types.kind(typ); - let size = self.types.size_bits(typ); - // Arrays decay to pointers - get address, not value - if type_kind == TypeKind::Array { - let result = self.alloc_pseudo(); - let elem_type = self.types.base_type(typ).unwrap_or(self.types.int_id); - let ptr_type = self.types.pointer_to(elem_type); - self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); - result - } - // Functions decay to function pointers, va_list decays to pointer (C99 6.3.2.1, 7.15.1), - // and large structs can't be loaded into registers - for all cases, return the address - else if type_kind == TypeKind::Function - || type_kind == TypeKind::VaList - || ((type_kind == TypeKind::Struct || type_kind == TypeKind::Union) && size > 64) - { - let result = self.alloc_pseudo(); - let ptr_type = self.types.pointer_to(typ); - self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); - result - } else { - let result = self.alloc_pseudo(); - self.emit(Instruction::load(result, sym_id, 0, typ, size)); - result - } - } - } - - /// Emit a symbol address for a string/wide-string label. - fn emit_string_sym(&mut self, expr: &Expr, label: String) -> PseudoId { - let sym_id = self.alloc_pseudo(); - let sym_pseudo = Pseudo::sym(sym_id, label); - if let Some(func) = &mut self.current_func { - func.add_pseudo(sym_pseudo); - } - let result = self.alloc_reg_pseudo(); - let typ = self.expr_type(expr); - self.emit(Instruction::sym_addr(result, sym_id, typ)); - result - } - - fn linearize_ternary( - &mut self, - expr: &Expr, - cond: &Expr, - then_expr: &Expr, - else_expr: &Expr, - ) -> PseudoId { - let result_typ = self.expr_type(expr); - let size = if self.types.kind(result_typ) == TypeKind::Function { - 64 - } else { - self.types.size_bits(result_typ) - }; - - if self.is_pure_expr(then_expr) && self.is_pure_expr(else_expr) && size <= 64 { - // Pure: use Select instruction (enables cmov/csel) - let cond_val = self.linearize_expr(cond); - let cond_typ = self.expr_type(cond); - let cond_bool = self.emit_compare_zero(cond_val, cond_typ); - let mut then_val = self.linearize_expr(then_expr); - let mut else_val = self.linearize_expr(else_expr); - - let then_typ = self.expr_type(then_expr); - let else_typ = self.expr_type(else_expr); - let then_size = self.types.size_bits(then_typ); - let else_size = self.types.size_bits(else_typ); - if then_size < size && then_size <= 32 && self.types.is_integer(then_typ) { - then_val = self.emit_convert(then_val, then_typ, result_typ); - } - if else_size < size && else_size <= 32 && self.types.is_integer(else_typ) { - else_val = self.emit_convert(else_val, else_typ, result_typ); - } - - let result = self.alloc_pseudo(); - self.emit(Instruction::select( - result, cond_bool, then_val, else_val, result_typ, size, - )); - result - } else { - // Impure: use control flow + phi for proper short-circuit evaluation - let then_bb = self.alloc_bb(); - let else_bb = self.alloc_bb(); - let merge_bb = self.alloc_bb(); - - let cond_val = self.linearize_expr(cond); - let cond_typ = self.expr_type(cond); - let cond_bool = self.emit_compare_zero(cond_val, cond_typ); - let cond_end_bb = self.current_bb.unwrap(); - - self.emit(Instruction::cbr(cond_bool, then_bb, else_bb)); - self.link_bb(cond_end_bb, then_bb); - self.link_bb(cond_end_bb, else_bb); - - self.switch_bb(then_bb); - let mut then_val = self.linearize_expr(then_expr); - let then_typ = self.expr_type(then_expr); - let then_size = self.types.size_bits(then_typ); - if then_size < size && then_size <= 32 && self.types.is_integer(then_typ) { - then_val = self.emit_convert(then_val, then_typ, result_typ); - } - let then_end_bb = self.current_bb.unwrap(); - self.emit(Instruction::br(merge_bb)); - self.link_bb(then_end_bb, merge_bb); - - self.switch_bb(else_bb); - let mut else_val = self.linearize_expr(else_expr); - let else_typ = self.expr_type(else_expr); - let else_size = self.types.size_bits(else_typ); - if else_size < size && else_size <= 32 && self.types.is_integer(else_typ) { - else_val = self.emit_convert(else_val, else_typ, result_typ); - } - let else_end_bb = self.current_bb.unwrap(); - self.emit(Instruction::br(merge_bb)); - self.link_bb(else_end_bb, merge_bb); - - self.switch_bb(merge_bb); - let result = self.alloc_pseudo(); - let phi_pseudo = Pseudo::phi(result, result.0); - if let Some(func) = &mut self.current_func { - func.add_pseudo(phi_pseudo); - } - let mut phi_insn = Instruction::phi(result, result_typ, size); - let phisrc1 = - self.emit_phi_source(then_end_bb, then_val, result, merge_bb, result_typ, size); - phi_insn.phi_list.push((then_end_bb, phisrc1)); - let phisrc2 = - self.emit_phi_source(else_end_bb, else_val, result, merge_bb, result_typ, size); - phi_insn.phi_list.push((else_end_bb, phisrc2)); - self.emit(phi_insn); - - result - } - } - - fn linearize_expr(&mut self, expr: &Expr) -> PseudoId { - // Set current position for debug info - self.current_pos = Some(expr.pos); - - match &expr.kind { - ExprKind::IntLit(val) => { - let typ = self.expr_type(expr); - self.emit_const(*val as i128, typ) - } - - ExprKind::Int128Lit(val) => { - let typ = self.expr_type(expr); - self.emit_const(*val, typ) - } - - ExprKind::FloatLit(val) => { - let typ = self.expr_type(expr); - self.emit_fconst(*val, typ) - } - - ExprKind::CharLit(c) => { - let typ = self.expr_type(expr); - self.emit_const(*c as u8 as i8 as i128, typ) - } - - ExprKind::StringLit(s) => { - let label = self.module.add_string(s.clone()); - self.emit_string_sym(expr, label) - } - - ExprKind::WideStringLit(s) => { - let label = self.module.add_wide_string(s.clone()); - self.emit_string_sym(expr, label) - } - - ExprKind::Ident(symbol_id) => self.linearize_ident(expr, *symbol_id), - - ExprKind::FuncName => self.linearize_func_name(), - - ExprKind::Unary { op, operand } => self.linearize_unary(expr, *op, operand), - - ExprKind::Binary { op, left, right } => self.linearize_binary(expr, *op, left, right), - - ExprKind::Assign { op, target, value } => self.emit_assign(*op, target, value), - - ExprKind::PostInc(operand) => self.linearize_postop(operand, true), - - ExprKind::PostDec(operand) => self.linearize_postop(operand, false), - - ExprKind::Conditional { - cond, - then_expr, - else_expr, - } => self.linearize_ternary(expr, cond, then_expr, else_expr), - - ExprKind::Call { func, args } => self.linearize_call(expr, func, args), - - ExprKind::Member { - expr: inner_expr, - member, - } => self.linearize_member(expr, inner_expr, *member), - - ExprKind::Arrow { - expr: inner_expr, - member, - } => self.linearize_arrow(expr, inner_expr, *member), - - ExprKind::Index { array, index } => self.linearize_index(expr, array, index), - - ExprKind::Cast { - cast_type, - expr: inner_expr, - } => self.linearize_cast(inner_expr, *cast_type), - - ExprKind::SizeofType(typ) => { - let size = self.types.size_bits(*typ) / 8; - // sizeof returns size_t, which is unsigned long in our implementation - let result_typ = self.types.ulong_id; - self.emit_const(size as i128, result_typ) - } - - ExprKind::SizeofExpr(inner_expr) => { - // Check if this is a VLA variable - need runtime sizeof - if let ExprKind::Ident(symbol_id) = &inner_expr.kind { - if let Some(info) = self.locals.get(symbol_id).cloned() { - if let (Some(size_sym), Some(elem_type)) = - (info.vla_size_sym, info.vla_elem_type) - { - // VLA: compute sizeof at runtime as num_elements * sizeof(element) - let result_typ = self.types.ulong_id; - let elem_size = self.types.size_bytes(elem_type) as i64; - - // Load the stored number of elements - let num_elements = self.alloc_pseudo(); - let load_insn = - Instruction::load(num_elements, size_sym, 0, result_typ, 64); - self.emit(load_insn); - - // Multiply by element size - let elem_size_const = self.emit_const(elem_size as i128, result_typ); - let result = self.alloc_pseudo(); - let mul_insn = Instruction::new(Opcode::Mul) - .with_target(result) - .with_src(num_elements) - .with_src(elem_size_const) - .with_size(64) - .with_type(result_typ); - self.emit(mul_insn); - return result; - } - } - } - - // Non-VLA: compute size at compile time - let inner_typ = self.expr_type(inner_expr); - let size = self.types.size_bits(inner_typ) / 8; - // sizeof returns size_t, which is unsigned long in our implementation - let result_typ = self.types.ulong_id; - self.emit_const(size as i128, result_typ) - } - - ExprKind::AlignofType(typ) => { - let align = self.types.alignment(*typ); - // _Alignof returns size_t - let result_typ = self.types.ulong_id; - self.emit_const(align as i128, result_typ) - } - - ExprKind::AlignofExpr(inner_expr) => { - let inner_typ = self.expr_type(inner_expr); - let align = self.types.alignment(inner_typ); - // _Alignof returns size_t - let result_typ = self.types.ulong_id; - self.emit_const(align as i128, result_typ) - } - - ExprKind::Comma(exprs) => { - let mut result = self.emit_const(0, self.types.int_id); - for e in exprs { - result = self.linearize_expr(e); - } - result - } - - ExprKind::InitList { .. } => { - // InitList is handled specially in linearize_local_decl and linearize_global_decl - // It shouldn't be reached here during normal expression evaluation - panic!("InitList should be handled in declaration context, not as standalone expression") - } - - ExprKind::CompoundLiteral { typ, elements } => { - // Compound literals have automatic storage at block scope - // Create an anonymous local variable, similar to how local variables work - - // Create a symbol pseudo for the compound literal (its address) - let sym_id = self.alloc_pseudo(); - let unique_name = format!(".compound_literal.{}", sym_id.0); - let sym = Pseudo::sym(sym_id, unique_name.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(sym); - // Register as local for proper stack allocation - func.add_local( - &unique_name, - sym_id, - *typ, - false, - false, - self.current_bb, - None, - ); - } - - // For compound literals with partial initialization, C99 6.7.8p21 requires - // zero-initialization of all subobjects not explicitly initialized. - // Zero the entire compound literal first, then initialize specific members. - let type_kind = self.types.kind(*typ); - if type_kind == TypeKind::Struct - || type_kind == TypeKind::Union - || type_kind == TypeKind::Array - { - self.emit_aggregate_zero(sym_id, *typ); - } - - // Initialize using existing init list machinery - self.linearize_init_list(sym_id, *typ, elements); - - // For arrays: return pointer (array-to-pointer decay) - // For structs/scalars: load and return the value - let result = self.alloc_reg_pseudo(); - - let type_kind = self.types.kind(*typ); - let size = self.types.size_bits(*typ); - if type_kind == TypeKind::Array { - // Array compound literal - decay to pointer to first element - let elem_type = self.types.base_type(*typ).unwrap_or(self.types.int_id); - let ptr_type = self.types.pointer_to(elem_type); - self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); - } else if (type_kind == TypeKind::Struct || type_kind == TypeKind::Union) - && size > 64 - { - // Large struct/union compound literal - return address - // Large structs can't be "loaded" into registers; assignment handles copying - let ptr_type = self.types.pointer_to(*typ); - self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); - } else { - // Scalar or small struct compound literal - load the value - self.emit(Instruction::load(result, sym_id, 0, *typ, size)); - } - result - } - - // ================================================================ - // Variadic function support (va_* builtins) - // ================================================================ - ExprKind::VaStart { ap, last_param } => { - // va_start(ap, last_param) - // Get address of ap (it's an lvalue) - let ap_addr = self.linearize_lvalue(ap); - let result = self.alloc_pseudo(); - - // Create instruction with last_param stored in func_name field - let insn = Instruction::new(Opcode::VaStart) - .with_target(result) - .with_src(ap_addr) - .with_func(self.str(*last_param).to_string()) - .with_type(self.types.void_id) - .with_size(0); - self.emit(insn); - result - } - - ExprKind::VaArg { ap, arg_type } => { - // va_arg(ap, type) - // Get address of ap (it's an lvalue) - let ap_addr = self.linearize_lvalue(ap); - let result = self.alloc_pseudo(); - - let arg_size = self.types.size_bits(*arg_type); - let insn = Instruction::new(Opcode::VaArg) - .with_target(result) - .with_src(ap_addr) - .with_type(*arg_type) - .with_size(arg_size); - self.emit(insn); - result - } - - ExprKind::VaEnd { ap } => { - // va_end(ap) - usually a no-op - let ap_addr = self.linearize_lvalue(ap); - let result = self.alloc_pseudo(); - - let insn = Instruction::new(Opcode::VaEnd) - .with_target(result) - .with_src(ap_addr) - .with_type(self.types.void_id) - .with_size(0); - self.emit(insn); - result - } - - ExprKind::VaCopy { dest, src } => { - // va_copy(dest, src) - let dest_addr = self.linearize_lvalue(dest); - let src_addr = self.linearize_lvalue(src); - let result = self.alloc_pseudo(); - - let insn = Instruction::new(Opcode::VaCopy) - .with_target(result) - .with_src(dest_addr) - .with_src(src_addr) - .with_type(self.types.void_id) - .with_size(0); - self.emit(insn); - result - } - - // ================================================================ - // Byte-swapping builtins - // ================================================================ - ExprKind::Bswap16 { arg } => { - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); - - let insn = Instruction::new(Opcode::Bswap16) - .with_target(result) - .with_src(arg_val) - .with_size(16) - .with_type(self.types.ushort_id); - self.emit(insn); - result - } - - ExprKind::Bswap32 { arg } => { - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); - - let insn = Instruction::new(Opcode::Bswap32) - .with_target(result) - .with_src(arg_val) - .with_size(32) - .with_type(self.types.uint_id); - self.emit(insn); - result - } - - ExprKind::Bswap64 { arg } => { - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); - - let insn = Instruction::new(Opcode::Bswap64) - .with_target(result) - .with_src(arg_val) - .with_size(64) - .with_type(self.types.ulonglong_id); - self.emit(insn); - result - } - - // ================================================================ - // Count trailing zeros builtins - // ================================================================ - ExprKind::Ctz { arg } => { - // __builtin_ctz - counts trailing zeros in unsigned int (32-bit) - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); - - let insn = Instruction::new(Opcode::Ctz32) - .with_target(result) - .with_src(arg_val) - .with_size(32) - .with_type(self.types.int_id); - self.emit(insn); - result - } - - ExprKind::Ctzl { arg } | ExprKind::Ctzll { arg } => { - // __builtin_ctzl/ctzll - counts trailing zeros in 64-bit value - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); - - let insn = Instruction::new(Opcode::Ctz64) - .with_target(result) - .with_src(arg_val) - .with_size(64) - .with_type(self.types.int_id); - self.emit(insn); - result + } } - - // ================================================================ - // Count leading zeros builtins - // ================================================================ - ExprKind::Clz { arg } => { - // __builtin_clz - counts leading zeros in unsigned int (32-bit) - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); - - let insn = Instruction::new(Opcode::Clz32) - .with_target(result) - .with_src(arg_val) - .with_size(32) - .with_type(self.types.int_id); - self.emit(insn); - result + ExprKind::Index { array, index } => { + // Array subscript: compute address and store + let array_type = self.expr_type(array); + let index_type = self.expr_type(index); + let array_kind = self.types.kind(array_type); + let (ptr_expr, idx_expr, idx_type) = + if array_kind == TypeKind::Pointer || array_kind == TypeKind::Array { + (array.as_ref(), index.as_ref(), index_type) + } else { + (index.as_ref(), array.as_ref(), array_type) + }; + let arr = self.linearize_expr(ptr_expr); + let idx = self.linearize_expr(idx_expr); + let elem_size = store_size / 8; + let elem_size_val = self.emit_const(elem_size as i128, self.types.long_id); + let idx_extended = self.emit_convert(idx, idx_type, self.types.long_id); + let offset = self.alloc_pseudo(); + let ptr_typ = self.types.long_id; + self.emit(Instruction::binop( + Opcode::Mul, + offset, + idx_extended, + elem_size_val, + ptr_typ, + 64, + )); + let addr = self.alloc_reg_pseudo(); + self.emit(Instruction::binop( + Opcode::Add, + addr, + arr, + offset, + ptr_typ, + 64, + )); + self.emit(Instruction::store(final_result, addr, 0, typ, store_size)); } + _ => {} + } - ExprKind::Clzl { arg } | ExprKind::Clzll { arg } => { - // __builtin_clzl/clzll - counts leading zeros in 64-bit value - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); - - let insn = Instruction::new(Opcode::Clz64) - .with_target(result) - .with_src(arg_val) - .with_size(64) - .with_type(self.types.int_id); - self.emit(insn); - result - } + old_val // Return old value + } - // ================================================================ - // Population count builtins - // ================================================================ - ExprKind::Popcount { arg } => { - // __builtin_popcount - counts set bits in unsigned int (32-bit) - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); + /// Linearize a binary expression (arithmetic, comparison, logical operators) + pub(crate) fn linearize_binary( + &mut self, + expr: &Expr, + op: BinaryOp, + left: &Expr, + right: &Expr, + ) -> PseudoId { + // Handle short-circuit operators before linearizing both operands + // C99 requires that && and || only evaluate the RHS if needed + if op == BinaryOp::LogAnd { + return self.emit_logical_and(left, right); + } + if op == BinaryOp::LogOr { + return self.emit_logical_or(left, right); + } - let insn = Instruction::new(Opcode::Popcount32) - .with_target(result) - .with_src(arg_val) - .with_size(32) - .with_type(self.types.int_id); - self.emit(insn); - result - } + let left_typ = self.expr_type(left); + let right_typ = self.expr_type(right); + let result_typ = self.expr_type(expr); - ExprKind::Popcountl { arg } | ExprKind::Popcountll { arg } => { - // __builtin_popcountl/popcountll - counts set bits in 64-bit value - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); + // Check for pointer arithmetic: ptr +/- int or int + ptr + let left_kind = self.types.kind(left_typ); + let right_kind = self.types.kind(right_typ); + let left_is_ptr_or_arr = left_kind == TypeKind::Pointer || left_kind == TypeKind::Array; + let right_is_ptr_or_arr = right_kind == TypeKind::Pointer || right_kind == TypeKind::Array; + let is_ptr_arith = (op == BinaryOp::Add || op == BinaryOp::Sub) + && ((left_is_ptr_or_arr && self.types.is_integer(right_typ)) + || (self.types.is_integer(left_typ) && right_is_ptr_or_arr)); - let insn = Instruction::new(Opcode::Popcount64) - .with_target(result) - .with_src(arg_val) - .with_size(64) - .with_type(self.types.int_id); - self.emit(insn); - result - } + // Check for pointer difference: ptr - ptr + let is_ptr_diff = op == BinaryOp::Sub && left_is_ptr_or_arr && right_is_ptr_or_arr; - ExprKind::Alloca { size } => { - let size_val = self.linearize_expr(size); - let result = self.alloc_pseudo(); + if is_ptr_diff { + // Pointer difference: (ptr1 - ptr2) / element_size + let left_val = self.linearize_expr(left); + let right_val = self.linearize_expr(right); - let insn = Instruction::new(Opcode::Alloca) - .with_target(result) - .with_src(size_val) - .with_type_and_size(self.types.void_ptr_id, 64); - self.emit(insn); - result - } + // Compute byte difference + let byte_diff = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Sub, + byte_diff, + left_val, + right_val, + self.types.long_id, + 64, + )); - ExprKind::Memset { dest, c, n } => { - let dest_val = self.linearize_expr(dest); - let c_val = self.linearize_expr(c); - let n_val = self.linearize_expr(n); - let result = self.alloc_pseudo(); + // Get element size from the pointer type + let elem_type = self.types.base_type(left_typ).unwrap_or(self.types.char_id); + let elem_size = self.types.size_bits(elem_type) / 8; - let insn = Instruction::new(Opcode::Memset) - .with_target(result) - .with_src3(dest_val, c_val, n_val) - .with_type_and_size(self.types.void_ptr_id, 64); - self.emit(insn); - result - } + // Divide by element size + let scale = self.emit_const(elem_size as i128, self.types.long_id); + let result = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::DivS, + result, + byte_diff, + scale, + self.types.long_id, + 64, + )); + result + } else if is_ptr_arith { + // Pointer arithmetic: scale integer operand by element size + let (ptr_val, ptr_typ, int_val) = if left_is_ptr_or_arr { + let ptr = self.linearize_expr(left); + let int = self.linearize_expr(right); + (ptr, left_typ, int) + } else { + // int + ptr case + let int = self.linearize_expr(left); + let ptr = self.linearize_expr(right); + (ptr, right_typ, int) + }; - ExprKind::Memcpy { dest, src, n } => { - let dest_val = self.linearize_expr(dest); - let src_val = self.linearize_expr(src); - let n_val = self.linearize_expr(n); - let result = self.alloc_pseudo(); + // Get element size + let elem_type = self.types.base_type(ptr_typ).unwrap_or(self.types.char_id); + let elem_size = self.types.size_bits(elem_type) / 8; - let insn = Instruction::new(Opcode::Memcpy) - .with_target(result) - .with_src3(dest_val, src_val, n_val) - .with_type_and_size(self.types.void_ptr_id, 64); - self.emit(insn); - result - } + // Scale the integer by element size + let scale = self.emit_const(elem_size as i128, self.types.long_id); + let scaled_offset = self.alloc_pseudo(); + // Extend int_val to 64-bit for proper address arithmetic + let actual_int_type = if left_is_ptr_or_arr { + right_typ + } else { + left_typ + }; + let int_val_extended = self.emit_convert(int_val, actual_int_type, self.types.long_id); + self.emit(Instruction::binop( + Opcode::Mul, + scaled_offset, + int_val_extended, + scale, + self.types.long_id, + 64, + )); - ExprKind::Memmove { dest, src, n } => { - let dest_val = self.linearize_expr(dest); - let src_val = self.linearize_expr(src); - let n_val = self.linearize_expr(n); - let result = self.alloc_pseudo(); + // Add (or subtract) to pointer + let result = self.alloc_pseudo(); + let opcode = if op == BinaryOp::Sub { + Opcode::Sub + } else { + Opcode::Add + }; + self.emit(Instruction::binop( + opcode, + result, + ptr_val, + scaled_offset, + self.types.long_id, + 64, + )); + result + } else if self.types.is_complex(result_typ) { + // Complex arithmetic: expand to real/imaginary operations + // For complex types, we need addresses to load real/imag parts + let left_addr = self.linearize_lvalue(left); + let right_addr = self.linearize_lvalue(right); + self.emit_complex_binary(op, left_addr, right_addr, result_typ) + } else { + // For comparisons, compute common type for both operands + // (usual arithmetic conversions) + let operand_typ = if op.is_comparison() { + self.common_type(left_typ, right_typ) + } else { + result_typ + }; - let insn = Instruction::new(Opcode::Memmove) - .with_target(result) - .with_src3(dest_val, src_val, n_val) - .with_type_and_size(self.types.void_ptr_id, 64); - self.emit(insn); - result - } + // Linearize operands + let left_val = self.linearize_expr(left); + let right_val = self.linearize_expr(right); - ExprKind::Fabs { arg } => { - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); + // Emit type conversions if needed + let left_val = self.emit_convert(left_val, left_typ, operand_typ); + let right_val = self.emit_convert(right_val, right_typ, operand_typ); - let insn = Instruction::new(Opcode::Fabs64) - .with_target(result) - .with_src(arg_val) - .with_size(64) - .with_type(self.types.double_id); - self.emit(insn); - result - } + self.emit_binary(op, left_val, right_val, result_typ, operand_typ) + } + } - ExprKind::Fabsf { arg } => { - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); + /// Linearize a unary expression (prefix operators, address-of, dereference, etc.) + pub(crate) fn linearize_unary(&mut self, expr: &Expr, op: UnaryOp, operand: &Expr) -> PseudoId { + // Handle AddrOf specially - we need the lvalue address, not the value + if op == UnaryOp::AddrOf { + return self.linearize_lvalue(operand); + } - let insn = Instruction::new(Opcode::Fabs32) - .with_target(result) - .with_src(arg_val) - .with_size(32) - .with_type(self.types.float_id); - self.emit(insn); - result - } + // Handle PreInc/PreDec specially - they need store-back + if op == UnaryOp::PreInc || op == UnaryOp::PreDec { + // For deref operands like *s++, compute the lvalue address once + // to avoid re-evaluating side effects (PostInc etc.) when storing back. + let deref_addr = if let ExprKind::Unary { + op: UnaryOp::Deref, .. + } = &operand.kind + { + let addr = self.linearize_lvalue(operand); + Some(addr) + } else { + None + }; + // If we pre-computed the deref address, load from it. + // Otherwise, evaluate normally. + let val = if let Some(addr) = deref_addr { + let deref_typ = self.expr_type(operand); + let deref_size = self.types.size_bits(deref_typ); + let loaded = self.alloc_reg_pseudo(); + self.emit(Instruction::load(loaded, addr, 0, deref_typ, deref_size)); + loaded + } else { + self.linearize_expr(operand) + }; + let typ = self.expr_type(operand); + let is_float = self.types.is_float(typ); + let is_ptr = self.types.kind(typ) == TypeKind::Pointer; - ExprKind::Fabsl { arg } => { - // Long double fabs - treat as 64-bit for now - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); + // Compute new value - for pointers, scale by element size + let increment = if is_ptr { + let elem_type = self.types.base_type(typ).unwrap_or(self.types.char_id); + let elem_size = self.types.size_bits(elem_type) / 8; + self.emit_const(elem_size as i128, self.types.long_id) + } else if is_float { + self.emit_fconst(1.0, typ) + } else { + self.emit_const(1, typ) + }; + let result = self.alloc_reg_pseudo(); + let opcode = if is_float { + if op == UnaryOp::PreInc { + Opcode::FAdd + } else { + Opcode::FSub + } + } else if op == UnaryOp::PreInc { + Opcode::Add + } else { + Opcode::Sub + }; + let size = self.types.size_bits(typ); + self.emit(Instruction::binop( + opcode, result, val, increment, typ, size, + )); - let insn = Instruction::new(Opcode::Fabs64) - .with_target(result) - .with_src(arg_val) - .with_size(128) // long double is 128-bit on our targets - .with_type(self.types.longdouble_id); - self.emit(insn); + // For _Bool, normalize the result (any non-zero -> 1) + let final_result = if self.types.kind(typ) == TypeKind::Bool { + self.emit_convert(result, self.types.int_id, typ) + } else { result + }; + + // Store back to the lvalue + let store_size = self.types.size_bits(typ); + match &operand.kind { + ExprKind::Ident(symbol_id) => { + let name_str = self.symbol_name(*symbol_id); + if let Some(local) = self.locals.get(symbol_id).cloned() { + // Check if this is a static local (sentinel value) + if local.sym.0 == u32::MAX { + self.emit_static_local_store(&name_str, final_result, typ, store_size); + } else { + // Regular local variable + self.emit(Instruction::store( + final_result, + local.sym, + 0, + typ, + store_size, + )); + } + } else if self.var_map.contains_key(&name_str) { + self.var_map.insert(name_str.clone(), final_result); + } else { + // Global variable - emit store + let sym_id = self.alloc_pseudo(); + let pseudo = Pseudo::sym(sym_id, name_str.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); + } + self.emit(Instruction::store(final_result, sym_id, 0, typ, store_size)); + } + } + ExprKind::Member { expr, member } => { + // Struct member: get address and store with offset + let base = self.linearize_lvalue(expr); + let base_struct_type = self.expr_type(expr); + let struct_type = self.resolve_struct_type(base_struct_type); + if let Some(member_info) = self.types.find_member(struct_type, *member) { + self.emit(Instruction::store( + final_result, + base, + member_info.offset as i64, + typ, + store_size, + )); + } + } + ExprKind::Arrow { expr, member } => { + // Pointer member: pointer value is the base address + let ptr = self.linearize_expr(expr); + let ptr_type = self.expr_type(expr); + let base_struct_type = self.types.base_type(ptr_type).unwrap_or(typ); + let struct_type = self.resolve_struct_type(base_struct_type); + if let Some(member_info) = self.types.find_member(struct_type, *member) { + self.emit(Instruction::store( + final_result, + ptr, + member_info.offset as i64, + typ, + store_size, + )); + } + } + ExprKind::Unary { + op: UnaryOp::Deref, .. + } => { + // Dereference: store to the pointer address. + // Use the pre-computed address to avoid re-evaluating + // side effects (e.g., s++ in ++*s++). + let ptr = deref_addr.expect("deref_addr should be set for Deref operand"); + self.emit(Instruction::store(final_result, ptr, 0, typ, store_size)); + } + ExprKind::Index { array, index } => { + // Array subscript: compute address and store + let array_type = self.expr_type(array); + let index_type = self.expr_type(index); + let array_kind = self.types.kind(array_type); + let (ptr_expr, idx_expr, idx_type) = + if array_kind == TypeKind::Pointer || array_kind == TypeKind::Array { + (array.as_ref(), index.as_ref(), index_type) + } else { + (index.as_ref(), array.as_ref(), array_type) + }; + let arr = self.linearize_expr(ptr_expr); + let idx = self.linearize_expr(idx_expr); + let elem_size = store_size / 8; + let elem_size_val = self.emit_const(elem_size as i128, self.types.long_id); + let idx_extended = self.emit_convert(idx, idx_type, self.types.long_id); + let offset = self.alloc_pseudo(); + let ptr_typ = self.types.long_id; + self.emit(Instruction::binop( + Opcode::Mul, + offset, + idx_extended, + elem_size_val, + ptr_typ, + 64, + )); + let addr = self.alloc_reg_pseudo(); + self.emit(Instruction::binop( + Opcode::Add, + addr, + arr, + offset, + ptr_typ, + 64, + )); + self.emit(Instruction::store(final_result, addr, 0, typ, store_size)); + } + _ => { + // Fallback: shouldn't happen for valid lvalues + } } - ExprKind::Signbit { arg } => { - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); + return final_result; + } - let insn = Instruction::new(Opcode::Signbit64) - .with_target(result) - .with_src(arg_val) - .with_size(64) - .with_type(self.types.int_id); - self.emit(insn); - result - } + let src = self.linearize_expr(operand); + let result_typ = self.expr_type(expr); + let operand_typ = self.expr_type(operand); + // For logical NOT, use operand type for comparison size + let typ = if op == UnaryOp::Not { + operand_typ + } else { + result_typ + }; + self.emit_unary(op, src, typ) + } - ExprKind::Signbitf { arg } => { - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); + /// Linearize an identifier expression (variable reference) + /// Handle __func__, __FUNCTION__, __PRETTY_FUNCTION__ builtins + pub(crate) fn linearize_func_name(&mut self) -> PseudoId { + // C99 6.4.2.2: __func__ behaves as if declared: + // static const char __func__[] = "function-name"; + // GCC extensions: __FUNCTION__ and __PRETTY_FUNCTION__ behave the same way + + // Add function name as a string literal to the module + let label = self.module.add_string(self.current_func_name.clone()); - let insn = Instruction::new(Opcode::Signbit32) - .with_target(result) - .with_src(arg_val) - .with_size(32) - .with_type(self.types.int_id); - self.emit(insn); - result - } + // Create symbol pseudo for the string label + let sym_id = self.alloc_pseudo(); + let sym_pseudo = Pseudo::sym(sym_id, label); + if let Some(func) = &mut self.current_func { + func.add_pseudo(sym_pseudo); + } - ExprKind::Signbitl { arg } => { - // Long double signbit - use 64-bit version - let arg_val = self.linearize_expr(arg); - let result = self.alloc_pseudo(); + // Create result pseudo for the address + let result = self.alloc_reg_pseudo(); - let insn = Instruction::new(Opcode::Signbit64) - .with_target(result) - .with_src(arg_val) - .with_size(128) // long double is 128-bit on our targets - .with_type(self.types.int_id); - self.emit(insn); - result - } + // Type: const char* (pointer to char) + let char_type = self.types.char_id; + let ptr_type = self.types.pointer_to(char_type); + self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); + result + } - ExprKind::Unreachable => { - // __builtin_unreachable() - marks code path as never reached - // Emits an instruction that will trap if actually executed - let result = self.alloc_pseudo(); + pub(crate) fn linearize_ident(&mut self, expr: &Expr, symbol_id: SymbolId) -> PseudoId { + let sym = self.symbols.get(symbol_id); + let name_str = self.str(sym.name).to_string(); - let insn = Instruction::new(Opcode::Unreachable) - .with_target(result) - .with_type(self.types.void_id); - self.emit(insn); - result + // First check if it's an enum constant + if sym.is_enum_constant() { + if let Some(value) = sym.enum_value { + return self.emit_const(value as i128, self.types.int_id); } + } - ExprKind::FrameAddress { level } => { - // __builtin_frame_address(level) - returns frame pointer at given level - let level_val = self.linearize_expr(level); - let result = self.alloc_pseudo(); - - let insn = Instruction::new(Opcode::FrameAddress) - .with_target(result) - .with_src(level_val) - .with_type_and_size(self.types.void_ptr_id, 64); - self.emit(insn); - result + // Check if it's a local variable + if let Some(local) = self.locals.get(&symbol_id).cloned() { + // Check if this is a static local (sentinel value) + if local.sym.0 == u32::MAX { + // Static local - look up the global name and treat as global + let key = format!("{}.{}", self.current_func_name, &name_str); + if let Some(static_info) = self.static_locals.get(&key).cloned() { + let sym_id = self.alloc_pseudo(); + let pseudo = Pseudo::sym(sym_id, static_info.global_name); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); + } + let typ = static_info.typ; + let type_kind = self.types.kind(typ); + let size = self.types.size_bits(typ); + // Arrays decay to pointers - get address, not value + if type_kind == TypeKind::Array { + let result = self.alloc_pseudo(); + let elem_type = self.types.base_type(typ).unwrap_or(self.types.int_id); + let ptr_type = self.types.pointer_to(elem_type); + self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); + return result; + } else if type_kind == TypeKind::VaList { + // va_list is defined as __va_list_tag[1] (an array type), so it decays to + // a pointer when used in expressions (C99 6.3.2.1, 7.15.1) + let result = self.alloc_pseudo(); + let ptr_type = self.types.pointer_to(typ); + self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); + return result; + } else if (type_kind == TypeKind::Struct || type_kind == TypeKind::Union) + && size > 64 + { + // Large structs can't be loaded into registers - return address + let result = self.alloc_pseudo(); + let ptr_type = self.types.pointer_to(typ); + self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); + return result; + } else { + let result = self.alloc_pseudo(); + self.emit(Instruction::load(result, sym_id, 0, typ, size)); + return result; + } + } else { + unreachable!("static local sentinel without static_locals entry"); + } + } + let result = self.alloc_reg_pseudo(); + let type_kind = self.types.kind(local.typ); + let size = self.types.size_bits(local.typ); + // Arrays decay to pointers - get address, not value + if type_kind == TypeKind::Array { + let elem_type = self.types.base_type(local.typ).unwrap_or(self.types.int_id); + let ptr_type = self.types.pointer_to(elem_type); + self.emit(Instruction::sym_addr(result, local.sym, ptr_type)); + } else if type_kind == TypeKind::VaList { + // va_list is defined as __va_list_tag[1] (an array type), so it decays to + // a pointer when used in expressions (C99 6.3.2.1, 7.15.1) + if local.is_indirect { + // va_list parameter: local holds a pointer to the va_list struct + // Load the pointer value (array decay already happened at call site) + let ptr_type = self.types.pointer_to(local.typ); + let ptr_size = self.types.size_bits(ptr_type); + self.emit(Instruction::load(result, local.sym, 0, ptr_type, ptr_size)); + } else { + // Regular va_list local: take address (normal array decay) + let ptr_type = self.types.pointer_to(local.typ); + self.emit(Instruction::sym_addr(result, local.sym, ptr_type)); + } + } else if (type_kind == TypeKind::Struct || type_kind == TypeKind::Union) && size > 64 { + // Large structs can't be loaded into registers - return address + let ptr_type = self.types.pointer_to(local.typ); + self.emit(Instruction::sym_addr(result, local.sym, ptr_type)); + } else { + self.emit(Instruction::load(result, local.sym, 0, local.typ, size)); + } + result + } + // Check if it's a parameter (already SSA value) + else if let Some(&pseudo) = self.var_map.get(&name_str) { + pseudo + } + // Global variable - create symbol reference and load + else { + // C99 6.7.4p3: A non-static inline function cannot refer to + // a file-scope static variable + if self.current_func_is_non_static_inline && self.file_scope_statics.contains(&name_str) + { + if let Some(pos) = self.current_pos { + error( + pos, + &format!( + "non-static inline function '{}' cannot reference file-scope static variable '{}'", + self.current_func_name, name_str + ), + ); + } } - ExprKind::ReturnAddress { level } => { - // __builtin_return_address(level) - returns return address at given level - let level_val = self.linearize_expr(level); + let sym_id = self.alloc_pseudo(); + let pseudo = Pseudo::sym(sym_id, name_str.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); + } + let typ = self.expr_type(expr); + let type_kind = self.types.kind(typ); + let size = self.types.size_bits(typ); + // Arrays decay to pointers - get address, not value + if type_kind == TypeKind::Array { let result = self.alloc_pseudo(); - - let insn = Instruction::new(Opcode::ReturnAddress) - .with_target(result) - .with_src(level_val) - .with_type_and_size(self.types.void_ptr_id, 64); - self.emit(insn); + let elem_type = self.types.base_type(typ).unwrap_or(self.types.int_id); + let ptr_type = self.types.pointer_to(elem_type); + self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); result } - - ExprKind::Setjmp { env } => { - // setjmp(env) - saves execution context, returns int - let env_val = self.linearize_expr(env); + // Functions decay to function pointers, va_list decays to pointer (C99 6.3.2.1, 7.15.1), + // and large structs can't be loaded into registers - for all cases, return the address + else if type_kind == TypeKind::Function + || type_kind == TypeKind::VaList + || ((type_kind == TypeKind::Struct || type_kind == TypeKind::Union) && size > 64) + { let result = self.alloc_pseudo(); - - let insn = Instruction::new(Opcode::Setjmp) - .with_target(result) - .with_src(env_val) - .with_type_and_size(self.types.int_id, 32); - self.emit(insn); + let ptr_type = self.types.pointer_to(typ); + self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); result - } - - ExprKind::Longjmp { env, val } => { - // longjmp(env, val) - restores execution context (never returns) - let env_val = self.linearize_expr(env); - let val_val = self.linearize_expr(val); + } else { let result = self.alloc_pseudo(); - - let mut insn = Instruction::new(Opcode::Longjmp); - insn.target = Some(result); - insn.src = vec![env_val, val_val]; - insn.typ = Some(self.types.void_id); - self.emit(insn); + self.emit(Instruction::load(result, sym_id, 0, typ, size)); result } + } + } - ExprKind::OffsetOf { type_id, path } => { - // __builtin_offsetof(type, member-designator) - // Compute the byte offset of the member within the struct - let mut offset: u64 = 0; - let mut current_type = *type_id; + /// Emit a symbol address for a string/wide-string label. + pub(crate) fn emit_string_sym(&mut self, expr: &Expr, label: String) -> PseudoId { + let sym_id = self.alloc_pseudo(); + let sym_pseudo = Pseudo::sym(sym_id, label); + if let Some(func) = &mut self.current_func { + func.add_pseudo(sym_pseudo); + } + let result = self.alloc_reg_pseudo(); + let typ = self.expr_type(expr); + self.emit(Instruction::sym_addr(result, sym_id, typ)); + result + } - for element in path { - match element { - OffsetOfPath::Field(field_id) => { - // Look up the field in the current struct type - let struct_type = self.resolve_struct_type(current_type); - let member_info = self - .types - .find_member(struct_type, *field_id) - .expect("offsetof: field not found in struct type"); - offset += member_info.offset as u64; - current_type = member_info.typ; - } - OffsetOfPath::Index(index) => { - // Array indexing: offset += index * sizeof(element) - let elem_type = self - .types - .base_type(current_type) - .expect("offsetof: array index on non-array type"); - let elem_size = self.types.size_bytes(elem_type); - offset += (*index as u64) * (elem_size as u64); - current_type = elem_type; - } - } - } + pub(crate) fn linearize_ternary( + &mut self, + expr: &Expr, + cond: &Expr, + then_expr: &Expr, + else_expr: &Expr, + ) -> PseudoId { + let result_typ = self.expr_type(expr); + let size = if self.types.kind(result_typ) == TypeKind::Function { + 64 + } else { + self.types.size_bits(result_typ) + }; - // Return the offset as a constant - self.emit_const(offset as i128, self.types.ulong_id) + if self.is_pure_expr(then_expr) && self.is_pure_expr(else_expr) && size <= 64 { + // Pure: use Select instruction (enables cmov/csel) + let cond_val = self.linearize_expr(cond); + let cond_typ = self.expr_type(cond); + let cond_bool = self.emit_compare_zero(cond_val, cond_typ); + let mut then_val = self.linearize_expr(then_expr); + let mut else_val = self.linearize_expr(else_expr); + + let then_typ = self.expr_type(then_expr); + let else_typ = self.expr_type(else_expr); + let then_size = self.types.size_bits(then_typ); + let else_size = self.types.size_bits(else_typ); + if then_size < size && then_size <= 32 && self.types.is_integer(then_typ) { + then_val = self.emit_convert(then_val, then_typ, result_typ); + } + if else_size < size && else_size <= 32 && self.types.is_integer(else_typ) { + else_val = self.emit_convert(else_val, else_typ, result_typ); } - // ================================================================ - // Atomic builtins (Clang __c11_atomic_* for C11 stdatomic.h) - // ================================================================ - ExprKind::C11AtomicInit { ptr, val } => { - // atomic_init is a non-atomic store (no memory ordering) - let ptr_val = self.linearize_expr(ptr); - let value = self.linearize_expr(val); - let ptr_type = self.expr_type(ptr); - let elem_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - let size = self.types.size_bits(elem_type); - let result = self.alloc_pseudo(); + let result = self.alloc_pseudo(); + self.emit(Instruction::select( + result, cond_bool, then_val, else_val, result_typ, size, + )); + result + } else { + // Impure: use control flow + phi for proper short-circuit evaluation + let then_bb = self.alloc_bb(); + let else_bb = self.alloc_bb(); + let merge_bb = self.alloc_bb(); - // Use AtomicStore with Relaxed ordering for init - let insn = Instruction::new(Opcode::AtomicStore) - .with_target(result) - .with_src(ptr_val) - .with_src(value) - .with_src(self.emit_const(0, self.types.int_id)) // Relaxed = 0 - .with_type_and_size(elem_type, size) - .with_memory_order(MemoryOrder::Relaxed); - self.emit(insn); - result - } + let cond_val = self.linearize_expr(cond); + let cond_typ = self.expr_type(cond); + let cond_bool = self.emit_compare_zero(cond_val, cond_typ); + let cond_end_bb = self.current_bb.unwrap(); - ExprKind::C11AtomicLoad { ptr, order } => { - let ptr_val = self.linearize_expr(ptr); - let order_val = self.linearize_expr(order); - let memory_order = self.eval_memory_order(order); - let ptr_type = self.expr_type(ptr); - let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - let size = self.types.size_bits(result_type); - let result = self.alloc_pseudo(); + self.emit(Instruction::cbr(cond_bool, then_bb, else_bb)); + self.link_bb(cond_end_bb, then_bb); + self.link_bb(cond_end_bb, else_bb); - let insn = Instruction::new(Opcode::AtomicLoad) - .with_target(result) - .with_src(ptr_val) - .with_src(order_val) - .with_type_and_size(result_type, size) - .with_memory_order(memory_order); - self.emit(insn); - result + self.switch_bb(then_bb); + let mut then_val = self.linearize_expr(then_expr); + let then_typ = self.expr_type(then_expr); + let then_size = self.types.size_bits(then_typ); + if then_size < size && then_size <= 32 && self.types.is_integer(then_typ) { + then_val = self.emit_convert(then_val, then_typ, result_typ); } + let then_end_bb = self.current_bb.unwrap(); + self.emit(Instruction::br(merge_bb)); + self.link_bb(then_end_bb, merge_bb); - ExprKind::C11AtomicStore { ptr, val, order } => { - let ptr_val = self.linearize_expr(ptr); - let value = self.linearize_expr(val); - let order_val = self.linearize_expr(order); - let memory_order = self.eval_memory_order(order); - let ptr_type = self.expr_type(ptr); - let elem_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - let size = self.types.size_bits(elem_type); - let result = self.alloc_pseudo(); + self.switch_bb(else_bb); + let mut else_val = self.linearize_expr(else_expr); + let else_typ = self.expr_type(else_expr); + let else_size = self.types.size_bits(else_typ); + if else_size < size && else_size <= 32 && self.types.is_integer(else_typ) { + else_val = self.emit_convert(else_val, else_typ, result_typ); + } + let else_end_bb = self.current_bb.unwrap(); + self.emit(Instruction::br(merge_bb)); + self.link_bb(else_end_bb, merge_bb); - let insn = Instruction::new(Opcode::AtomicStore) - .with_target(result) - .with_src(ptr_val) - .with_src(value) - .with_src(order_val) - .with_type_and_size(elem_type, size) - .with_memory_order(memory_order); - self.emit(insn); - result + self.switch_bb(merge_bb); + let result = self.alloc_pseudo(); + let phi_pseudo = Pseudo::phi(result, result.0); + if let Some(func) = &mut self.current_func { + func.add_pseudo(phi_pseudo); } + let mut phi_insn = Instruction::phi(result, result_typ, size); + let phisrc1 = + self.emit_phi_source(then_end_bb, then_val, result, merge_bb, result_typ, size); + phi_insn.phi_list.push((then_end_bb, phisrc1)); + let phisrc2 = + self.emit_phi_source(else_end_bb, else_val, result, merge_bb, result_typ, size); + phi_insn.phi_list.push((else_end_bb, phisrc2)); + self.emit(phi_insn); - ExprKind::C11AtomicExchange { ptr, val, order } => { - let ptr_val = self.linearize_expr(ptr); - let value = self.linearize_expr(val); - let order_val = self.linearize_expr(order); - let memory_order = self.eval_memory_order(order); - let ptr_type = self.expr_type(ptr); - let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - let size = self.types.size_bits(result_type); - let result = self.alloc_pseudo(); + result + } + } - let insn = Instruction::new(Opcode::AtomicSwap) - .with_target(result) - .with_src(ptr_val) - .with_src(value) - .with_src(order_val) - .with_type_and_size(result_type, size) - .with_memory_order(memory_order); - self.emit(insn); - result - } + pub(crate) fn linearize_compound_literal(&mut self, expr: &Expr) -> PseudoId { + match &expr.kind { + ExprKind::CompoundLiteral { typ, elements } => { + // Compound literals have automatic storage at block scope + // Create an anonymous local variable, similar to how local variables work - ExprKind::C11AtomicCompareExchangeStrong { - ptr, - expected, - desired, - succ_order, - } - | ExprKind::C11AtomicCompareExchangeWeak { - ptr, - expected, - desired, - succ_order, - } => { - // Both strong and weak are implemented the same (as strong) - let ptr_val = self.linearize_expr(ptr); - let expected_ptr = self.linearize_expr(expected); - let desired_val = self.linearize_expr(desired); - let order_val = self.linearize_expr(succ_order); - let memory_order = self.eval_memory_order(succ_order); - let ptr_type = self.expr_type(ptr); - let elem_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - let elem_size = self.types.size_bits(elem_type); - let result = self.alloc_pseudo(); + // Create a symbol pseudo for the compound literal (its address) + let sym_id = self.alloc_pseudo(); + let unique_name = format!(".compound_literal.{}", sym_id.0); + let sym = Pseudo::sym(sym_id, unique_name.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(sym); + // Register as local for proper stack allocation + func.add_local( + &unique_name, + sym_id, + *typ, + false, + false, + self.current_bb, + None, + ); + } - // For CAS, typ is bool (result), but size is the element size for codegen - let insn = Instruction::new(Opcode::AtomicCas) - .with_target(result) - .with_src(ptr_val) - .with_src(expected_ptr) - .with_src(desired_val) - .with_src(order_val) - .with_type(self.types.bool_id) - .with_size(elem_size) - .with_memory_order(memory_order); - self.emit(insn); + // For compound literals with partial initialization, C99 6.7.8p21 requires + // zero-initialization of all subobjects not explicitly initialized. + // Zero the entire compound literal first, then initialize specific members. + let type_kind = self.types.kind(*typ); + if type_kind == TypeKind::Struct + || type_kind == TypeKind::Union + || type_kind == TypeKind::Array + { + self.emit_aggregate_zero(sym_id, *typ); + } + + // Initialize using existing init list machinery + self.linearize_init_list(sym_id, *typ, elements); + + // For arrays: return pointer (array-to-pointer decay) + // For structs/scalars: load and return the value + let result = self.alloc_reg_pseudo(); + + let type_kind = self.types.kind(*typ); + let size = self.types.size_bits(*typ); + if type_kind == TypeKind::Array { + // Array compound literal - decay to pointer to first element + let elem_type = self.types.base_type(*typ).unwrap_or(self.types.int_id); + let ptr_type = self.types.pointer_to(elem_type); + self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); + } else if (type_kind == TypeKind::Struct || type_kind == TypeKind::Union) + && size > 64 + { + // Large struct/union compound literal - return address + // Large structs can't be "loaded" into registers; assignment handles copying + let ptr_type = self.types.pointer_to(*typ); + self.emit(Instruction::sym_addr(result, sym_id, ptr_type)); + } else { + // Scalar or small struct compound literal - load the value + self.emit(Instruction::load(result, sym_id, 0, *typ, size)); + } result } - - ExprKind::C11AtomicFetchAdd { ptr, val, order } => { - let ptr_val = self.linearize_expr(ptr); - let value = self.linearize_expr(val); - let order_val = self.linearize_expr(order); - let memory_order = self.eval_memory_order(order); - let ptr_type = self.expr_type(ptr); - let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - let size = self.types.size_bits(result_type); + _ => unreachable!(), + } + } + + pub(crate) fn linearize_va_op(&mut self, expr: &Expr) -> PseudoId { + match &expr.kind { + // ================================================================ + // Variadic function support (va_* builtins) + // ================================================================ + ExprKind::VaStart { ap, last_param } => { + // va_start(ap, last_param) + // Get address of ap (it's an lvalue) + let ap_addr = self.linearize_lvalue(ap); let result = self.alloc_pseudo(); - let insn = Instruction::new(Opcode::AtomicFetchAdd) + // Create instruction with last_param stored in func_name field + let insn = Instruction::new(Opcode::VaStart) .with_target(result) - .with_src(ptr_val) - .with_src(value) - .with_src(order_val) - .with_type_and_size(result_type, size) - .with_memory_order(memory_order); + .with_src(ap_addr) + .with_func(self.str(*last_param).to_string()) + .with_type(self.types.void_id) + .with_size(0); self.emit(insn); result } - ExprKind::C11AtomicFetchSub { ptr, val, order } => { - let ptr_val = self.linearize_expr(ptr); - let value = self.linearize_expr(val); - let order_val = self.linearize_expr(order); - let memory_order = self.eval_memory_order(order); - let ptr_type = self.expr_type(ptr); - let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - let size = self.types.size_bits(result_type); + ExprKind::VaArg { ap, arg_type } => { + // va_arg(ap, type) + // Get address of ap (it's an lvalue) + let ap_addr = self.linearize_lvalue(ap); let result = self.alloc_pseudo(); - let insn = Instruction::new(Opcode::AtomicFetchSub) + let arg_size = self.types.size_bits(*arg_type); + let insn = Instruction::new(Opcode::VaArg) .with_target(result) - .with_src(ptr_val) - .with_src(value) - .with_src(order_val) - .with_type_and_size(result_type, size) - .with_memory_order(memory_order); + .with_src(ap_addr) + .with_type(*arg_type) + .with_size(arg_size); self.emit(insn); result } - ExprKind::C11AtomicFetchAnd { ptr, val, order } => { - let ptr_val = self.linearize_expr(ptr); - let value = self.linearize_expr(val); - let order_val = self.linearize_expr(order); - let memory_order = self.eval_memory_order(order); - let ptr_type = self.expr_type(ptr); - let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - let size = self.types.size_bits(result_type); + ExprKind::VaEnd { ap } => { + // va_end(ap) - usually a no-op + let ap_addr = self.linearize_lvalue(ap); let result = self.alloc_pseudo(); - let insn = Instruction::new(Opcode::AtomicFetchAnd) + let insn = Instruction::new(Opcode::VaEnd) .with_target(result) - .with_src(ptr_val) - .with_src(value) - .with_src(order_val) - .with_type_and_size(result_type, size) - .with_memory_order(memory_order); + .with_src(ap_addr) + .with_type(self.types.void_id) + .with_size(0); self.emit(insn); result } - ExprKind::C11AtomicFetchOr { ptr, val, order } => { - let ptr_val = self.linearize_expr(ptr); - let value = self.linearize_expr(val); - let order_val = self.linearize_expr(order); - let memory_order = self.eval_memory_order(order); - let ptr_type = self.expr_type(ptr); - let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - let size = self.types.size_bits(result_type); + ExprKind::VaCopy { dest, src } => { + // va_copy(dest, src) + let dest_addr = self.linearize_lvalue(dest); + let src_addr = self.linearize_lvalue(src); let result = self.alloc_pseudo(); - let insn = Instruction::new(Opcode::AtomicFetchOr) + let insn = Instruction::new(Opcode::VaCopy) .with_target(result) - .with_src(ptr_val) - .with_src(value) - .with_src(order_val) - .with_type_and_size(result_type, size) - .with_memory_order(memory_order); + .with_src(dest_addr) + .with_src(src_addr) + .with_type(self.types.void_id) + .with_size(0); self.emit(insn); result } + _ => unreachable!(), + } + } - ExprKind::C11AtomicFetchXor { ptr, val, order } => { - let ptr_val = self.linearize_expr(ptr); - let value = self.linearize_expr(val); - let order_val = self.linearize_expr(order); - let memory_order = self.eval_memory_order(order); - let ptr_type = self.expr_type(ptr); - let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - let size = self.types.size_bits(result_type); + pub(crate) fn linearize_builtin(&mut self, expr: &Expr) -> PseudoId { + match &expr.kind { + // ================================================================ + // Byte-swapping builtins + // ================================================================ + ExprKind::Bswap16 { arg } => { + let arg_val = self.linearize_expr(arg); let result = self.alloc_pseudo(); - let insn = Instruction::new(Opcode::AtomicFetchXor) + let insn = Instruction::new(Opcode::Bswap16) .with_target(result) - .with_src(ptr_val) - .with_src(value) - .with_src(order_val) - .with_type_and_size(result_type, size) - .with_memory_order(memory_order); + .with_src(arg_val) + .with_size(16) + .with_type(self.types.ushort_id); self.emit(insn); result } - ExprKind::C11AtomicThreadFence { order } => { - let order_val = self.linearize_expr(order); - let memory_order = self.eval_memory_order(order); + ExprKind::Bswap32 { arg } => { + let arg_val = self.linearize_expr(arg); let result = self.alloc_pseudo(); - let insn = Instruction::new(Opcode::Fence) + let insn = Instruction::new(Opcode::Bswap32) .with_target(result) - .with_src(order_val) - .with_type(self.types.void_id) - .with_memory_order(memory_order); + .with_src(arg_val) + .with_size(32) + .with_type(self.types.uint_id); self.emit(insn); result } - ExprKind::C11AtomicSignalFence { order } => { - // Signal fence is a compiler barrier only (no memory fence instruction) - // For now, treat it the same as thread fence - let order_val = self.linearize_expr(order); - let memory_order = self.eval_memory_order(order); + ExprKind::Bswap64 { arg } => { + let arg_val = self.linearize_expr(arg); let result = self.alloc_pseudo(); - let insn = Instruction::new(Opcode::Fence) + let insn = Instruction::new(Opcode::Bswap64) .with_target(result) - .with_src(order_val) - .with_type(self.types.void_id) - .with_memory_order(memory_order); + .with_src(arg_val) + .with_size(64) + .with_type(self.types.ulonglong_id); self.emit(insn); result - } - - ExprKind::StmtExpr { stmts, result } => { - // GNU statement expression: ({ stmt; stmt; expr; }) - // Linearize all the statements first - for item in stmts { - match item { - BlockItem::Declaration(decl) => self.linearize_local_decl(decl), - BlockItem::Statement(s) => self.linearize_stmt(s), - } - } - // The result is the value of the final expression - self.linearize_expr(result) - } - } - } - - /// Evaluate a memory order expression to a MemoryOrder enum value. - /// If the expression is not a constant or out of range, defaults to SeqCst. - fn eval_memory_order(&self, expr: &Expr) -> MemoryOrder { - // Try to evaluate as a constant integer - if let ExprKind::IntLit(val) = &expr.kind { - match *val { - 0 => MemoryOrder::Relaxed, - 1 => MemoryOrder::Consume, - 2 => MemoryOrder::Acquire, - 3 => MemoryOrder::Release, - 4 => MemoryOrder::AcqRel, - 5 => MemoryOrder::SeqCst, - _ => MemoryOrder::SeqCst, // Invalid, use strongest ordering - } - } else { - // Non-constant order expression - use SeqCst for safety - MemoryOrder::SeqCst - } - } - - fn emit_const(&mut self, val: i128, typ: TypeId) -> PseudoId { - let id = self.alloc_pseudo(); - let pseudo = Pseudo::val(id, val); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - - // Emit setval instruction - let insn = Instruction::new(Opcode::SetVal) - .with_target(id) - .with_type_and_size(typ, self.types.size_bits(typ)); - self.emit(insn); - - id - } - - fn emit_fconst(&mut self, val: f64, typ: TypeId) -> PseudoId { - let id = self.alloc_pseudo(); - let pseudo = Pseudo::fval(id, val); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - - // Emit setval instruction - let insn = Instruction::new(Opcode::SetVal) - .with_target(id) - .with_type_and_size(typ, self.types.size_bits(typ)); - self.emit(insn); - - id - } - - /// Emit a store to a static local variable. - /// The caller must have already verified that `name_str` refers to a static local - /// (i.e., the local's sym is the sentinel value u32::MAX). - fn emit_static_local_store(&mut self, name_str: &str, value: PseudoId, typ: TypeId, size: u32) { - let key = format!("{}.{}", self.current_func_name, name_str); - if let Some(static_info) = self.static_locals.get(&key).cloned() { - let sym_id = self.alloc_pseudo(); - let pseudo = Pseudo::sym(sym_id, static_info.global_name); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - self.emit(Instruction::store(value, sym_id, 0, typ, size)); - } else { - unreachable!("static local sentinel without static_locals entry"); - } - } - - /// Emit stores to zero-initialize an aggregate (struct, union, or array) - /// This handles C99 6.7.8p19: uninitialized members must be zero-initialized - fn emit_aggregate_zero(&mut self, base_sym: PseudoId, typ: TypeId) { - let total_bytes = self.types.size_bits(typ) / 8; - let mut offset: i64 = 0; - - // Create a zero constant for 64-bit stores - let zero64 = self.emit_const(0, self.types.long_id); - - // Zero in 8-byte chunks - while offset + 8 <= total_bytes as i64 { - self.emit(Instruction::store( - zero64, - base_sym, - offset, - self.types.long_id, - 64, - )); - offset += 8; - } - - // Handle remaining bytes (if any) - if offset < total_bytes as i64 { - let remaining = total_bytes as i64 - offset; - if remaining >= 4 { - let zero32 = self.emit_const(0, self.types.int_id); - self.emit(Instruction::store( - zero32, - base_sym, - offset, - self.types.int_id, - 32, - )); - offset += 4; - } - if offset < total_bytes as i64 { - let remaining = total_bytes as i64 - offset; - if remaining >= 2 { - let zero16 = self.emit_const(0, self.types.short_id); - self.emit(Instruction::store( - zero16, - base_sym, - offset, - self.types.short_id, - 16, - )); - offset += 2; - } - if offset < total_bytes as i64 { - let zero8 = self.emit_const(0, self.types.char_id); - self.emit(Instruction::store( - zero8, - base_sym, - offset, - self.types.char_id, - 8, - )); - } - } - } - } - - /// Emit a block copy from src to dst using integer chunks. - fn emit_block_copy(&mut self, dst: PseudoId, src: PseudoId, size_bytes: i64) { - self.emit_block_copy_at_offset(dst, 0, src, size_bytes); - } - - /// Emit a block copy from src to dst using integer chunks. - /// The destination stores start at dst_base_offset. - fn emit_block_copy_at_offset( - &mut self, - dst: PseudoId, - dst_base_offset: i64, - src: PseudoId, - size_bytes: i64, - ) { - let mut offset: i64 = 0; - while offset + 8 <= size_bytes { - let tmp = self.alloc_pseudo(); - self.emit(Instruction::load(tmp, src, offset, self.types.ulong_id, 64)); - self.emit(Instruction::store( - tmp, - dst, - dst_base_offset + offset, - self.types.ulong_id, - 64, - )); - offset += 8; - } - let remaining = size_bytes - offset; - if remaining >= 4 { - let tmp = self.alloc_pseudo(); - self.emit(Instruction::load(tmp, src, offset, self.types.uint_id, 32)); - self.emit(Instruction::store( - tmp, - dst, - dst_base_offset + offset, - self.types.uint_id, - 32, - )); - offset += 4; - } - if remaining % 4 >= 2 { - let tmp = self.alloc_pseudo(); - self.emit(Instruction::load( - tmp, - src, - offset, - self.types.ushort_id, - 16, - )); - self.emit(Instruction::store( - tmp, - dst, - dst_base_offset + offset, - self.types.ushort_id, - 16, - )); - offset += 2; - } - if remaining % 2 == 1 { - let tmp = self.alloc_pseudo(); - self.emit(Instruction::load(tmp, src, offset, self.types.uchar_id, 8)); - self.emit(Instruction::store( - tmp, - dst, - dst_base_offset + offset, - self.types.uchar_id, - 8, - )); - } - } - - /// Emit code to load a bitfield value - /// Returns the loaded value as a PseudoId - fn emit_bitfield_load( - &mut self, - base: PseudoId, - byte_offset: usize, - bit_offset: u32, - bit_width: u32, - storage_size: u32, - typ: TypeId, - ) -> PseudoId { - // Determine storage type based on storage unit size - let storage_type = self.bitfield_storage_type(storage_size); - let storage_bits = storage_size * 8; - - // 1. Load the entire storage unit - let storage_val = self.alloc_pseudo(); - self.emit(Instruction::load( - storage_val, - base, - byte_offset as i64, - storage_type, - storage_bits, - )); + } - // 2. Shift right by bit_offset (using logical shift for unsigned extraction) - let shifted = if bit_offset > 0 { - let shift_amount = self.emit_const(bit_offset as i128, self.types.int_id); - let shifted = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Lsr, - shifted, - storage_val, - shift_amount, - storage_type, - storage_bits, - )); - shifted - } else { - storage_val - }; + // ================================================================ + // Count trailing zeros builtins + // ================================================================ + ExprKind::Ctz { arg } => { + // __builtin_ctz - counts trailing zeros in unsigned int (32-bit) + let arg_val = self.linearize_expr(arg); + let result = self.alloc_pseudo(); - // 3. Mask to bit_width bits - let mask = (1u64 << bit_width) - 1; - let mask_val = self.emit_const(mask as i128, storage_type); - let masked = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::And, - masked, - shifted, - mask_val, - storage_type, - storage_bits, - )); + let insn = Instruction::new(Opcode::Ctz32) + .with_target(result) + .with_src(arg_val) + .with_size(32) + .with_type(self.types.int_id); + self.emit(insn); + result + } - // 4. Sign extend if this is a signed bitfield - if !self.types.is_unsigned(typ) && bit_width < storage_bits { - self.emit_sign_extend_bitfield(masked, bit_width, storage_bits) - } else { - masked - } - } + ExprKind::Ctzl { arg } | ExprKind::Ctzll { arg } => { + // __builtin_ctzl/ctzll - counts trailing zeros in 64-bit value + let arg_val = self.linearize_expr(arg); + let result = self.alloc_pseudo(); - /// Sign-extend a bitfield value from bit_width to target_bits - fn emit_sign_extend_bitfield( - &mut self, - value: PseudoId, - bit_width: u32, - target_bits: u32, - ) -> PseudoId { - // Sign extend by shifting left then arithmetic shifting right - let shift_amount = target_bits - bit_width; - let typ = if target_bits <= 32 { - self.types.int_id - } else { - self.types.long_id - }; + let insn = Instruction::new(Opcode::Ctz64) + .with_target(result) + .with_src(arg_val) + .with_size(64) + .with_type(self.types.int_id); + self.emit(insn); + result + } - let shift_val = self.emit_const(shift_amount as i128, typ); - let shifted_left = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Shl, - shifted_left, - value, - shift_val, - typ, - target_bits, - )); + // ================================================================ + // Count leading zeros builtins + // ================================================================ + ExprKind::Clz { arg } => { + // __builtin_clz - counts leading zeros in unsigned int (32-bit) + let arg_val = self.linearize_expr(arg); + let result = self.alloc_pseudo(); - let result = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Asr, - result, - shifted_left, - shift_val, - typ, - target_bits, - )); - result - } + let insn = Instruction::new(Opcode::Clz32) + .with_target(result) + .with_src(arg_val) + .with_size(32) + .with_type(self.types.int_id); + self.emit(insn); + result + } - /// Emit code to store a value into a bitfield - fn emit_bitfield_store( - &mut self, - base: PseudoId, - byte_offset: usize, - bit_offset: u32, - bit_width: u32, - storage_size: u32, - new_value: PseudoId, - ) { - // Determine storage type based on storage unit size - let storage_type = self.bitfield_storage_type(storage_size); - let storage_bits = storage_size * 8; - - // 1. Load current storage unit value - let old_val = self.alloc_pseudo(); - self.emit(Instruction::load( - old_val, - base, - byte_offset as i64, - storage_type, - storage_bits, - )); + ExprKind::Clzl { arg } | ExprKind::Clzll { arg } => { + // __builtin_clzl/clzll - counts leading zeros in 64-bit value + let arg_val = self.linearize_expr(arg); + let result = self.alloc_pseudo(); - // 2. Create mask for the bitfield bits: ~(((1 << width) - 1) << offset) - let field_mask = ((1u64 << bit_width) - 1) << bit_offset; - let clear_mask = !field_mask; - let clear_mask_val = self.emit_const(clear_mask as i128, storage_type); + let insn = Instruction::new(Opcode::Clz64) + .with_target(result) + .with_src(arg_val) + .with_size(64) + .with_type(self.types.int_id); + self.emit(insn); + result + } - // 3. Clear the bitfield bits in old value - let cleared = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::And, - cleared, - old_val, - clear_mask_val, - storage_type, - storage_bits, - )); + // ================================================================ + // Population count builtins + // ================================================================ + ExprKind::Popcount { arg } => { + // __builtin_popcount - counts set bits in unsigned int (32-bit) + let arg_val = self.linearize_expr(arg); + let result = self.alloc_pseudo(); - // 4. Mask new value to bit_width and shift to position - let value_mask = (1u64 << bit_width) - 1; - let value_mask_val = self.emit_const(value_mask as i128, storage_type); - let masked_new = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::And, - masked_new, - new_value, - value_mask_val, - storage_type, - storage_bits, - )); + let insn = Instruction::new(Opcode::Popcount32) + .with_target(result) + .with_src(arg_val) + .with_size(32) + .with_type(self.types.int_id); + self.emit(insn); + result + } - let positioned = if bit_offset > 0 { - let shift_val = self.emit_const(bit_offset as i128, self.types.int_id); - let positioned = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Shl, - positioned, - masked_new, - shift_val, - storage_type, - storage_bits, - )); - positioned - } else { - masked_new - }; + ExprKind::Popcountl { arg } | ExprKind::Popcountll { arg } => { + // __builtin_popcountl/popcountll - counts set bits in 64-bit value + let arg_val = self.linearize_expr(arg); + let result = self.alloc_pseudo(); - // 5. OR cleared value with positioned new value - let combined = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Or, - combined, - cleared, - positioned, - storage_type, - storage_bits, - )); + let insn = Instruction::new(Opcode::Popcount64) + .with_target(result) + .with_src(arg_val) + .with_size(64) + .with_type(self.types.int_id); + self.emit(insn); + result + } - // 6. Store back - self.emit(Instruction::store( - combined, - base, - byte_offset as i64, - storage_type, - storage_bits, - )); - } + ExprKind::Alloca { size } => { + let size_val = self.linearize_expr(size); + let result = self.alloc_pseudo(); - fn emit_unary(&mut self, op: UnaryOp, src: PseudoId, typ: TypeId) -> PseudoId { - let is_float = self.types.is_float(typ); - let size = self.types.size_bits(typ); + let insn = Instruction::new(Opcode::Alloca) + .with_target(result) + .with_src(size_val) + .with_type_and_size(self.types.void_ptr_id, 64); + self.emit(insn); + result + } - let result = self.alloc_pseudo(); + ExprKind::Memset { dest, c, n } => { + let dest_val = self.linearize_expr(dest); + let c_val = self.linearize_expr(c); + let n_val = self.linearize_expr(n); + let result = self.alloc_pseudo(); - let opcode = match op { - UnaryOp::Neg => { - if is_float { - Opcode::FNeg - } else { - Opcode::Neg - } - } - UnaryOp::Not => { - // Logical not: compare with 0 - if is_float { - let zero = self.emit_fconst(0.0, typ); - self.emit(Instruction::binop( - Opcode::FCmpOEq, - result, - src, - zero, - typ, - size, - )); - } else { - let zero = self.emit_const(0, typ); - self.emit(Instruction::binop( - Opcode::SetEq, - result, - src, - zero, - typ, - size, - )); - } - return result; - } - UnaryOp::BitNot => Opcode::Not, - UnaryOp::AddrOf => { - return src; - } - UnaryOp::Deref => { - // Dereferencing a pointer-to-array gives an array, which is just an address - // (arrays decay to their first element's address) - let type_kind = self.types.kind(typ); - if type_kind == TypeKind::Array { - return src; - } - // In C, dereferencing a function pointer is a no-op: - // *func_ptr == func_ptr (C99 6.5.3.2, 6.3.2.1) - if type_kind == TypeKind::Function { - return src; - } - // For struct types, always return the address — struct member - // access requires an address for offset-based field access. - if type_kind == TypeKind::Struct { - return src; - } - // For large union types (> 64 bits), return the address. - // For small unions (<= 64 bits), LOAD the value — unions are - // accessed as whole values, not via member offsets, and - // returning the pointer causes callers to store the pointer - // instead of the union value (Bug L). - if type_kind == TypeKind::Union && size > 64 { - return src; - } - self.emit(Instruction::load(result, src, 0, typ, size)); - return result; - } - UnaryOp::PreInc => { - let is_ptr = self.types.kind(typ) == TypeKind::Pointer; - let increment = if is_ptr { - let elem_type = self.types.base_type(typ).unwrap_or(self.types.char_id); - let elem_size = self.types.size_bits(elem_type) / 8; - self.emit_const(elem_size as i128, self.types.long_id) - } else if is_float { - self.emit_fconst(1.0, typ) - } else { - self.emit_const(1, typ) - }; - let opcode = if is_float { Opcode::FAdd } else { Opcode::Add }; - self.emit(Instruction::binop( - opcode, result, src, increment, typ, size, - )); - return result; - } - UnaryOp::PreDec => { - let is_ptr = self.types.kind(typ) == TypeKind::Pointer; - let decrement = if is_ptr { - let elem_type = self.types.base_type(typ).unwrap_or(self.types.char_id); - let elem_size = self.types.size_bits(elem_type) / 8; - self.emit_const(elem_size as i128, self.types.long_id) - } else if is_float { - self.emit_fconst(1.0, typ) - } else { - self.emit_const(1, typ) - }; - let opcode = if is_float { Opcode::FSub } else { Opcode::Sub }; - self.emit(Instruction::binop( - opcode, result, src, decrement, typ, size, - )); - return result; + let insn = Instruction::new(Opcode::Memset) + .with_target(result) + .with_src3(dest_val, c_val, n_val) + .with_type_and_size(self.types.void_ptr_id, 64); + self.emit(insn); + result } - }; - self.emit(Instruction::unop(opcode, result, src, typ, size)); - result - } + ExprKind::Memcpy { dest, src, n } => { + let dest_val = self.linearize_expr(dest); + let src_val = self.linearize_expr(src); + let n_val = self.linearize_expr(n); + let result = self.alloc_pseudo(); - fn emit_binary( - &mut self, - op: BinaryOp, - left: PseudoId, - right: PseudoId, - result_typ: TypeId, - operand_typ: TypeId, - ) -> PseudoId { - let is_float = self.types.is_float(operand_typ); - let is_unsigned = self.types.is_unsigned(operand_typ); + let insn = Instruction::new(Opcode::Memcpy) + .with_target(result) + .with_src3(dest_val, src_val, n_val) + .with_type_and_size(self.types.void_ptr_id, 64); + self.emit(insn); + result + } - let result = self.alloc_pseudo(); + ExprKind::Memmove { dest, src, n } => { + let dest_val = self.linearize_expr(dest); + let src_val = self.linearize_expr(src); + let n_val = self.linearize_expr(n); + let result = self.alloc_pseudo(); - let opcode = match op { - BinaryOp::Add => { - if is_float { - Opcode::FAdd - } else { - Opcode::Add - } - } - BinaryOp::Sub => { - if is_float { - Opcode::FSub - } else { - Opcode::Sub - } - } - BinaryOp::Mul => { - if is_float { - Opcode::FMul - } else { - Opcode::Mul - } - } - BinaryOp::Div => { - if is_float { - Opcode::FDiv - } else if is_unsigned { - Opcode::DivU - } else { - Opcode::DivS - } - } - BinaryOp::Mod => { - // Modulo is not supported for floats in hardware - use fmod() library call - // For now, use integer modulo (semantic analysis should catch float % float) - if is_unsigned { - Opcode::ModU - } else { - Opcode::ModS - } - } - BinaryOp::Lt => { - if is_float { - Opcode::FCmpOLt - } else if is_unsigned { - Opcode::SetB - } else { - Opcode::SetLt - } - } - BinaryOp::Gt => { - if is_float { - Opcode::FCmpOGt - } else if is_unsigned { - Opcode::SetA - } else { - Opcode::SetGt - } - } - BinaryOp::Le => { - if is_float { - Opcode::FCmpOLe - } else if is_unsigned { - Opcode::SetBe - } else { - Opcode::SetLe - } + let insn = Instruction::new(Opcode::Memmove) + .with_target(result) + .with_src3(dest_val, src_val, n_val) + .with_type_and_size(self.types.void_ptr_id, 64); + self.emit(insn); + result } - BinaryOp::Ge => { - if is_float { - Opcode::FCmpOGe - } else if is_unsigned { - Opcode::SetAe - } else { - Opcode::SetGe - } + + ExprKind::Fabs { arg } => { + let arg_val = self.linearize_expr(arg); + let result = self.alloc_pseudo(); + + let insn = Instruction::new(Opcode::Fabs64) + .with_target(result) + .with_src(arg_val) + .with_size(64) + .with_type(self.types.double_id); + self.emit(insn); + result } - BinaryOp::Eq => { - if is_float { - Opcode::FCmpOEq - } else { - Opcode::SetEq - } + + ExprKind::Fabsf { arg } => { + let arg_val = self.linearize_expr(arg); + let result = self.alloc_pseudo(); + + let insn = Instruction::new(Opcode::Fabs32) + .with_target(result) + .with_src(arg_val) + .with_size(32) + .with_type(self.types.float_id); + self.emit(insn); + result } - BinaryOp::Ne => { - if is_float { - Opcode::FCmpONe - } else { - Opcode::SetNe - } + + ExprKind::Fabsl { arg } => { + // Long double fabs - treat as 64-bit for now + let arg_val = self.linearize_expr(arg); + let result = self.alloc_pseudo(); + + let insn = Instruction::new(Opcode::Fabs64) + .with_target(result) + .with_src(arg_val) + .with_size(128) // long double is 128-bit on our targets + .with_type(self.types.longdouble_id); + self.emit(insn); + result } - // LogAnd and LogOr are handled earlier in linearize_expr via - // emit_logical_and/emit_logical_or for proper short-circuit evaluation - BinaryOp::LogAnd | BinaryOp::LogOr => { - unreachable!("LogAnd/LogOr should be handled in ExprKind::Binary") + + ExprKind::Signbit { arg } => { + let arg_val = self.linearize_expr(arg); + let result = self.alloc_pseudo(); + + let insn = Instruction::new(Opcode::Signbit64) + .with_target(result) + .with_src(arg_val) + .with_size(64) + .with_type(self.types.int_id); + self.emit(insn); + result } - BinaryOp::BitAnd => Opcode::And, - BinaryOp::BitOr => Opcode::Or, - BinaryOp::BitXor => Opcode::Xor, - BinaryOp::Shl => Opcode::Shl, - BinaryOp::Shr => { - // Logical shift for unsigned, arithmetic for signed - if is_unsigned { - Opcode::Lsr - } else { - Opcode::Asr - } + + ExprKind::Signbitf { arg } => { + let arg_val = self.linearize_expr(arg); + let result = self.alloc_pseudo(); + + let insn = Instruction::new(Opcode::Signbit32) + .with_target(result) + .with_src(arg_val) + .with_size(32) + .with_type(self.types.int_id); + self.emit(insn); + result } - }; - // For comparison operations, use operand_typ to ensure correct size - // (comparisons produce int result but must operate at operand size) - let insn_typ = match opcode { - Opcode::SetEq - | Opcode::SetNe - | Opcode::SetLt - | Opcode::SetLe - | Opcode::SetGt - | Opcode::SetGe - | Opcode::SetB - | Opcode::SetBe - | Opcode::SetA - | Opcode::SetAe - | Opcode::FCmpOEq - | Opcode::FCmpONe - | Opcode::FCmpOLt - | Opcode::FCmpOLe - | Opcode::FCmpOGt - | Opcode::FCmpOGe => operand_typ, - _ => result_typ, - }; - let insn_size = self.types.size_bits(insn_typ); - self.emit(Instruction::binop( - opcode, result, left, right, insn_typ, insn_size, - )); - result - } + ExprKind::Signbitl { arg } => { + // Long double signbit - use 64-bit version + let arg_val = self.linearize_expr(arg); + let result = self.alloc_pseudo(); - /// Emit complex arithmetic operation - /// Complex values are stored as two adjacent float/double values (real, imag) - /// This function expands complex ops to operations on the component parts - fn emit_complex_binary( - &mut self, - op: BinaryOp, - left_addr: PseudoId, - right_addr: PseudoId, - complex_typ: TypeId, - ) -> PseudoId { - // Get the base float type (float, double, or long double) - let base_typ = self.types.complex_base(complex_typ); - let base_size = self.types.size_bits(base_typ); - let base_bytes = (base_size / 8) as i64; + let insn = Instruction::new(Opcode::Signbit64) + .with_target(result) + .with_src(arg_val) + .with_size(128) // long double is 128-bit on our targets + .with_type(self.types.int_id); + self.emit(insn); + result + } - // Allocate result temporary (stack space for the complex result) - let result_addr = self.alloc_local_temp(complex_typ); + ExprKind::Unreachable => { + // __builtin_unreachable() - marks code path as never reached + // Emits an instruction that will trap if actually executed + let result = self.alloc_pseudo(); - // Load real and imaginary parts of left operand - let left_real = self.alloc_pseudo(); - self.emit(Instruction::load( - left_real, left_addr, 0, base_typ, base_size, - )); + let insn = Instruction::new(Opcode::Unreachable) + .with_target(result) + .with_type(self.types.void_id); + self.emit(insn); + result + } - let left_imag = self.alloc_pseudo(); - self.emit(Instruction::load( - left_imag, left_addr, base_bytes, base_typ, base_size, - )); + ExprKind::FrameAddress { level } => { + // __builtin_frame_address(level) - returns frame pointer at given level + let level_val = self.linearize_expr(level); + let result = self.alloc_pseudo(); - // Load real and imaginary parts of right operand - let right_real = self.alloc_pseudo(); - self.emit(Instruction::load( - right_real, right_addr, 0, base_typ, base_size, - )); + let insn = Instruction::new(Opcode::FrameAddress) + .with_target(result) + .with_src(level_val) + .with_type_and_size(self.types.void_ptr_id, 64); + self.emit(insn); + result + } - let right_imag = self.alloc_pseudo(); - self.emit(Instruction::load( - right_imag, right_addr, base_bytes, base_typ, base_size, - )); + ExprKind::ReturnAddress { level } => { + // __builtin_return_address(level) - returns return address at given level + let level_val = self.linearize_expr(level); + let result = self.alloc_pseudo(); - // Perform the operation on the components - let (result_real, result_imag) = match op { - BinaryOp::Add => { - // (a + bi) + (c + di) = (a+c) + (b+d)i - let real = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::FAdd, - real, - left_real, - right_real, - base_typ, - base_size, - )); - let imag = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::FAdd, - imag, - left_imag, - right_imag, - base_typ, - base_size, - )); - (real, imag) + let insn = Instruction::new(Opcode::ReturnAddress) + .with_target(result) + .with_src(level_val) + .with_type_and_size(self.types.void_ptr_id, 64); + self.emit(insn); + result } - BinaryOp::Sub => { - // (a + bi) - (c + di) = (a-c) + (b-d)i - let real = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::FSub, - real, - left_real, - right_real, - base_typ, - base_size, - )); - let imag = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::FSub, - imag, - left_imag, - right_imag, - base_typ, - base_size, - )); - (real, imag) + + ExprKind::Setjmp { env } => { + // setjmp(env) - saves execution context, returns int + let env_val = self.linearize_expr(env); + let result = self.alloc_pseudo(); + + let insn = Instruction::new(Opcode::Setjmp) + .with_target(result) + .with_src(env_val) + .with_type_and_size(self.types.int_id, 32); + self.emit(insn); + result } - BinaryOp::Mul => { - // Complex multiply via rtlib call (__mulsc3, __muldc3, etc.) - let base_kind = self.types.kind(base_typ); - let func_name = crate::arch::mapping::complex_mul_name(base_kind, self.target); - let call_result = self.emit_complex_rtlib_call( - func_name, - (left_real, left_imag), - (right_real, right_imag), - base_typ, - complex_typ, - ); - // Load real/imag from the call result - let real = self.alloc_pseudo(); - self.emit(Instruction::load(real, call_result, 0, base_typ, base_size)); - let imag = self.alloc_pseudo(); - self.emit(Instruction::load( - imag, - call_result, - base_bytes, - base_typ, - base_size, - )); - (real, imag) + + ExprKind::Longjmp { env, val } => { + // longjmp(env, val) - restores execution context (never returns) + let env_val = self.linearize_expr(env); + let val_val = self.linearize_expr(val); + let result = self.alloc_pseudo(); + + let mut insn = Instruction::new(Opcode::Longjmp); + insn.target = Some(result); + insn.src = vec![env_val, val_val]; + insn.typ = Some(self.types.void_id); + self.emit(insn); + result } - BinaryOp::Div => { - // Complex divide via rtlib call (__divsc3, __divdc3, etc.) - let base_kind = self.types.kind(base_typ); - let func_name = crate::arch::mapping::complex_div_name(base_kind, self.target); - let call_result = self.emit_complex_rtlib_call( - func_name, - (left_real, left_imag), - (right_real, right_imag), - base_typ, - complex_typ, - ); - // Load real/imag from the call result - let real = self.alloc_pseudo(); - self.emit(Instruction::load(real, call_result, 0, base_typ, base_size)); - let imag = self.alloc_pseudo(); - self.emit(Instruction::load( - imag, - call_result, - base_bytes, - base_typ, - base_size, - )); - (real, imag) + _ => unreachable!(), + } + } + + pub(crate) fn linearize_c11_atomic(&mut self, expr: &Expr) -> PseudoId { + match &expr.kind { + // ================================================================ + // Atomic builtins (Clang __c11_atomic_* for C11 stdatomic.h) + // ================================================================ + ExprKind::C11AtomicInit { ptr, val } => { + // atomic_init is a non-atomic store (no memory ordering) + let ptr_val = self.linearize_expr(ptr); + let value = self.linearize_expr(val); + let ptr_type = self.expr_type(ptr); + let elem_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + let size = self.types.size_bits(elem_type); + let result = self.alloc_pseudo(); + + // Use AtomicStore with Relaxed ordering for init + let insn = Instruction::new(Opcode::AtomicStore) + .with_target(result) + .with_src(ptr_val) + .with_src(value) + .with_src(self.emit_const(0, self.types.int_id)) // Relaxed = 0 + .with_type_and_size(elem_type, size) + .with_memory_order(MemoryOrder::Relaxed); + self.emit(insn); + result } - _ => { - // Other operations not supported for complex types - error( - Position::default(), - &format!("unsupported operation {:?} on complex types", op), - ); - return result_addr; + + ExprKind::C11AtomicLoad { ptr, order } => { + let ptr_val = self.linearize_expr(ptr); + let order_val = self.linearize_expr(order); + let memory_order = self.eval_memory_order(order); + let ptr_type = self.expr_type(ptr); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + let size = self.types.size_bits(result_type); + let result = self.alloc_pseudo(); + + let insn = Instruction::new(Opcode::AtomicLoad) + .with_target(result) + .with_src(ptr_val) + .with_src(order_val) + .with_type_and_size(result_type, size) + .with_memory_order(memory_order); + self.emit(insn); + result } - }; - // Store result components - self.emit(Instruction::store( - result_real, - result_addr, - 0, - base_typ, - base_size, - )); - self.emit(Instruction::store( - result_imag, - result_addr, - base_bytes, - base_typ, - base_size, - )); + ExprKind::C11AtomicStore { ptr, val, order } => { + let ptr_val = self.linearize_expr(ptr); + let value = self.linearize_expr(val); + let order_val = self.linearize_expr(order); + let memory_order = self.eval_memory_order(order); + let ptr_type = self.expr_type(ptr); + let elem_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + let size = self.types.size_bits(elem_type); + let result = self.alloc_pseudo(); - result_addr - } + let insn = Instruction::new(Opcode::AtomicStore) + .with_target(result) + .with_src(ptr_val) + .with_src(value) + .with_src(order_val) + .with_type_and_size(elem_type, size) + .with_memory_order(memory_order); + self.emit(insn); + result + } - /// Allocate a local temporary variable for a complex result - fn alloc_local_temp(&mut self, typ: TypeId) -> PseudoId { - let size = self.types.size_bytes(typ); - let size_const = self.emit_const(size as i128, self.types.ulong_id); - let addr = self.alloc_pseudo(); - let alloca_insn = Instruction::new(Opcode::Alloca) - .with_target(addr) - .with_src(size_const) - .with_type_and_size(self.types.void_ptr_id, 64); - self.emit(alloca_insn); - addr - } + ExprKind::C11AtomicExchange { ptr, val, order } => { + let ptr_val = self.linearize_expr(ptr); + let value = self.linearize_expr(val); + let order_val = self.linearize_expr(order); + let memory_order = self.eval_memory_order(order); + let ptr_type = self.expr_type(ptr); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + let size = self.types.size_bits(result_type); + let result = self.alloc_pseudo(); - /// Emit a call to a complex rtlib function (__mulXc3, __divXc3). - /// - /// These functions take 4 scalar args (left_real, left_imag, right_real, right_imag) - /// and return a complex value. The result is stored in newly allocated local storage. - /// - /// Returns the address where the complex result is stored. - fn emit_complex_rtlib_call( - &mut self, - func_name: &str, - left: (PseudoId, PseudoId), - right: (PseudoId, PseudoId), - base_typ: TypeId, - complex_typ: TypeId, - ) -> PseudoId { - let (left_real, left_imag) = left; - let (right_real, right_imag) = right; - // Allocate local storage for the complex result - let result_sym = self.alloc_pseudo(); - let unique_name = format!("__cret_{}", result_sym.0); - let result_pseudo = Pseudo::sym(result_sym, unique_name.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(result_pseudo); - func.add_local( - &unique_name, - result_sym, - complex_typ, - false, // not volatile - false, // not atomic - self.current_bb, - None, // no explicit alignment - ); - } + let insn = Instruction::new(Opcode::AtomicSwap) + .with_target(result) + .with_src(ptr_val) + .with_src(value) + .with_src(order_val) + .with_type_and_size(result_type, size) + .with_memory_order(memory_order); + self.emit(insn); + result + } + + ExprKind::C11AtomicCompareExchangeStrong { + ptr, + expected, + desired, + succ_order, + } + | ExprKind::C11AtomicCompareExchangeWeak { + ptr, + expected, + desired, + succ_order, + } => { + // Both strong and weak are implemented the same (as strong) + let ptr_val = self.linearize_expr(ptr); + let expected_ptr = self.linearize_expr(expected); + let desired_val = self.linearize_expr(desired); + let order_val = self.linearize_expr(succ_order); + let memory_order = self.eval_memory_order(succ_order); + let ptr_type = self.expr_type(ptr); + let elem_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + let elem_size = self.types.size_bits(elem_type); + let result = self.alloc_pseudo(); - // Build argument list: 4 scalar FP values - let arg_vals = vec![left_real, left_imag, right_real, right_imag]; - let arg_types = vec![base_typ, base_typ, base_typ, base_typ]; + // For CAS, typ is bool (result), but size is the element size for codegen + let insn = Instruction::new(Opcode::AtomicCas) + .with_target(result) + .with_src(ptr_val) + .with_src(expected_ptr) + .with_src(desired_val) + .with_src(order_val) + .with_type(self.types.bool_id) + .with_size(elem_size) + .with_memory_order(memory_order); + self.emit(insn); + result + } - // Compute ABI classification for the call - let abi = get_abi_for_conv(self.current_calling_conv, self.target); - let param_classes: Vec<_> = arg_types - .iter() - .map(|&t| abi.classify_param(t, self.types)) - .collect(); - let ret_class = abi.classify_return(complex_typ, self.types); - let call_abi_info = Box::new(CallAbiInfo::new(param_classes, ret_class)); + ExprKind::C11AtomicFetchAdd { ptr, val, order } => { + let ptr_val = self.linearize_expr(ptr); + let value = self.linearize_expr(val); + let order_val = self.linearize_expr(order); + let memory_order = self.eval_memory_order(order); + let ptr_type = self.expr_type(ptr); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + let size = self.types.size_bits(result_type); + let result = self.alloc_pseudo(); - // Create the call instruction - let ret_size = self.types.size_bits(complex_typ); - let mut call_insn = Instruction::call( - Some(result_sym), - func_name, - arg_vals, - arg_types, - complex_typ, - ret_size, - ); - call_insn.abi_info = Some(call_abi_info); - self.emit(call_insn); - - result_sym - } + let insn = Instruction::new(Opcode::AtomicFetchAdd) + .with_target(result) + .with_src(ptr_val) + .with_src(value) + .with_src(order_val) + .with_type_and_size(result_type, size) + .with_memory_order(memory_order); + self.emit(insn); + result + } - fn emit_compare_zero(&mut self, val: PseudoId, operand_typ: TypeId) -> PseudoId { - let result = self.alloc_pseudo(); - let zero = self.emit_const(0, operand_typ); - let size = self.types.size_bits(operand_typ); - self.emit(Instruction::binop( - Opcode::SetNe, - result, - val, - zero, - operand_typ, - size, - )); - result - } + ExprKind::C11AtomicFetchSub { ptr, val, order } => { + let ptr_val = self.linearize_expr(ptr); + let value = self.linearize_expr(val); + let order_val = self.linearize_expr(order); + let memory_order = self.eval_memory_order(order); + let ptr_type = self.expr_type(ptr); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + let size = self.types.size_bits(result_type); + let result = self.alloc_pseudo(); - /// Emit short-circuit logical AND: a && b - /// If a is false, skip evaluation of b and return 0. - /// Otherwise, evaluate b and return (b != 0). - fn emit_logical_and(&mut self, left: &Expr, right: &Expr) -> PseudoId { - let result_typ = self.types.int_id; + let insn = Instruction::new(Opcode::AtomicFetchSub) + .with_target(result) + .with_src(ptr_val) + .with_src(value) + .with_src(order_val) + .with_type_and_size(result_type, size) + .with_memory_order(memory_order); + self.emit(insn); + result + } - // Create basic blocks - let eval_b_bb = self.alloc_bb(); - let merge_bb = self.alloc_bb(); + ExprKind::C11AtomicFetchAnd { ptr, val, order } => { + let ptr_val = self.linearize_expr(ptr); + let value = self.linearize_expr(val); + let order_val = self.linearize_expr(order); + let memory_order = self.eval_memory_order(order); + let ptr_type = self.expr_type(ptr); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + let size = self.types.size_bits(result_type); + let result = self.alloc_pseudo(); - // Evaluate LHS - let left_typ = self.expr_type(left); - let left_val = self.linearize_expr(left); - let left_bool = self.emit_compare_zero(left_val, left_typ); + let insn = Instruction::new(Opcode::AtomicFetchAnd) + .with_target(result) + .with_src(ptr_val) + .with_src(value) + .with_src(order_val) + .with_type_and_size(result_type, size) + .with_memory_order(memory_order); + self.emit(insn); + result + } - // Emit the short-circuit value (0) BEFORE the branch, while still in LHS block - // This value will be used if we short-circuit (LHS is false) - let zero = self.emit_const(0, result_typ); + ExprKind::C11AtomicFetchOr { ptr, val, order } => { + let ptr_val = self.linearize_expr(ptr); + let value = self.linearize_expr(val); + let order_val = self.linearize_expr(order); + let memory_order = self.eval_memory_order(order); + let ptr_type = self.expr_type(ptr); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + let size = self.types.size_bits(result_type); + let result = self.alloc_pseudo(); - // Get the block where LHS evaluation ended (may differ from initial block - // if LHS contains nested control flow) - let lhs_end_bb = self.current_bb.unwrap(); + let insn = Instruction::new(Opcode::AtomicFetchOr) + .with_target(result) + .with_src(ptr_val) + .with_src(value) + .with_src(order_val) + .with_type_and_size(result_type, size) + .with_memory_order(memory_order); + self.emit(insn); + result + } - // Branch: if LHS is false, go to merge (result = 0); else evaluate RHS - self.emit(Instruction::cbr(left_bool, eval_b_bb, merge_bb)); - self.link_bb(lhs_end_bb, eval_b_bb); - self.link_bb(lhs_end_bb, merge_bb); + ExprKind::C11AtomicFetchXor { ptr, val, order } => { + let ptr_val = self.linearize_expr(ptr); + let value = self.linearize_expr(val); + let order_val = self.linearize_expr(order); + let memory_order = self.eval_memory_order(order); + let ptr_type = self.expr_type(ptr); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + let size = self.types.size_bits(result_type); + let result = self.alloc_pseudo(); - // eval_b_bb: Evaluate RHS - self.switch_bb(eval_b_bb); - let right_typ = self.expr_type(right); - let right_val = self.linearize_expr(right); - let right_bool = self.emit_compare_zero(right_val, right_typ); + let insn = Instruction::new(Opcode::AtomicFetchXor) + .with_target(result) + .with_src(ptr_val) + .with_src(value) + .with_src(order_val) + .with_type_and_size(result_type, size) + .with_memory_order(memory_order); + self.emit(insn); + result + } - // Get the actual block where RHS evaluation ended (may differ from eval_b_bb - // if RHS contains nested control flow like another &&/||) - let rhs_end_bb = self.current_bb.unwrap(); + ExprKind::C11AtomicThreadFence { order } => { + let order_val = self.linearize_expr(order); + let memory_order = self.eval_memory_order(order); + let result = self.alloc_pseudo(); - // Branch to merge - self.emit(Instruction::br(merge_bb)); - self.link_bb(rhs_end_bb, merge_bb); + let insn = Instruction::new(Opcode::Fence) + .with_target(result) + .with_src(order_val) + .with_type(self.types.void_id) + .with_memory_order(memory_order); + self.emit(insn); + result + } - // merge_bb: Create phi node to merge results - self.switch_bb(merge_bb); + ExprKind::C11AtomicSignalFence { order } => { + // Signal fence is a compiler barrier only (no memory fence instruction) + // For now, treat it the same as thread fence + let order_val = self.linearize_expr(order); + let memory_order = self.eval_memory_order(order); + let result = self.alloc_pseudo(); - // Result is 0 if we came from lhs_end_bb (LHS was false), - // or right_bool if we came from rhs_end_bb (LHS was true) - let result = self.alloc_pseudo(); - // Create a phi pseudo for the target - important for register allocation - let phi_pseudo = Pseudo::phi(result, result.0); - if let Some(func) = &mut self.current_func { - func.add_pseudo(phi_pseudo); + let insn = Instruction::new(Opcode::Fence) + .with_target(result) + .with_src(order_val) + .with_type(self.types.void_id) + .with_memory_order(memory_order); + self.emit(insn); + result + } + _ => unreachable!(), } - let mut phi_insn = Instruction::phi(result, result_typ, 32); - let phisrc1 = self.emit_phi_source(lhs_end_bb, zero, result, merge_bb, result_typ, 32); - phi_insn.phi_list.push((lhs_end_bb, phisrc1)); - let phisrc2 = - self.emit_phi_source(rhs_end_bb, right_bool, result, merge_bb, result_typ, 32); - phi_insn.phi_list.push((rhs_end_bb, phisrc2)); - self.emit(phi_insn); - - result } - /// Emit short-circuit logical OR: a || b - /// If a is true, skip evaluation of b and return 1. - /// Otherwise, evaluate b and return (b != 0). - fn emit_logical_or(&mut self, left: &Expr, right: &Expr) -> PseudoId { - let result_typ = self.types.int_id; - - // Create basic blocks - let eval_b_bb = self.alloc_bb(); - let merge_bb = self.alloc_bb(); + pub(crate) fn linearize_expr(&mut self, expr: &Expr) -> PseudoId { + // Set current position for debug info + self.current_pos = Some(expr.pos); - // Evaluate LHS - let left_typ = self.expr_type(left); - let left_val = self.linearize_expr(left); - let left_bool = self.emit_compare_zero(left_val, left_typ); + match &expr.kind { + ExprKind::IntLit(val) => { + let typ = self.expr_type(expr); + self.emit_const(*val as i128, typ) + } - // Emit the short-circuit value (1) BEFORE the branch, while still in LHS block - // This value will be used if we short-circuit (LHS is true) - let one = self.emit_const(1, result_typ); + ExprKind::Int128Lit(val) => { + let typ = self.expr_type(expr); + self.emit_const(*val, typ) + } - // Get the block where LHS evaluation ended (may differ from initial block - // if LHS contains nested control flow) - let lhs_end_bb = self.current_bb.unwrap(); + ExprKind::FloatLit(val) => { + let typ = self.expr_type(expr); + self.emit_fconst(*val, typ) + } - // Branch: if LHS is true, go to merge (result = 1); else evaluate RHS - self.emit(Instruction::cbr(left_bool, merge_bb, eval_b_bb)); - self.link_bb(lhs_end_bb, merge_bb); - self.link_bb(lhs_end_bb, eval_b_bb); + ExprKind::CharLit(c) => { + let typ = self.expr_type(expr); + self.emit_const(*c as u8 as i8 as i128, typ) + } - // eval_b_bb: Evaluate RHS - self.switch_bb(eval_b_bb); - let right_typ = self.expr_type(right); - let right_val = self.linearize_expr(right); - let right_bool = self.emit_compare_zero(right_val, right_typ); + ExprKind::StringLit(s) => { + let label = self.module.add_string(s.clone()); + self.emit_string_sym(expr, label) + } - // Get the actual block where RHS evaluation ended (may differ from eval_b_bb - // if RHS contains nested control flow like another &&/||) - let rhs_end_bb = self.current_bb.unwrap(); + ExprKind::WideStringLit(s) => { + let label = self.module.add_wide_string(s.clone()); + self.emit_string_sym(expr, label) + } - // Branch to merge - self.emit(Instruction::br(merge_bb)); - self.link_bb(rhs_end_bb, merge_bb); + ExprKind::Ident(symbol_id) => self.linearize_ident(expr, *symbol_id), - // merge_bb: Create phi node to merge results - self.switch_bb(merge_bb); + ExprKind::FuncName => self.linearize_func_name(), - // Result is 1 if we came from lhs_end_bb (LHS was true), - // or right_bool if we came from rhs_end_bb (LHS was false) - let result = self.alloc_pseudo(); - // Create a phi pseudo for the target - important for register allocation - let phi_pseudo = Pseudo::phi(result, result.0); - if let Some(func) = &mut self.current_func { - func.add_pseudo(phi_pseudo); - } - let mut phi_insn = Instruction::phi(result, result_typ, 32); - let phisrc1 = self.emit_phi_source(lhs_end_bb, one, result, merge_bb, result_typ, 32); - phi_insn.phi_list.push((lhs_end_bb, phisrc1)); - let phisrc2 = - self.emit_phi_source(rhs_end_bb, right_bool, result, merge_bb, result_typ, 32); - phi_insn.phi_list.push((rhs_end_bb, phisrc2)); - self.emit(phi_insn); + ExprKind::Unary { op, operand } => self.linearize_unary(expr, *op, operand), - result - } + ExprKind::Binary { op, left, right } => self.linearize_binary(expr, *op, left, right), - fn emit_assign(&mut self, op: AssignOp, target: &Expr, value: &Expr) -> PseudoId { - let target_typ = self.expr_type(target); - let value_typ = self.expr_type(value); - - // For complex type assignment, handle specially - copy real and imag parts - if self.types.is_complex(target_typ) && op == AssignOp::Assign { - let target_addr = self.linearize_lvalue(target); - let value_addr = self.linearize_lvalue(value); - let base_typ = self.types.complex_base(target_typ); - let base_size = self.types.size_bits(base_typ); - let base_bytes = (base_size / 8) as i64; - - // Load and store real part - let real = self.alloc_pseudo(); - self.emit(Instruction::load(real, value_addr, 0, base_typ, base_size)); - self.emit(Instruction::store( - real, - target_addr, - 0, - base_typ, - base_size, - )); + ExprKind::Assign { op, target, value } => self.emit_assign(*op, target, value), - // Load and store imaginary part - let imag = self.alloc_pseudo(); - self.emit(Instruction::load( - imag, value_addr, base_bytes, base_typ, base_size, - )); - self.emit(Instruction::store( - imag, - target_addr, - base_bytes, - base_typ, - base_size, - )); + ExprKind::PostInc(operand) => self.linearize_postop(operand, true), - return real; // Return real part as the result value - } + ExprKind::PostDec(operand) => self.linearize_postop(operand, false), - // For struct/union assignment, do a block copy via addresses. - // Structs are not loaded into registers by linearize_expr — they return - // an address. So we must handle ALL struct sizes here, not just large ones. - let target_kind = self.types.kind(target_typ); - let target_size = self.types.size_bits(target_typ); - if (target_kind == TypeKind::Struct || target_kind == TypeKind::Union) - && target_size > 0 - && op == AssignOp::Assign - { - let target_addr = self.linearize_lvalue(target); - let value_addr = self.linearize_lvalue(value); - let target_size_bytes = target_size / 8; + ExprKind::Conditional { + cond, + then_expr, + else_expr, + } => self.linearize_ternary(expr, cond, then_expr, else_expr), - self.emit_block_copy(target_addr, value_addr, target_size_bytes as i64); + ExprKind::Call { func, args } => self.linearize_call(expr, func, args), - // Return the target address as the result - return target_addr; - } + ExprKind::Member { + expr: inner_expr, + member, + } => self.linearize_member(expr, inner_expr, *member), - let rhs = self.linearize_expr(value); + ExprKind::Arrow { + expr: inner_expr, + member, + } => self.linearize_arrow(expr, inner_expr, *member), - // Check for pointer compound assignment (p += n or p -= n) - let is_ptr_arith = self.types.kind(target_typ) == TypeKind::Pointer - && self.types.is_integer(value_typ) - && (op == AssignOp::AddAssign || op == AssignOp::SubAssign); + ExprKind::Index { array, index } => self.linearize_index(expr, array, index), - // Convert RHS to target type if needed (but not for pointer arithmetic) - let rhs = if is_ptr_arith { - // For pointer arithmetic, scale the integer by element size - let elem_type = self - .types - .base_type(target_typ) - .unwrap_or(self.types.char_id); - let elem_size = self.types.size_bits(elem_type) / 8; - let scale = self.emit_const(elem_size as i128, self.types.long_id); + ExprKind::Cast { + cast_type, + expr: inner_expr, + } => self.linearize_cast(inner_expr, *cast_type), - // Extend the integer to 64-bit for proper arithmetic - let rhs_extended = self.emit_convert(rhs, value_typ, self.types.long_id); + ExprKind::SizeofType(typ) => { + let size = self.types.size_bits(*typ) / 8; + // sizeof returns size_t, which is unsigned long in our implementation + let result_typ = self.types.ulong_id; + self.emit_const(size as i128, result_typ) + } - let scaled = self.alloc_reg_pseudo(); - self.emit(Instruction::binop( - Opcode::Mul, - scaled, - rhs_extended, - scale, - self.types.long_id, - 64, - )); - scaled - } else { - self.emit_convert(rhs, value_typ, target_typ) - }; + ExprKind::SizeofExpr(inner_expr) => { + // Check if this is a VLA variable - need runtime sizeof + if let ExprKind::Ident(symbol_id) = &inner_expr.kind { + if let Some(info) = self.locals.get(symbol_id).cloned() { + if let (Some(size_sym), Some(elem_type)) = + (info.vla_size_sym, info.vla_elem_type) + { + // VLA: compute sizeof at runtime as num_elements * sizeof(element) + let result_typ = self.types.ulong_id; + let elem_size = self.types.size_bytes(elem_type) as i64; - let final_val = match op { - AssignOp::Assign => rhs, - _ => { - // Compound assignment - get current value and apply operation - let lhs = self.linearize_expr(target); - let result = self.alloc_reg_pseudo(); + // Load the stored number of elements + let num_elements = self.alloc_pseudo(); + let load_insn = + Instruction::load(num_elements, size_sym, 0, result_typ, 64); + self.emit(load_insn); - let is_float = self.types.is_float(target_typ); - let is_unsigned = self.types.is_unsigned(target_typ); - let opcode = match op { - AssignOp::AddAssign => { - if is_float { - Opcode::FAdd - } else { - Opcode::Add - } - } - AssignOp::SubAssign => { - if is_float { - Opcode::FSub - } else { - Opcode::Sub - } - } - AssignOp::MulAssign => { - if is_float { - Opcode::FMul - } else { - Opcode::Mul - } - } - AssignOp::DivAssign => { - if is_float { - Opcode::FDiv - } else if is_unsigned { - Opcode::DivU - } else { - Opcode::DivS - } - } - AssignOp::ModAssign => { - // Modulo not supported for floats - if is_unsigned { - Opcode::ModU - } else { - Opcode::ModS - } - } - AssignOp::AndAssign => Opcode::And, - AssignOp::OrAssign => Opcode::Or, - AssignOp::XorAssign => Opcode::Xor, - AssignOp::ShlAssign => Opcode::Shl, - AssignOp::ShrAssign => { - if is_unsigned { - Opcode::Lsr - } else { - Opcode::Asr + // Multiply by element size + let elem_size_const = self.emit_const(elem_size as i128, result_typ); + let result = self.alloc_pseudo(); + let mul_insn = Instruction::new(Opcode::Mul) + .with_target(result) + .with_src(num_elements) + .with_src(elem_size_const) + .with_size(64) + .with_type(result_typ); + self.emit(mul_insn); + return result; } } - AssignOp::Assign => unreachable!(), - }; - - // For pointer arithmetic, use Long type for the operation - let arith_type = if is_ptr_arith { - self.types.long_id - } else { - target_typ - }; + } - let arith_size = self.types.size_bits(arith_type); - self.emit(Instruction::binop( - opcode, result, lhs, rhs, arith_type, arith_size, - )); - result + // Non-VLA: compute size at compile time + let inner_typ = self.expr_type(inner_expr); + let size = self.types.size_bits(inner_typ) / 8; + // sizeof returns size_t, which is unsigned long in our implementation + let result_typ = self.types.ulong_id; + self.emit_const(size as i128, result_typ) } - }; - // Store based on target expression type - let target_size = self.types.size_bits(target_typ); - match &target.kind { - ExprKind::Ident(symbol_id) => { - let name_str = self.symbol_name(*symbol_id); - if let Some(local) = self.locals.get(symbol_id).cloned() { - // Check if this is a static local (sentinel value) - if local.sym.0 == u32::MAX { - self.emit_static_local_store(&name_str, final_val, target_typ, target_size); - } else { - // Regular local variable: emit Store - self.emit(Instruction::store( - final_val, - local.sym, - 0, - target_typ, - target_size, - )); - } - } else if self.var_map.contains_key(&name_str) { - // Parameter: this is not SSA-correct but parameters - // shouldn't be reassigned. If they are, we'd need to - // demote them to locals. For now, just update the mapping. - self.var_map.insert(name_str.clone(), final_val); - } else { - // Global variable - emit store - let sym_id = self.alloc_pseudo(); - let pseudo = Pseudo::sym(sym_id, name_str); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - self.emit(Instruction::store( - final_val, - sym_id, - 0, - target_typ, - target_size, - )); - } + ExprKind::AlignofType(typ) => { + let align = self.types.alignment(*typ); + // _Alignof returns size_t + let result_typ = self.types.ulong_id; + self.emit_const(align as i128, result_typ) } - ExprKind::Member { expr, member } => { - // Struct member: get address and store with offset - let base = self.linearize_lvalue(expr); - let base_struct_type = self.expr_type(expr); - // Resolve if the struct type is incomplete (forward-declared) - let struct_type = self.resolve_struct_type(base_struct_type); - let member_info = - self.types - .find_member(struct_type, *member) - .unwrap_or(MemberInfo { - offset: 0, - typ: target_typ, - bit_offset: None, - bit_width: None, - storage_unit_size: None, - }); - if let (Some(bit_offset), Some(bit_width), Some(storage_size)) = ( - member_info.bit_offset, - member_info.bit_width, - member_info.storage_unit_size, - ) { - // Bitfield store - self.emit_bitfield_store( - base, - member_info.offset, - bit_offset, - bit_width, - storage_size, - final_val, - ); - } else { - let member_size = self.types.size_bits(member_info.typ); - self.emit(Instruction::store( - final_val, - base, - member_info.offset as i64, - member_info.typ, - member_size, - )); - } + + ExprKind::AlignofExpr(inner_expr) => { + let inner_typ = self.expr_type(inner_expr); + let align = self.types.alignment(inner_typ); + // _Alignof returns size_t + let result_typ = self.types.ulong_id; + self.emit_const(align as i128, result_typ) } - ExprKind::Arrow { expr, member } => { - // Pointer member: pointer value is the base address - let ptr = self.linearize_expr(expr); - let ptr_type = self.expr_type(expr); - let base_struct_type = self.types.base_type(ptr_type).unwrap_or(target_typ); - // Resolve if the struct type is incomplete (forward-declared) - let struct_type = self.resolve_struct_type(base_struct_type); - let member_info = - self.types - .find_member(struct_type, *member) - .unwrap_or(MemberInfo { - offset: 0, - typ: target_typ, - bit_offset: None, - bit_width: None, - storage_unit_size: None, - }); - if let (Some(bit_offset), Some(bit_width), Some(storage_size)) = ( - member_info.bit_offset, - member_info.bit_width, - member_info.storage_unit_size, - ) { - // Bitfield store - self.emit_bitfield_store( - ptr, - member_info.offset, - bit_offset, - bit_width, - storage_size, - final_val, - ); - } else { - let member_size = self.types.size_bits(member_info.typ); - self.emit(Instruction::store( - final_val, - ptr, - member_info.offset as i64, - member_info.typ, - member_size, - )); + + ExprKind::Comma(exprs) => { + let mut result = self.emit_const(0, self.types.int_id); + for e in exprs { + result = self.linearize_expr(e); } + result } - ExprKind::Unary { - op: UnaryOp::Deref, - operand, - } => { - // Dereference: store to the pointer address - let ptr = self.linearize_expr(operand); - self.emit(Instruction::store( - final_val, - ptr, - 0, - target_typ, - target_size, - )); - } - ExprKind::Index { array, index } => { - // Array subscript: compute address and store - // Handle commutative form: 0[arr] is equivalent to arr[0] - let array_type = self.expr_type(array); - let index_type = self.expr_type(index); - let array_kind = self.types.kind(array_type); - let (ptr_expr, idx_expr, idx_type) = - if array_kind == TypeKind::Pointer || array_kind == TypeKind::Array { - (array, index, index_type) - } else { - // Swap: index is actually the pointer/array - (index, array, array_type) - }; + ExprKind::InitList { .. } => { + // InitList is handled specially in linearize_local_decl and linearize_global_decl + // It shouldn't be reached here during normal expression evaluation + panic!("InitList should be handled in declaration context, not as standalone expression") + } - let arr = self.linearize_expr(ptr_expr); - let idx = self.linearize_expr(idx_expr); - let elem_size = target_size / 8; - let elem_size_val = self.emit_const(elem_size as i128, self.types.long_id); + ExprKind::CompoundLiteral { .. } => self.linearize_compound_literal(expr), - // Sign-extend index to 64-bit for proper pointer arithmetic (negative indices) - let idx_extended = self.emit_convert(idx, idx_type, self.types.long_id); + ExprKind::VaStart { .. } + | ExprKind::VaArg { .. } + | ExprKind::VaEnd { .. } + | ExprKind::VaCopy { .. } => self.linearize_va_op(expr), + + ExprKind::Bswap16 { .. } + | ExprKind::Bswap32 { .. } + | ExprKind::Bswap64 { .. } + | ExprKind::Ctz { .. } + | ExprKind::Ctzl { .. } + | ExprKind::Ctzll { .. } + | ExprKind::Clz { .. } + | ExprKind::Clzl { .. } + | ExprKind::Clzll { .. } + | ExprKind::Popcount { .. } + | ExprKind::Popcountl { .. } + | ExprKind::Popcountll { .. } + | ExprKind::Alloca { .. } + | ExprKind::Memset { .. } + | ExprKind::Memcpy { .. } + | ExprKind::Memmove { .. } + | ExprKind::Fabs { .. } + | ExprKind::Fabsf { .. } + | ExprKind::Fabsl { .. } + | ExprKind::Signbit { .. } + | ExprKind::Signbitf { .. } + | ExprKind::Signbitl { .. } + | ExprKind::Unreachable + | ExprKind::FrameAddress { .. } + | ExprKind::ReturnAddress { .. } + | ExprKind::Setjmp { .. } + | ExprKind::Longjmp { .. } => self.linearize_builtin(expr), - let offset = self.alloc_pseudo(); - let ptr_typ = self.types.long_id; - self.emit(Instruction::binop( - Opcode::Mul, - offset, - idx_extended, - elem_size_val, - ptr_typ, - 64, - )); + ExprKind::OffsetOf { type_id, path } => { + // __builtin_offsetof(type, member-designator) + // Compute the byte offset of the member within the struct + let mut offset: u64 = 0; + let mut current_type = *type_id; - let addr = self.alloc_pseudo(); - self.emit(Instruction::binop( - Opcode::Add, - addr, - arr, - offset, - ptr_typ, - 64, - )); + for element in path { + match element { + OffsetOfPath::Field(field_id) => { + // Look up the field in the current struct type + let struct_type = self.resolve_struct_type(current_type); + let member_info = self + .types + .find_member(struct_type, *field_id) + .expect("offsetof: field not found in struct type"); + offset += member_info.offset as u64; + current_type = member_info.typ; + } + OffsetOfPath::Index(index) => { + // Array indexing: offset += index * sizeof(element) + let elem_type = self + .types + .base_type(current_type) + .expect("offsetof: array index on non-array type"); + let elem_size = self.types.size_bytes(elem_type); + offset += (*index as u64) * (elem_size as u64); + current_type = elem_type; + } + } + } - self.emit(Instruction::store( - final_val, - addr, - 0, - target_typ, - target_size, - )); + // Return the offset as a constant + self.emit_const(offset as i128, self.types.ulong_id) } - _ => { - // Other lvalues - should not happen for valid C code + + ExprKind::C11AtomicInit { .. } + | ExprKind::C11AtomicLoad { .. } + | ExprKind::C11AtomicStore { .. } + | ExprKind::C11AtomicExchange { .. } + | ExprKind::C11AtomicCompareExchangeStrong { .. } + | ExprKind::C11AtomicCompareExchangeWeak { .. } + | ExprKind::C11AtomicFetchAdd { .. } + | ExprKind::C11AtomicFetchSub { .. } + | ExprKind::C11AtomicFetchAnd { .. } + | ExprKind::C11AtomicFetchOr { .. } + | ExprKind::C11AtomicFetchXor { .. } + | ExprKind::C11AtomicThreadFence { .. } + | ExprKind::C11AtomicSignalFence { .. } => self.linearize_c11_atomic(expr), + + ExprKind::StmtExpr { stmts, result } => { + // GNU statement expression: ({ stmt; stmt; expr; }) + // Linearize all the statements first + for item in stmts { + match item { + BlockItem::Declaration(decl) => self.linearize_local_decl(decl), + BlockItem::Statement(s) => self.linearize_stmt(s), + } + } + // The result is the value of the final expression + self.linearize_expr(result) } } + } - final_val + /// Evaluate a memory order expression to a MemoryOrder enum value. + /// If the expression is not a constant or out of range, defaults to SeqCst. + pub(crate) fn eval_memory_order(&self, expr: &Expr) -> MemoryOrder { + // Try to evaluate as a constant integer + if let ExprKind::IntLit(val) = &expr.kind { + match *val { + 0 => MemoryOrder::Relaxed, + 1 => MemoryOrder::Consume, + 2 => MemoryOrder::Acquire, + 3 => MemoryOrder::Release, + 4 => MemoryOrder::AcqRel, + 5 => MemoryOrder::SeqCst, + _ => MemoryOrder::SeqCst, // Invalid, use strongest ordering + } + } else { + // Non-constant order expression - use SeqCst for safety + MemoryOrder::SeqCst + } } } diff --git a/cc/ir/linearize_emit.rs b/cc/ir/linearize_emit.rs new file mode 100644 index 00000000..dd4d658b --- /dev/null +++ b/cc/ir/linearize_emit.rs @@ -0,0 +1,1404 @@ +// +// Copyright (c) 2025-2026 Jeff Garzik +// +// This file is part of the posixutils-rs project covered under +// the MIT License. For the full license text, please see the LICENSE +// file in the root directory of this project. +// SPDX-License-Identifier: MIT + +//! Emit helpers for the linearizer (constants, block copies, bitfields, operators, assignments) + +use super::{CallAbiInfo, Instruction, Opcode, Pseudo, PseudoId}; +use crate::abi::get_abi_for_conv; +use crate::diag::{error, Position}; +use crate::parse::ast::{AssignOp, BinaryOp, Expr, ExprKind, UnaryOp}; +use crate::types::{MemberInfo, TypeId, TypeKind}; + +impl<'a> super::linearize::Linearizer<'a> { + pub(crate) fn emit_const(&mut self, val: i128, typ: TypeId) -> PseudoId { + let id = self.alloc_pseudo(); + let pseudo = Pseudo::val(id, val); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); + } + + // Emit setval instruction + let insn = Instruction::new(Opcode::SetVal) + .with_target(id) + .with_type_and_size(typ, self.types.size_bits(typ)); + self.emit(insn); + + id + } + + pub(crate) fn emit_fconst(&mut self, val: f64, typ: TypeId) -> PseudoId { + let id = self.alloc_pseudo(); + let pseudo = Pseudo::fval(id, val); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); + } + + // Emit setval instruction + let insn = Instruction::new(Opcode::SetVal) + .with_target(id) + .with_type_and_size(typ, self.types.size_bits(typ)); + self.emit(insn); + + id + } + + /// Emit a store to a static local variable. + /// The caller must have already verified that `name_str` refers to a static local + /// (i.e., the local's sym is the sentinel value u32::MAX). + pub(crate) fn emit_static_local_store( + &mut self, + name_str: &str, + value: PseudoId, + typ: TypeId, + size: u32, + ) { + let key = format!("{}.{}", self.current_func_name, name_str); + if let Some(static_info) = self.static_locals.get(&key).cloned() { + let sym_id = self.alloc_pseudo(); + let pseudo = Pseudo::sym(sym_id, static_info.global_name); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); + } + self.emit(Instruction::store(value, sym_id, 0, typ, size)); + } else { + unreachable!("static local sentinel without static_locals entry"); + } + } + + /// Emit stores to zero-initialize an aggregate (struct, union, or array) + /// This handles C99 6.7.8p19: uninitialized members must be zero-initialized + pub(crate) fn emit_aggregate_zero(&mut self, base_sym: PseudoId, typ: TypeId) { + let total_bytes = self.types.size_bits(typ) / 8; + let mut offset: i64 = 0; + + // Create a zero constant for 64-bit stores + let zero64 = self.emit_const(0, self.types.long_id); + + // Zero in 8-byte chunks + while offset + 8 <= total_bytes as i64 { + self.emit(Instruction::store( + zero64, + base_sym, + offset, + self.types.long_id, + 64, + )); + offset += 8; + } + + // Handle remaining bytes (if any) + if offset < total_bytes as i64 { + let remaining = total_bytes as i64 - offset; + if remaining >= 4 { + let zero32 = self.emit_const(0, self.types.int_id); + self.emit(Instruction::store( + zero32, + base_sym, + offset, + self.types.int_id, + 32, + )); + offset += 4; + } + if offset < total_bytes as i64 { + let remaining = total_bytes as i64 - offset; + if remaining >= 2 { + let zero16 = self.emit_const(0, self.types.short_id); + self.emit(Instruction::store( + zero16, + base_sym, + offset, + self.types.short_id, + 16, + )); + offset += 2; + } + if offset < total_bytes as i64 { + let zero8 = self.emit_const(0, self.types.char_id); + self.emit(Instruction::store( + zero8, + base_sym, + offset, + self.types.char_id, + 8, + )); + } + } + } + } + + /// Emit a block copy from src to dst using integer chunks. + pub(crate) fn emit_block_copy(&mut self, dst: PseudoId, src: PseudoId, size_bytes: i64) { + self.emit_block_copy_at_offset(dst, 0, src, size_bytes); + } + + /// Emit a block copy from src to dst using integer chunks. + /// The destination stores start at dst_base_offset. + pub(crate) fn emit_block_copy_at_offset( + &mut self, + dst: PseudoId, + dst_base_offset: i64, + src: PseudoId, + size_bytes: i64, + ) { + let mut offset: i64 = 0; + while offset + 8 <= size_bytes { + let tmp = self.alloc_pseudo(); + self.emit(Instruction::load(tmp, src, offset, self.types.ulong_id, 64)); + self.emit(Instruction::store( + tmp, + dst, + dst_base_offset + offset, + self.types.ulong_id, + 64, + )); + offset += 8; + } + let remaining = size_bytes - offset; + if remaining >= 4 { + let tmp = self.alloc_pseudo(); + self.emit(Instruction::load(tmp, src, offset, self.types.uint_id, 32)); + self.emit(Instruction::store( + tmp, + dst, + dst_base_offset + offset, + self.types.uint_id, + 32, + )); + offset += 4; + } + if remaining % 4 >= 2 { + let tmp = self.alloc_pseudo(); + self.emit(Instruction::load( + tmp, + src, + offset, + self.types.ushort_id, + 16, + )); + self.emit(Instruction::store( + tmp, + dst, + dst_base_offset + offset, + self.types.ushort_id, + 16, + )); + offset += 2; + } + if remaining % 2 == 1 { + let tmp = self.alloc_pseudo(); + self.emit(Instruction::load(tmp, src, offset, self.types.uchar_id, 8)); + self.emit(Instruction::store( + tmp, + dst, + dst_base_offset + offset, + self.types.uchar_id, + 8, + )); + } + } + + /// Emit code to load a bitfield value + /// Returns the loaded value as a PseudoId + pub(crate) fn emit_bitfield_load( + &mut self, + base: PseudoId, + byte_offset: usize, + bit_offset: u32, + bit_width: u32, + storage_size: u32, + typ: TypeId, + ) -> PseudoId { + // Determine storage type based on storage unit size + let storage_type = self.bitfield_storage_type(storage_size); + let storage_bits = storage_size * 8; + + // 1. Load the entire storage unit + let storage_val = self.alloc_pseudo(); + self.emit(Instruction::load( + storage_val, + base, + byte_offset as i64, + storage_type, + storage_bits, + )); + + // 2. Shift right by bit_offset (using logical shift for unsigned extraction) + let shifted = if bit_offset > 0 { + let shift_amount = self.emit_const(bit_offset as i128, self.types.int_id); + let shifted = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Lsr, + shifted, + storage_val, + shift_amount, + storage_type, + storage_bits, + )); + shifted + } else { + storage_val + }; + + // 3. Mask to bit_width bits + let mask = (1u64 << bit_width) - 1; + let mask_val = self.emit_const(mask as i128, storage_type); + let masked = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::And, + masked, + shifted, + mask_val, + storage_type, + storage_bits, + )); + + // 4. Sign extend if this is a signed bitfield + if !self.types.is_unsigned(typ) && bit_width < storage_bits { + self.emit_sign_extend_bitfield(masked, bit_width, storage_bits) + } else { + masked + } + } + + /// Sign-extend a bitfield value from bit_width to target_bits + pub(crate) fn emit_sign_extend_bitfield( + &mut self, + value: PseudoId, + bit_width: u32, + target_bits: u32, + ) -> PseudoId { + // Sign extend by shifting left then arithmetic shifting right + let shift_amount = target_bits - bit_width; + let typ = if target_bits <= 32 { + self.types.int_id + } else { + self.types.long_id + }; + + let shift_val = self.emit_const(shift_amount as i128, typ); + let shifted_left = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Shl, + shifted_left, + value, + shift_val, + typ, + target_bits, + )); + + let result = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Asr, + result, + shifted_left, + shift_val, + typ, + target_bits, + )); + result + } + + /// Emit code to store a value into a bitfield + pub(crate) fn emit_bitfield_store( + &mut self, + base: PseudoId, + byte_offset: usize, + bit_offset: u32, + bit_width: u32, + storage_size: u32, + new_value: PseudoId, + ) { + // Determine storage type based on storage unit size + let storage_type = self.bitfield_storage_type(storage_size); + let storage_bits = storage_size * 8; + + // 1. Load current storage unit value + let old_val = self.alloc_pseudo(); + self.emit(Instruction::load( + old_val, + base, + byte_offset as i64, + storage_type, + storage_bits, + )); + + // 2. Create mask for the bitfield bits: ~(((1 << width) - 1) << offset) + let field_mask = ((1u64 << bit_width) - 1) << bit_offset; + let clear_mask = !field_mask; + let clear_mask_val = self.emit_const(clear_mask as i128, storage_type); + + // 3. Clear the bitfield bits in old value + let cleared = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::And, + cleared, + old_val, + clear_mask_val, + storage_type, + storage_bits, + )); + + // 4. Mask new value to bit_width and shift to position + let value_mask = (1u64 << bit_width) - 1; + let value_mask_val = self.emit_const(value_mask as i128, storage_type); + let masked_new = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::And, + masked_new, + new_value, + value_mask_val, + storage_type, + storage_bits, + )); + + let positioned = if bit_offset > 0 { + let shift_val = self.emit_const(bit_offset as i128, self.types.int_id); + let positioned = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Shl, + positioned, + masked_new, + shift_val, + storage_type, + storage_bits, + )); + positioned + } else { + masked_new + }; + + // 5. OR cleared value with positioned new value + let combined = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Or, + combined, + cleared, + positioned, + storage_type, + storage_bits, + )); + + // 6. Store back + self.emit(Instruction::store( + combined, + base, + byte_offset as i64, + storage_type, + storage_bits, + )); + } + + pub(crate) fn emit_unary(&mut self, op: UnaryOp, src: PseudoId, typ: TypeId) -> PseudoId { + let is_float = self.types.is_float(typ); + let size = self.types.size_bits(typ); + + let result = self.alloc_pseudo(); + + let opcode = match op { + UnaryOp::Neg => { + if is_float { + Opcode::FNeg + } else { + Opcode::Neg + } + } + UnaryOp::Not => { + // Logical not: compare with 0 + if is_float { + let zero = self.emit_fconst(0.0, typ); + self.emit(Instruction::binop( + Opcode::FCmpOEq, + result, + src, + zero, + typ, + size, + )); + } else { + let zero = self.emit_const(0, typ); + self.emit(Instruction::binop( + Opcode::SetEq, + result, + src, + zero, + typ, + size, + )); + } + return result; + } + UnaryOp::BitNot => Opcode::Not, + UnaryOp::AddrOf => { + return src; + } + UnaryOp::Deref => { + // Dereferencing a pointer-to-array gives an array, which is just an address + // (arrays decay to their first element's address) + let type_kind = self.types.kind(typ); + if type_kind == TypeKind::Array { + return src; + } + // In C, dereferencing a function pointer is a no-op: + // *func_ptr == func_ptr (C99 6.5.3.2, 6.3.2.1) + if type_kind == TypeKind::Function { + return src; + } + // For struct types, always return the address — struct member + // access requires an address for offset-based field access. + if type_kind == TypeKind::Struct { + return src; + } + // For large union types (> 64 bits), return the address. + // For small unions (<= 64 bits), LOAD the value — unions are + // accessed as whole values, not via member offsets, and + // returning the pointer causes callers to store the pointer + // instead of the union value (Bug L). + if type_kind == TypeKind::Union && size > 64 { + return src; + } + self.emit(Instruction::load(result, src, 0, typ, size)); + return result; + } + UnaryOp::PreInc => { + let is_ptr = self.types.kind(typ) == TypeKind::Pointer; + let increment = if is_ptr { + let elem_type = self.types.base_type(typ).unwrap_or(self.types.char_id); + let elem_size = self.types.size_bits(elem_type) / 8; + self.emit_const(elem_size as i128, self.types.long_id) + } else if is_float { + self.emit_fconst(1.0, typ) + } else { + self.emit_const(1, typ) + }; + let opcode = if is_float { Opcode::FAdd } else { Opcode::Add }; + self.emit(Instruction::binop( + opcode, result, src, increment, typ, size, + )); + return result; + } + UnaryOp::PreDec => { + let is_ptr = self.types.kind(typ) == TypeKind::Pointer; + let decrement = if is_ptr { + let elem_type = self.types.base_type(typ).unwrap_or(self.types.char_id); + let elem_size = self.types.size_bits(elem_type) / 8; + self.emit_const(elem_size as i128, self.types.long_id) + } else if is_float { + self.emit_fconst(1.0, typ) + } else { + self.emit_const(1, typ) + }; + let opcode = if is_float { Opcode::FSub } else { Opcode::Sub }; + self.emit(Instruction::binop( + opcode, result, src, decrement, typ, size, + )); + return result; + } + }; + + self.emit(Instruction::unop(opcode, result, src, typ, size)); + result + } + + pub(crate) fn emit_binary( + &mut self, + op: BinaryOp, + left: PseudoId, + right: PseudoId, + result_typ: TypeId, + operand_typ: TypeId, + ) -> PseudoId { + let is_float = self.types.is_float(operand_typ); + let is_unsigned = self.types.is_unsigned(operand_typ); + + let result = self.alloc_pseudo(); + + let opcode = match op { + BinaryOp::Add => { + if is_float { + Opcode::FAdd + } else { + Opcode::Add + } + } + BinaryOp::Sub => { + if is_float { + Opcode::FSub + } else { + Opcode::Sub + } + } + BinaryOp::Mul => { + if is_float { + Opcode::FMul + } else { + Opcode::Mul + } + } + BinaryOp::Div => { + if is_float { + Opcode::FDiv + } else if is_unsigned { + Opcode::DivU + } else { + Opcode::DivS + } + } + BinaryOp::Mod => { + // Modulo is not supported for floats in hardware - use fmod() library call + // For now, use integer modulo (semantic analysis should catch float % float) + if is_unsigned { + Opcode::ModU + } else { + Opcode::ModS + } + } + BinaryOp::Lt => { + if is_float { + Opcode::FCmpOLt + } else if is_unsigned { + Opcode::SetB + } else { + Opcode::SetLt + } + } + BinaryOp::Gt => { + if is_float { + Opcode::FCmpOGt + } else if is_unsigned { + Opcode::SetA + } else { + Opcode::SetGt + } + } + BinaryOp::Le => { + if is_float { + Opcode::FCmpOLe + } else if is_unsigned { + Opcode::SetBe + } else { + Opcode::SetLe + } + } + BinaryOp::Ge => { + if is_float { + Opcode::FCmpOGe + } else if is_unsigned { + Opcode::SetAe + } else { + Opcode::SetGe + } + } + BinaryOp::Eq => { + if is_float { + Opcode::FCmpOEq + } else { + Opcode::SetEq + } + } + BinaryOp::Ne => { + if is_float { + Opcode::FCmpONe + } else { + Opcode::SetNe + } + } + // LogAnd and LogOr are handled earlier in linearize_expr via + // emit_logical_and/emit_logical_or for proper short-circuit evaluation + BinaryOp::LogAnd | BinaryOp::LogOr => { + unreachable!("LogAnd/LogOr should be handled in ExprKind::Binary") + } + BinaryOp::BitAnd => Opcode::And, + BinaryOp::BitOr => Opcode::Or, + BinaryOp::BitXor => Opcode::Xor, + BinaryOp::Shl => Opcode::Shl, + BinaryOp::Shr => { + // Logical shift for unsigned, arithmetic for signed + if is_unsigned { + Opcode::Lsr + } else { + Opcode::Asr + } + } + }; + + // For comparison operations, use operand_typ to ensure correct size + // (comparisons produce int result but must operate at operand size) + let insn_typ = match opcode { + Opcode::SetEq + | Opcode::SetNe + | Opcode::SetLt + | Opcode::SetLe + | Opcode::SetGt + | Opcode::SetGe + | Opcode::SetB + | Opcode::SetBe + | Opcode::SetA + | Opcode::SetAe + | Opcode::FCmpOEq + | Opcode::FCmpONe + | Opcode::FCmpOLt + | Opcode::FCmpOLe + | Opcode::FCmpOGt + | Opcode::FCmpOGe => operand_typ, + _ => result_typ, + }; + let insn_size = self.types.size_bits(insn_typ); + self.emit(Instruction::binop( + opcode, result, left, right, insn_typ, insn_size, + )); + result + } + + /// Emit complex arithmetic operation + /// Complex values are stored as two adjacent float/double values (real, imag) + /// This function expands complex ops to operations on the component parts + pub(crate) fn emit_complex_binary( + &mut self, + op: BinaryOp, + left_addr: PseudoId, + right_addr: PseudoId, + complex_typ: TypeId, + ) -> PseudoId { + // Get the base float type (float, double, or long double) + let base_typ = self.types.complex_base(complex_typ); + let base_size = self.types.size_bits(base_typ); + let base_bytes = (base_size / 8) as i64; + + // Allocate result temporary (stack space for the complex result) + let result_addr = self.alloc_local_temp(complex_typ); + + // Load real and imaginary parts of left operand + let left_real = self.alloc_pseudo(); + self.emit(Instruction::load( + left_real, left_addr, 0, base_typ, base_size, + )); + + let left_imag = self.alloc_pseudo(); + self.emit(Instruction::load( + left_imag, left_addr, base_bytes, base_typ, base_size, + )); + + // Load real and imaginary parts of right operand + let right_real = self.alloc_pseudo(); + self.emit(Instruction::load( + right_real, right_addr, 0, base_typ, base_size, + )); + + let right_imag = self.alloc_pseudo(); + self.emit(Instruction::load( + right_imag, right_addr, base_bytes, base_typ, base_size, + )); + + // Perform the operation on the components + let (result_real, result_imag) = match op { + BinaryOp::Add => { + // (a + bi) + (c + di) = (a+c) + (b+d)i + let real = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::FAdd, + real, + left_real, + right_real, + base_typ, + base_size, + )); + let imag = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::FAdd, + imag, + left_imag, + right_imag, + base_typ, + base_size, + )); + (real, imag) + } + BinaryOp::Sub => { + // (a + bi) - (c + di) = (a-c) + (b-d)i + let real = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::FSub, + real, + left_real, + right_real, + base_typ, + base_size, + )); + let imag = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::FSub, + imag, + left_imag, + right_imag, + base_typ, + base_size, + )); + (real, imag) + } + BinaryOp::Mul => { + // Complex multiply via rtlib call (__mulsc3, __muldc3, etc.) + let base_kind = self.types.kind(base_typ); + let func_name = crate::arch::mapping::complex_mul_name(base_kind, self.target); + let call_result = self.emit_complex_rtlib_call( + func_name, + (left_real, left_imag), + (right_real, right_imag), + base_typ, + complex_typ, + ); + // Load real/imag from the call result + let real = self.alloc_pseudo(); + self.emit(Instruction::load(real, call_result, 0, base_typ, base_size)); + let imag = self.alloc_pseudo(); + self.emit(Instruction::load( + imag, + call_result, + base_bytes, + base_typ, + base_size, + )); + (real, imag) + } + BinaryOp::Div => { + // Complex divide via rtlib call (__divsc3, __divdc3, etc.) + let base_kind = self.types.kind(base_typ); + let func_name = crate::arch::mapping::complex_div_name(base_kind, self.target); + let call_result = self.emit_complex_rtlib_call( + func_name, + (left_real, left_imag), + (right_real, right_imag), + base_typ, + complex_typ, + ); + // Load real/imag from the call result + let real = self.alloc_pseudo(); + self.emit(Instruction::load(real, call_result, 0, base_typ, base_size)); + let imag = self.alloc_pseudo(); + self.emit(Instruction::load( + imag, + call_result, + base_bytes, + base_typ, + base_size, + )); + (real, imag) + } + _ => { + // Other operations not supported for complex types + error( + Position::default(), + &format!("unsupported operation {:?} on complex types", op), + ); + return result_addr; + } + }; + + // Store result components + self.emit(Instruction::store( + result_real, + result_addr, + 0, + base_typ, + base_size, + )); + self.emit(Instruction::store( + result_imag, + result_addr, + base_bytes, + base_typ, + base_size, + )); + + result_addr + } + + /// Allocate a local temporary variable for a complex result + pub(crate) fn alloc_local_temp(&mut self, typ: TypeId) -> PseudoId { + let size = self.types.size_bytes(typ); + let size_const = self.emit_const(size as i128, self.types.ulong_id); + let addr = self.alloc_pseudo(); + let alloca_insn = Instruction::new(Opcode::Alloca) + .with_target(addr) + .with_src(size_const) + .with_type_and_size(self.types.void_ptr_id, 64); + self.emit(alloca_insn); + addr + } + + /// Emit a call to a complex rtlib function (__mulXc3, __divXc3). + /// + /// These functions take 4 scalar args (left_real, left_imag, right_real, right_imag) + /// and return a complex value. The result is stored in newly allocated local storage. + /// + /// Returns the address where the complex result is stored. + pub(crate) fn emit_complex_rtlib_call( + &mut self, + func_name: &str, + left: (PseudoId, PseudoId), + right: (PseudoId, PseudoId), + base_typ: TypeId, + complex_typ: TypeId, + ) -> PseudoId { + let (left_real, left_imag) = left; + let (right_real, right_imag) = right; + // Allocate local storage for the complex result + let result_sym = self.alloc_pseudo(); + let unique_name = format!("__cret_{}", result_sym.0); + let result_pseudo = Pseudo::sym(result_sym, unique_name.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(result_pseudo); + func.add_local( + &unique_name, + result_sym, + complex_typ, + false, // not volatile + false, // not atomic + self.current_bb, + None, // no explicit alignment + ); + } + + // Build argument list: 4 scalar FP values + let arg_vals = vec![left_real, left_imag, right_real, right_imag]; + let arg_types = vec![base_typ, base_typ, base_typ, base_typ]; + + // Compute ABI classification for the call + let abi = get_abi_for_conv(self.current_calling_conv, self.target); + let param_classes: Vec<_> = arg_types + .iter() + .map(|&t| abi.classify_param(t, self.types)) + .collect(); + let ret_class = abi.classify_return(complex_typ, self.types); + let call_abi_info = Box::new(CallAbiInfo::new(param_classes, ret_class)); + + // Create the call instruction + let ret_size = self.types.size_bits(complex_typ); + let mut call_insn = Instruction::call( + Some(result_sym), + func_name, + arg_vals, + arg_types, + complex_typ, + ret_size, + ); + call_insn.abi_info = Some(call_abi_info); + self.emit(call_insn); + + result_sym + } + + pub(crate) fn emit_compare_zero(&mut self, val: PseudoId, operand_typ: TypeId) -> PseudoId { + let result = self.alloc_pseudo(); + let zero = self.emit_const(0, operand_typ); + let size = self.types.size_bits(operand_typ); + self.emit(Instruction::binop( + Opcode::SetNe, + result, + val, + zero, + operand_typ, + size, + )); + result + } + + /// Emit short-circuit logical AND: a && b + /// If a is false, skip evaluation of b and return 0. + /// Otherwise, evaluate b and return (b != 0). + pub(crate) fn emit_logical_and(&mut self, left: &Expr, right: &Expr) -> PseudoId { + let result_typ = self.types.int_id; + + // Create basic blocks + let eval_b_bb = self.alloc_bb(); + let merge_bb = self.alloc_bb(); + + // Evaluate LHS + let left_typ = self.expr_type(left); + let left_val = self.linearize_expr(left); + let left_bool = self.emit_compare_zero(left_val, left_typ); + + // Emit the short-circuit value (0) BEFORE the branch, while still in LHS block + // This value will be used if we short-circuit (LHS is false) + let zero = self.emit_const(0, result_typ); + + // Get the block where LHS evaluation ended (may differ from initial block + // if LHS contains nested control flow) + let lhs_end_bb = self.current_bb.unwrap(); + + // Branch: if LHS is false, go to merge (result = 0); else evaluate RHS + self.emit(Instruction::cbr(left_bool, eval_b_bb, merge_bb)); + self.link_bb(lhs_end_bb, eval_b_bb); + self.link_bb(lhs_end_bb, merge_bb); + + // eval_b_bb: Evaluate RHS + self.switch_bb(eval_b_bb); + let right_typ = self.expr_type(right); + let right_val = self.linearize_expr(right); + let right_bool = self.emit_compare_zero(right_val, right_typ); + + // Get the actual block where RHS evaluation ended (may differ from eval_b_bb + // if RHS contains nested control flow like another &&/||) + let rhs_end_bb = self.current_bb.unwrap(); + + // Branch to merge + self.emit(Instruction::br(merge_bb)); + self.link_bb(rhs_end_bb, merge_bb); + + // merge_bb: Create phi node to merge results + self.switch_bb(merge_bb); + + // Result is 0 if we came from lhs_end_bb (LHS was false), + // or right_bool if we came from rhs_end_bb (LHS was true) + let result = self.alloc_pseudo(); + // Create a phi pseudo for the target - important for register allocation + let phi_pseudo = Pseudo::phi(result, result.0); + if let Some(func) = &mut self.current_func { + func.add_pseudo(phi_pseudo); + } + let mut phi_insn = Instruction::phi(result, result_typ, 32); + let phisrc1 = self.emit_phi_source(lhs_end_bb, zero, result, merge_bb, result_typ, 32); + phi_insn.phi_list.push((lhs_end_bb, phisrc1)); + let phisrc2 = + self.emit_phi_source(rhs_end_bb, right_bool, result, merge_bb, result_typ, 32); + phi_insn.phi_list.push((rhs_end_bb, phisrc2)); + self.emit(phi_insn); + + result + } + + /// Emit short-circuit logical OR: a || b + /// If a is true, skip evaluation of b and return 1. + /// Otherwise, evaluate b and return (b != 0). + pub(crate) fn emit_logical_or(&mut self, left: &Expr, right: &Expr) -> PseudoId { + let result_typ = self.types.int_id; + + // Create basic blocks + let eval_b_bb = self.alloc_bb(); + let merge_bb = self.alloc_bb(); + + // Evaluate LHS + let left_typ = self.expr_type(left); + let left_val = self.linearize_expr(left); + let left_bool = self.emit_compare_zero(left_val, left_typ); + + // Emit the short-circuit value (1) BEFORE the branch, while still in LHS block + // This value will be used if we short-circuit (LHS is true) + let one = self.emit_const(1, result_typ); + + // Get the block where LHS evaluation ended (may differ from initial block + // if LHS contains nested control flow) + let lhs_end_bb = self.current_bb.unwrap(); + + // Branch: if LHS is true, go to merge (result = 1); else evaluate RHS + self.emit(Instruction::cbr(left_bool, merge_bb, eval_b_bb)); + self.link_bb(lhs_end_bb, merge_bb); + self.link_bb(lhs_end_bb, eval_b_bb); + + // eval_b_bb: Evaluate RHS + self.switch_bb(eval_b_bb); + let right_typ = self.expr_type(right); + let right_val = self.linearize_expr(right); + let right_bool = self.emit_compare_zero(right_val, right_typ); + + // Get the actual block where RHS evaluation ended (may differ from eval_b_bb + // if RHS contains nested control flow like another &&/||) + let rhs_end_bb = self.current_bb.unwrap(); + + // Branch to merge + self.emit(Instruction::br(merge_bb)); + self.link_bb(rhs_end_bb, merge_bb); + + // merge_bb: Create phi node to merge results + self.switch_bb(merge_bb); + + // Result is 1 if we came from lhs_end_bb (LHS was true), + // or right_bool if we came from rhs_end_bb (LHS was false) + let result = self.alloc_pseudo(); + // Create a phi pseudo for the target - important for register allocation + let phi_pseudo = Pseudo::phi(result, result.0); + if let Some(func) = &mut self.current_func { + func.add_pseudo(phi_pseudo); + } + let mut phi_insn = Instruction::phi(result, result_typ, 32); + let phisrc1 = self.emit_phi_source(lhs_end_bb, one, result, merge_bb, result_typ, 32); + phi_insn.phi_list.push((lhs_end_bb, phisrc1)); + let phisrc2 = + self.emit_phi_source(rhs_end_bb, right_bool, result, merge_bb, result_typ, 32); + phi_insn.phi_list.push((rhs_end_bb, phisrc2)); + self.emit(phi_insn); + + result + } + + pub(crate) fn emit_assign(&mut self, op: AssignOp, target: &Expr, value: &Expr) -> PseudoId { + let target_typ = self.expr_type(target); + let value_typ = self.expr_type(value); + + // For complex type assignment, handle specially - copy real and imag parts + if self.types.is_complex(target_typ) && op == AssignOp::Assign { + let target_addr = self.linearize_lvalue(target); + let value_addr = self.linearize_lvalue(value); + let base_typ = self.types.complex_base(target_typ); + let base_size = self.types.size_bits(base_typ); + let base_bytes = (base_size / 8) as i64; + + // Load and store real part + let real = self.alloc_pseudo(); + self.emit(Instruction::load(real, value_addr, 0, base_typ, base_size)); + self.emit(Instruction::store( + real, + target_addr, + 0, + base_typ, + base_size, + )); + + // Load and store imaginary part + let imag = self.alloc_pseudo(); + self.emit(Instruction::load( + imag, value_addr, base_bytes, base_typ, base_size, + )); + self.emit(Instruction::store( + imag, + target_addr, + base_bytes, + base_typ, + base_size, + )); + + return real; // Return real part as the result value + } + + // For struct/union assignment, do a block copy via addresses. + // Structs are not loaded into registers by linearize_expr — they return + // an address. So we must handle ALL struct sizes here, not just large ones. + let target_kind = self.types.kind(target_typ); + let target_size = self.types.size_bits(target_typ); + if (target_kind == TypeKind::Struct || target_kind == TypeKind::Union) + && target_size > 0 + && op == AssignOp::Assign + { + let target_addr = self.linearize_lvalue(target); + let value_addr = self.linearize_lvalue(value); + let target_size_bytes = target_size / 8; + + self.emit_block_copy(target_addr, value_addr, target_size_bytes as i64); + + // Return the target address as the result + return target_addr; + } + + let rhs = self.linearize_expr(value); + + // Check for pointer compound assignment (p += n or p -= n) + let is_ptr_arith = self.types.kind(target_typ) == TypeKind::Pointer + && self.types.is_integer(value_typ) + && (op == AssignOp::AddAssign || op == AssignOp::SubAssign); + + // Convert RHS to target type if needed (but not for pointer arithmetic) + let rhs = if is_ptr_arith { + // For pointer arithmetic, scale the integer by element size + let elem_type = self + .types + .base_type(target_typ) + .unwrap_or(self.types.char_id); + let elem_size = self.types.size_bits(elem_type) / 8; + let scale = self.emit_const(elem_size as i128, self.types.long_id); + + // Extend the integer to 64-bit for proper arithmetic + let rhs_extended = self.emit_convert(rhs, value_typ, self.types.long_id); + + let scaled = self.alloc_reg_pseudo(); + self.emit(Instruction::binop( + Opcode::Mul, + scaled, + rhs_extended, + scale, + self.types.long_id, + 64, + )); + scaled + } else { + self.emit_convert(rhs, value_typ, target_typ) + }; + + let final_val = match op { + AssignOp::Assign => rhs, + _ => { + // Compound assignment - get current value and apply operation + let lhs = self.linearize_expr(target); + let result = self.alloc_reg_pseudo(); + + let is_float = self.types.is_float(target_typ); + let is_unsigned = self.types.is_unsigned(target_typ); + let opcode = match op { + AssignOp::AddAssign => { + if is_float { + Opcode::FAdd + } else { + Opcode::Add + } + } + AssignOp::SubAssign => { + if is_float { + Opcode::FSub + } else { + Opcode::Sub + } + } + AssignOp::MulAssign => { + if is_float { + Opcode::FMul + } else { + Opcode::Mul + } + } + AssignOp::DivAssign => { + if is_float { + Opcode::FDiv + } else if is_unsigned { + Opcode::DivU + } else { + Opcode::DivS + } + } + AssignOp::ModAssign => { + // Modulo not supported for floats + if is_unsigned { + Opcode::ModU + } else { + Opcode::ModS + } + } + AssignOp::AndAssign => Opcode::And, + AssignOp::OrAssign => Opcode::Or, + AssignOp::XorAssign => Opcode::Xor, + AssignOp::ShlAssign => Opcode::Shl, + AssignOp::ShrAssign => { + if is_unsigned { + Opcode::Lsr + } else { + Opcode::Asr + } + } + AssignOp::Assign => unreachable!(), + }; + + // For pointer arithmetic, use Long type for the operation + let arith_type = if is_ptr_arith { + self.types.long_id + } else { + target_typ + }; + + let arith_size = self.types.size_bits(arith_type); + self.emit(Instruction::binop( + opcode, result, lhs, rhs, arith_type, arith_size, + )); + result + } + }; + + // Store based on target expression type + let target_size = self.types.size_bits(target_typ); + match &target.kind { + ExprKind::Ident(symbol_id) => { + let name_str = self.symbol_name(*symbol_id); + if let Some(local) = self.locals.get(symbol_id).cloned() { + // Check if this is a static local (sentinel value) + if local.sym.0 == u32::MAX { + self.emit_static_local_store(&name_str, final_val, target_typ, target_size); + } else { + // Regular local variable: emit Store + self.emit(Instruction::store( + final_val, + local.sym, + 0, + target_typ, + target_size, + )); + } + } else if self.var_map.contains_key(&name_str) { + // Parameter: this is not SSA-correct but parameters + // shouldn't be reassigned. If they are, we'd need to + // demote them to locals. For now, just update the mapping. + self.var_map.insert(name_str.clone(), final_val); + } else { + // Global variable - emit store + let sym_id = self.alloc_pseudo(); + let pseudo = Pseudo::sym(sym_id, name_str); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); + } + self.emit(Instruction::store( + final_val, + sym_id, + 0, + target_typ, + target_size, + )); + } + } + ExprKind::Member { expr, member } => { + // Struct member: get address and store with offset + let base = self.linearize_lvalue(expr); + let base_struct_type = self.expr_type(expr); + // Resolve if the struct type is incomplete (forward-declared) + let struct_type = self.resolve_struct_type(base_struct_type); + let member_info = + self.types + .find_member(struct_type, *member) + .unwrap_or(MemberInfo { + offset: 0, + typ: target_typ, + bit_offset: None, + bit_width: None, + storage_unit_size: None, + }); + if let (Some(bit_offset), Some(bit_width), Some(storage_size)) = ( + member_info.bit_offset, + member_info.bit_width, + member_info.storage_unit_size, + ) { + // Bitfield store + self.emit_bitfield_store( + base, + member_info.offset, + bit_offset, + bit_width, + storage_size, + final_val, + ); + } else { + let member_size = self.types.size_bits(member_info.typ); + self.emit(Instruction::store( + final_val, + base, + member_info.offset as i64, + member_info.typ, + member_size, + )); + } + } + ExprKind::Arrow { expr, member } => { + // Pointer member: pointer value is the base address + let ptr = self.linearize_expr(expr); + let ptr_type = self.expr_type(expr); + let base_struct_type = self.types.base_type(ptr_type).unwrap_or(target_typ); + // Resolve if the struct type is incomplete (forward-declared) + let struct_type = self.resolve_struct_type(base_struct_type); + let member_info = + self.types + .find_member(struct_type, *member) + .unwrap_or(MemberInfo { + offset: 0, + typ: target_typ, + bit_offset: None, + bit_width: None, + storage_unit_size: None, + }); + if let (Some(bit_offset), Some(bit_width), Some(storage_size)) = ( + member_info.bit_offset, + member_info.bit_width, + member_info.storage_unit_size, + ) { + // Bitfield store + self.emit_bitfield_store( + ptr, + member_info.offset, + bit_offset, + bit_width, + storage_size, + final_val, + ); + } else { + let member_size = self.types.size_bits(member_info.typ); + self.emit(Instruction::store( + final_val, + ptr, + member_info.offset as i64, + member_info.typ, + member_size, + )); + } + } + ExprKind::Unary { + op: UnaryOp::Deref, + operand, + } => { + // Dereference: store to the pointer address + let ptr = self.linearize_expr(operand); + self.emit(Instruction::store( + final_val, + ptr, + 0, + target_typ, + target_size, + )); + } + ExprKind::Index { array, index } => { + // Array subscript: compute address and store + // Handle commutative form: 0[arr] is equivalent to arr[0] + let array_type = self.expr_type(array); + let index_type = self.expr_type(index); + + let array_kind = self.types.kind(array_type); + let (ptr_expr, idx_expr, idx_type) = + if array_kind == TypeKind::Pointer || array_kind == TypeKind::Array { + (array, index, index_type) + } else { + // Swap: index is actually the pointer/array + (index, array, array_type) + }; + + let arr = self.linearize_expr(ptr_expr); + let idx = self.linearize_expr(idx_expr); + let elem_size = target_size / 8; + let elem_size_val = self.emit_const(elem_size as i128, self.types.long_id); + + // Sign-extend index to 64-bit for proper pointer arithmetic (negative indices) + let idx_extended = self.emit_convert(idx, idx_type, self.types.long_id); + + let offset = self.alloc_pseudo(); + let ptr_typ = self.types.long_id; + self.emit(Instruction::binop( + Opcode::Mul, + offset, + idx_extended, + elem_size_val, + ptr_typ, + 64, + )); + + let addr = self.alloc_pseudo(); + self.emit(Instruction::binop( + Opcode::Add, + addr, + arr, + offset, + ptr_typ, + 64, + )); + + self.emit(Instruction::store( + final_val, + addr, + 0, + target_typ, + target_size, + )); + } + _ => { + // Other lvalues - should not happen for valid C code + } + } + + final_val + } +} diff --git a/cc/ir/linearize_init.rs b/cc/ir/linearize_init.rs new file mode 100644 index 00000000..d52ef9cd --- /dev/null +++ b/cc/ir/linearize_init.rs @@ -0,0 +1,1101 @@ +// +// Copyright (c) 2025-2026 Jeff Garzik +// +// This file is part of the posixutils-rs project covered under +// the MIT License. For the full license text, please see the LICENSE +// file in the root directory of this project. +// SPDX-License-Identifier: MIT + +//! Initializer and global declaration linearization + +use super::linearize::*; +use super::Initializer; +use crate::diag::error; +use crate::parse::ast::{BinaryOp, Declaration, Designator, Expr, ExprKind, InitElement, UnaryOp}; +use crate::strings::StringId; +use crate::types::{MemberInfo, TypeId, TypeKind, TypeModifiers}; +use std::collections::HashMap; + +impl<'a> super::linearize::Linearizer<'a> { + // ======================================================================== + // Global declarations + // ======================================================================== + + pub(crate) fn linearize_global_decl(&mut self, decl: &Declaration) { + for declarator in &decl.declarators { + // Use storage_class from declarator (extern, static, _Thread_local, etc.) + // These are NOT stored in the type system + let storage_class = declarator.storage_class; + let name = self.symbol_name(declarator.symbol); + + // Skip typedef declarations - they don't define storage + if storage_class.contains(TypeModifiers::TYPEDEF) { + continue; + } + + // Function declarations without bodies are external functions + // Track them in extern_symbols so codegen uses GOT access + if self.types.kind(declarator.typ) == TypeKind::Function { + // Check if not defined in this module (forward refs will be cleaned up later) + if !self.module.functions.iter().any(|f| f.name == name) { + self.module.extern_symbols.insert(name); + } + continue; + } + + // Skip extern declarations - they don't define storage + // But track them so codegen can use GOT access on macOS + // Only add to extern_symbols if not already defined (handles both cases: + // extern int x; int x = 1; - x is defined, not extern + // int x = 1; extern int x; - x is defined, not extern) + if storage_class.contains(TypeModifiers::EXTERN) { + // Check if this symbol is already defined in globals + if !self.module.globals.iter().any(|g| g.name == name) { + self.module.extern_symbols.insert(name.clone()); + // Track extern thread-local symbols separately for TLS access + if storage_class.contains(TypeModifiers::THREAD_LOCAL) { + self.module.extern_tls_symbols.insert(name); + } + } + continue; + } + + let init = declarator.init.as_ref().map_or(Initializer::None, |e| { + self.ast_init_to_ir(e, declarator.typ) + }); + + // Track file-scope static variables for inline semantic checks + if storage_class.contains(TypeModifiers::STATIC) { + self.file_scope_statics.insert(name.clone()); + } + + // If this symbol was previously declared extern, remove it from extern_symbols + // (we now have the actual definition) + self.module.extern_symbols.remove(&name); + + // Check for thread-local storage + let is_static = storage_class.contains(TypeModifiers::STATIC); + if storage_class.contains(TypeModifiers::THREAD_LOCAL) { + self.module.add_global_tls_aligned( + &name, + declarator.typ, + init, + declarator.explicit_align, + is_static, + ); + } else { + self.module.add_global_aligned( + &name, + declarator.typ, + init, + declarator.explicit_align, + is_static, + ); + } + } + } + + /// Convert an AST initializer expression to an IR Initializer + /// + /// This handles: + /// - Scalar initializers (int, float, char literals) + /// - String literals (for char arrays or char pointers) + /// - Array initializers with designated and positional elements + /// - Struct initializers with designated and positional fields + /// - Address-of expressions (&symbol) + /// - Nested initializers + /// - Compound literals (C99 6.5.2.5) + pub(crate) fn ast_init_to_ir(&mut self, expr: &Expr, typ: TypeId) -> Initializer { + match &expr.kind { + ExprKind::IntLit(v) => Initializer::Int(*v as i128), + ExprKind::Int128Lit(v) => Initializer::Int(*v), + ExprKind::FloatLit(v) => Initializer::Float(*v), + ExprKind::CharLit(c) => Initializer::Int(*c as u8 as i8 as i128), + + // String literal - for arrays, store as String; for pointers, create label reference + ExprKind::StringLit(s) => { + let type_kind = self.types.kind(typ); + if type_kind == TypeKind::Array { + // char array - embed the string directly + Initializer::String(s.clone()) + } else { + // Pointer - create a string constant and reference it + let label = format!(".LC{}", self.module.strings.len()); + self.module.strings.push((label.clone(), s.clone())); + Initializer::SymAddr(label) + } + } + + // Wide string literal - for arrays, store as WideString; for pointers, create label reference + ExprKind::WideStringLit(s) => { + let type_kind = self.types.kind(typ); + if type_kind == TypeKind::Array { + // wchar_t array - embed the wide string directly + Initializer::WideString(s.clone()) + } else { + // Pointer - create a wide string constant and reference it + // Use .LWC prefix to avoid collision with regular .LC string labels + let label = format!(".LWC{}", self.module.wide_strings.len()); + self.module.wide_strings.push((label.clone(), s.clone())); + Initializer::SymAddr(label) + } + } + + // Negative literal (fast path for simple cases) + ExprKind::Unary { + op: UnaryOp::Neg, + operand, + } => match &operand.kind { + ExprKind::IntLit(v) => Initializer::Int(-(*v as i128)), + ExprKind::Int128Lit(v) => Initializer::Int(v.wrapping_neg()), + ExprKind::FloatLit(v) => Initializer::Float(-*v), + // For more complex expressions like -(1+2), try constant evaluation + _ => { + if let Some(val) = self.eval_const_expr(expr) { + Initializer::Int(val) + } else { + Initializer::None + } + } + }, + + // Address-of expression + ExprKind::Unary { + op: UnaryOp::AddrOf, + operand, + } => { + // Try to compute the address as symbol + offset + if let Some((name, offset)) = self.eval_static_address(operand) { + if offset == 0 { + Initializer::SymAddr(name) + } else { + Initializer::SymAddrOffset(name, offset) + } + } else { + Initializer::None + } + } + + // Cast expression - evaluate the inner expression + ExprKind::Cast { expr: inner, .. } => self.ast_init_to_ir(inner, typ), + + // Initializer list for arrays/structs + ExprKind::InitList { elements } => self.ast_init_list_to_ir(elements, typ), + + // Compound literal in initializer context (C99 6.5.2.5) + ExprKind::CompoundLiteral { + typ: cl_type, + elements, + } => { + // Check if compound literal type matches target type + if *cl_type == typ { + // Direct value - treat like InitList + self.ast_init_list_to_ir(elements, typ) + } else if self.types.kind(typ) == TypeKind::Pointer { + // Pointer initialization - create anonymous static global + // and return its address + let anon_name = format!(".CL{}", self.compound_literal_counter); + self.compound_literal_counter += 1; + + // Create the anonymous global + let init = self.ast_init_list_to_ir(elements, *cl_type); + self.module.add_global(&anon_name, *cl_type, init); + + // Return address of the anonymous global + Initializer::SymAddr(anon_name) + } else { + // Type mismatch - use the compound literal's own type + self.ast_init_list_to_ir(elements, *cl_type) + } + } + + // Identifier - for constant addresses (function pointers, array decay, etc.) + // or enum constants + ExprKind::Ident(symbol_id) => { + let type_kind = self.types.kind(typ); + // For pointer types, this is likely a function address or array decay + if type_kind == TypeKind::Pointer { + let name_str = self.symbol_name(*symbol_id); + // Check if this is a static local variable + // Static locals have mangled names like "func_name.var_name.N" + let key = format!("{}.{}", self.current_func_name, name_str); + if let Some(static_info) = self.static_locals.get(&key) { + Initializer::SymAddr(static_info.global_name.clone()) + } else { + Initializer::SymAddr(name_str) + } + } else { + // Check if it's an enum constant + let sym = self.symbols.get(*symbol_id); + if let Some(val) = sym.enum_value { + Initializer::Int(val as i128) + } else { + Initializer::None + } + } + } + + // Binary add/sub with pointer operand → SymAddrOffset + ExprKind::Binary { + op: op @ (BinaryOp::Add | BinaryOp::Sub), + left, + right, + } => { + // Try pointer/array + int or int + pointer/array → symbol address with offset + let is_ptr_or_array = + |t: TypeId| matches!(self.types.kind(t), TypeKind::Pointer | TypeKind::Array); + let (ptr_expr, int_expr, is_sub) = if left.typ.is_some_and(is_ptr_or_array) { + (left.as_ref(), right.as_ref(), *op == BinaryOp::Sub) + } else if right.typ.is_some_and(is_ptr_or_array) && *op == BinaryOp::Add { + (right.as_ref(), left.as_ref(), false) + } else { + // Neither operand is pointer — try as integer constant + if let Some(val) = self.eval_const_expr(expr) { + return Initializer::Int(val); + } + error( + self.current_pos.unwrap_or_default(), + &format!( + "unsupported expression in global initializer: {:?}", + expr.kind + ), + ); + return Initializer::None; + }; + + // Evaluate the pointer side as a static address + if let Some((name, base_off)) = self.eval_static_address(ptr_expr) { + // Evaluate the integer side as a constant + if let Some(int_val) = self.eval_const_expr(int_expr) { + // Get the pointee size for pointer arithmetic scaling + let pointee_size = ptr_expr + .typ + .and_then(|t| self.types.base_type(t)) + .map(|t| self.types.size_bytes(t) as i64) + .unwrap_or(1); + let byte_offset = if is_sub { + base_off - int_val as i64 * pointee_size + } else { + base_off + int_val as i64 * pointee_size + }; + if byte_offset == 0 { + Initializer::SymAddr(name) + } else { + Initializer::SymAddrOffset(name, byte_offset) + } + } else if let Some(val) = self.eval_const_expr(expr) { + Initializer::Int(val) + } else { + error( + self.current_pos.unwrap_or_default(), + "non-constant offset in pointer arithmetic initializer", + ); + Initializer::None + } + } else if let Some(val) = self.eval_const_expr(expr) { + Initializer::Int(val) + } else { + error( + self.current_pos.unwrap_or_default(), + "non-constant pointer expression in global initializer", + ); + Initializer::None + } + } + + // Compile-time ternary: cond ? then_expr : else_expr + // Used in CPython's _Py_LATIN1_CHR() macro for static initializers + ExprKind::Conditional { + cond, + then_expr, + else_expr, + } => { + if let Some(cond_val) = self.eval_const_expr(cond) { + if cond_val != 0 { + return self.ast_init_to_ir(then_expr, typ); + } else { + return self.ast_init_to_ir(else_expr, typ); + } + } + // If condition isn't constant, fall through to error + error( + self.current_pos.unwrap_or_default(), + &format!( + "non-constant condition in global initializer ternary: {:?}", + cond.kind + ), + ); + Initializer::None + } + + // Other constant expressions + // Try to evaluate as integer or float constant expression + _ => { + if let Some(val) = self.eval_const_expr(expr) { + Initializer::Int(val) + } else if let Some(val) = self.eval_const_float_expr(expr) { + Initializer::Float(val) + } else if let Some((name, offset)) = self.eval_static_address(expr) { + // Try as a static address (e.g., &global.field->subfield chains) + if offset != 0 { + Initializer::SymAddrOffset(name, offset) + } else { + Initializer::SymAddr(name) + } + } else { + // Hard error for non-empty expressions we can't evaluate + error( + self.current_pos.unwrap_or_default(), + &format!( + "unsupported expression in global initializer: {:?}", + expr.kind + ), + ); + Initializer::None + } + } + } + } + + /// Count the number of scalar fields needed to fill an aggregate type + /// (for brace elision per C99 6.7.8p17-20). + pub(crate) fn count_scalar_fields(&self, typ: TypeId) -> usize { + match self.types.kind(typ) { + TypeKind::Array => { + let elem_type = self.types.base_type(typ).unwrap_or(self.types.int_id); + let count = self.types.get(typ).array_size.unwrap_or(0); + count * self.count_scalar_fields(elem_type) + } + TypeKind::Struct => { + if let Some(composite) = self.types.get(typ).composite.as_ref() { + composite + .members + .iter() + // Skip unnamed bitfield padding + .filter(|m| m.name != StringId::EMPTY || m.bit_width.is_none()) + .map(|m| self.count_scalar_fields(m.typ)) + .sum() + } else { + 1 + } + } + TypeKind::Union => { + // Union only initializes first named member + if let Some(composite) = self.types.get(typ).composite.as_ref() { + composite + .members + .iter() + .find(|m| m.name != StringId::EMPTY) + .map(|m| self.count_scalar_fields(m.typ)) + .unwrap_or(1) + } else { + 1 + } + } + _ => 1, + } + } + + /// Check if brace elision applies: the element is a positional scalar targeting + /// an aggregate member, and is NOT a string literal initializing a char array + /// (C99 6.7.8p14: string literals are a special case for char arrays). + pub(crate) fn is_brace_elision_candidate( + &self, + element: &InitElement, + target_type: TypeId, + ) -> bool { + if !element.designators.is_empty() { + return false; + } + let target_is_aggregate = matches!( + self.types.kind(target_type), + TypeKind::Array | TypeKind::Struct | TypeKind::Union + ); + if !target_is_aggregate { + return false; + } + // String/wide string literals can directly initialize char/wchar_t arrays + // without brace elision (C99 6.7.8p14) + if matches!( + element.value.kind, + ExprKind::InitList { .. } | ExprKind::StringLit(_) | ExprKind::WideStringLit(_) + ) { + return false; + } + true + } + + /// Consume elements from `elements[elem_idx..]` via brace elision to fill + /// an aggregate `target_type`. Returns the collected sub-elements. + /// Advances `elem_idx` past the consumed elements. + pub(crate) fn consume_brace_elision( + &self, + elements: &[InitElement], + elem_idx: &mut usize, + target_type: TypeId, + ) -> Vec { + let n = self.count_scalar_fields(target_type); + let mut sub_elements = Vec::new(); + let mut consumed = 0; + while consumed < n && *elem_idx < elements.len() { + let e = &elements[*elem_idx]; + // Stop at designated elements (they apply to the current aggregate level) + if consumed > 0 && !e.designators.is_empty() { + break; + } + sub_elements.push(InitElement { + designators: vec![], + value: e.value.clone(), + }); + *elem_idx += 1; + consumed += 1; + } + sub_elements + } + + /// Group array init elements by index, handling designators, brace elision, + /// and nested InitList flattening. Shared between static and runtime paths. + pub(crate) fn group_array_init_elements( + &self, + elements: &[InitElement], + elem_type: TypeId, + ) -> ArrayInitGroups { + let mut element_lists: HashMap> = HashMap::new(); + let mut element_indices: Vec = Vec::new(); + let mut current_idx: i64 = 0; + let mut elem_idx = 0; + + while elem_idx < elements.len() { + let element = &elements[elem_idx]; + let mut index = None; + let mut index_pos = None; + for (pos, designator) in element.designators.iter().enumerate() { + if let Designator::Index(idx) = designator { + index = Some(*idx); + index_pos = Some(pos); + break; + } + } + + let element_index = if let Some(idx) = index { + current_idx = idx + 1; + idx + } else { + let idx = current_idx; + current_idx += 1; + idx + }; + + let remaining_designators = match index_pos { + Some(pos) => element.designators[pos + 1..].to_vec(), + None => element.designators.clone(), + }; + + // Brace elision (C99 6.7.8p20): positional scalar for aggregate element + if remaining_designators.is_empty() + && self.is_brace_elision_candidate(element, elem_type) + { + let sub_elements = self.consume_brace_elision(elements, &mut elem_idx, elem_type); + let entry = element_lists.entry(element_index).or_insert_with(|| { + element_indices.push(element_index); + Vec::new() + }); + entry.extend(sub_elements); + continue; + } + + let entry = element_lists.entry(element_index).or_insert_with(|| { + element_indices.push(element_index); + Vec::new() + }); + + if remaining_designators.is_empty() { + if let ExprKind::InitList { + elements: nested_elements, + } = &element.value.kind + { + entry.extend(nested_elements.clone()); + elem_idx += 1; + continue; + } + } + + entry.push(InitElement { + designators: remaining_designators, + value: element.value.clone(), + }); + elem_idx += 1; + } + + element_indices.sort(); + ArrayInitGroups { + element_lists, + indices: element_indices, + } + } + + /// Walk struct/union init elements and produce field visits. + /// Handles positional iteration, anonymous struct continuation, designators, + /// and brace elision. Shared between static and runtime init paths. + pub(crate) fn walk_struct_init_fields( + &self, + resolved_typ: TypeId, + members: &[crate::types::StructMember], + is_union: bool, + elements: &[InitElement], + ) -> Vec { + let mut visits = Vec::new(); + let mut current_field_idx = 0; + let mut anon_cont: Option = None; + let mut elem_idx = 0; + + while elem_idx < elements.len() { + let element = &elements[elem_idx]; + if element.designators.is_empty() { + // Positional: check anonymous struct continuation, then next member + let mut member = None; + if anon_cont.is_some() { + member = self.get_anon_continuation_member( + &mut anon_cont, + members, + &mut current_field_idx, + ); + } + if member.is_none() { + member = self.next_positional_member(members, is_union, &mut current_field_idx); + } + let Some(member) = member else { + elem_idx += 1; + continue; + }; + let field_size = (self.types.size_bits(member.typ) / 8) as usize; + + // Brace elision: scalar for aggregate member + let kind = if self.is_brace_elision_candidate(element, member.typ) { + let sub_elements = + self.consume_brace_elision(elements, &mut elem_idx, member.typ); + StructFieldVisitKind::BraceElision(sub_elements) + } else { + elem_idx += 1; + StructFieldVisitKind::Expr(element.value.clone()) + }; + visits.push(StructFieldVisit { + offset: member.offset, + typ: member.typ, + field_size, + kind, + bit_offset: member.bit_offset, + bit_width: member.bit_width, + storage_unit_size: member.storage_unit_size, + }); + continue; + } + + // Designated path + let resolved = self.resolve_designator_chain(resolved_typ, 0, &element.designators); + let Some(ResolvedDesignator { + offset, + typ: field_type, + bit_offset, + bit_width, + storage_unit_size, + }) = resolved + else { + elem_idx += 1; + continue; + }; + if let Some(Designator::Field(name)) = element.designators.first() { + if let Some(result) = self.member_index_for_designator(members, *name) { + match result { + MemberDesignatorResult::Direct(next_idx) => { + current_field_idx = next_idx; + anon_cont = None; + } + MemberDesignatorResult::Anonymous { outer_idx, levels } => { + current_field_idx = outer_idx; + anon_cont = Some(AnonContinuation { outer_idx, levels }); + } + } + } + } + let field_size = (self.types.size_bits(field_type) / 8) as usize; + visits.push(StructFieldVisit { + offset, + typ: field_type, + field_size, + kind: StructFieldVisitKind::Expr(element.value.clone()), + bit_offset, + bit_width, + storage_unit_size, + }); + elem_idx += 1; + } + + visits + } + + /// Convert an AST initializer list to an IR Initializer + pub(crate) fn ast_init_list_to_ir( + &mut self, + elements: &[InitElement], + typ: TypeId, + ) -> Initializer { + let type_kind = self.types.kind(typ); + let total_size = (self.types.size_bits(typ) / 8) as usize; + + match type_kind { + TypeKind::Array => { + let elem_type = self.types.base_type(typ).unwrap_or(self.types.int_id); + let elem_size = (self.types.size_bits(elem_type) / 8) as usize; + let elem_is_aggregate = matches!( + self.types.kind(elem_type), + TypeKind::Array | TypeKind::Struct | TypeKind::Union + ); + + let groups = self.group_array_init_elements(elements, elem_type); + let mut init_elements = Vec::new(); + for element_index in groups.indices { + let Some(list) = groups.element_lists.get(&element_index) else { + continue; + }; + let offset = (element_index as usize) * elem_size; + // When a string literal initializes a char/wchar_t array element + // (e.g., char names[3][4] = {"Sun", "Mon", "Tue"}), handle it + // directly with the ARRAY type. Otherwise ast_init_list_to_ir + // recurses and passes elem_type=char, causing the string to be + // stored as a pointer instead of inline char data. + let is_string_for_char_array = elem_is_aggregate + && list.len() == 1 + && matches!( + list[0].value.kind, + ExprKind::StringLit(_) | ExprKind::WideStringLit(_) + ) + && self.types.kind(elem_type) == TypeKind::Array; + let elem_init = if is_string_for_char_array { + self.ast_init_to_ir(&list[0].value, elem_type) + } else if elem_is_aggregate { + self.ast_init_list_to_ir(list, elem_type) + } else if let Some(last) = list.last() { + self.ast_init_to_ir(&last.value, elem_type) + } else { + Initializer::None + }; + init_elements.push((offset, elem_init)); + } + + init_elements.sort_by_key(|(offset, _)| *offset); + + Initializer::Array { + elem_size, + total_size, + elements: init_elements, + } + } + + TypeKind::Struct | TypeKind::Union => { + let resolved_typ = self.resolve_struct_type(typ); + let resolved_size = (self.types.size_bits(resolved_typ) / 8) as usize; + if let Some(composite) = self.types.get(resolved_typ).composite.as_ref() { + let members: Vec<_> = composite.members.clone(); + let is_union = self.types.kind(resolved_typ) == TypeKind::Union; + + let visits = + self.walk_struct_init_fields(resolved_typ, &members, is_union, elements); + + // Convert field visits to RawFieldInit by evaluating expressions + let mut raw_fields: Vec = Vec::new(); + for visit in visits { + let field_init = match visit.kind { + StructFieldVisitKind::BraceElision(sub_elements) => { + self.ast_init_list_to_ir(&sub_elements, visit.typ) + } + StructFieldVisitKind::Expr(expr) => { + self.ast_init_to_ir(&expr, visit.typ) + } + }; + raw_fields.push(RawFieldInit { + offset: visit.offset, + field_size: visit.field_size, + init: field_init, + bit_offset: visit.bit_offset, + bit_width: visit.bit_width, + storage_unit_size: visit.storage_unit_size, + }); + } + + // Sort fields by offset to ensure proper emission order + // (designated initializers can be in any order) + // For bitfields, also sort by bit_offset to keep them together + raw_fields.sort_by(|a, b| { + a.offset + .cmp(&b.offset) + .then_with(|| a.bit_offset.unwrap_or(0).cmp(&b.bit_offset.unwrap_or(0))) + }); + + // Remove duplicate initializations (later one wins, per C semantics) + let mut idx = 0; + while idx + 1 < raw_fields.len() { + let same_offset = raw_fields[idx].offset == raw_fields[idx + 1].offset; + let both_bitfields = raw_fields[idx].bit_offset.is_some() + && raw_fields[idx + 1].bit_offset.is_some(); + let same_bitfield = both_bitfields + && raw_fields[idx].bit_offset == raw_fields[idx + 1].bit_offset; + + if same_offset && (!both_bitfields || same_bitfield) { + raw_fields.remove(idx); + } else { + idx += 1; + } + } + + // Pack bitfields that share the same storage unit + let mut init_fields: Vec<(usize, usize, Initializer)> = Vec::new(); + let mut i = 0; + while i < raw_fields.len() { + let RawFieldInit { + offset, + field_size, + init, + bit_offset, + bit_width, + storage_unit_size, + } = &raw_fields[i]; + + if let (Some(bit_off), Some(bit_w), Some(storage_size)) = + (bit_offset, bit_width, storage_unit_size) + { + let mut packed_value: u64 = 0; + if let Initializer::Int(v) = init { + let mask = (1u64 << bit_w) - 1; + packed_value |= ((*v as u64) & mask) << bit_off; + } + + let mut j = i + 1; + while j < raw_fields.len() { + let RawFieldInit { + offset: next_off, + init: next_init, + bit_offset: next_bit_off, + bit_width: next_bit_w, + .. + } = &raw_fields[j]; + if *next_off != *offset { + break; + } + if let (Some(nb_off), Some(nb_w)) = (next_bit_off, next_bit_w) { + if let Initializer::Int(v) = next_init { + let mask = (1u64 << nb_w) - 1; + packed_value |= ((*v as u64) & mask) << nb_off; + } + } + j += 1; + } + + init_fields.push(( + *offset, + *storage_size as usize, + Initializer::Int(packed_value as i128), + )); + i = j; + } else { + init_fields.push((*offset, *field_size, init.clone())); + i += 1; + } + } + + Initializer::Struct { + total_size: resolved_size, + fields: init_fields, + } + } else { + Initializer::None + } + } + + _ => { + if let Some(element) = elements.first() { + self.ast_init_to_ir(&element.value, typ) + } else { + Initializer::None + } + } + } + } + + pub(crate) fn resolve_designator_chain( + &self, + base_type: TypeId, + base_offset: usize, + designators: &[Designator], + ) -> Option { + let mut offset = base_offset; + let mut typ = base_type; + let mut bit_offset = None; + let mut bit_width = None; + let mut storage_unit_size = None; + + for (idx, designator) in designators.iter().enumerate() { + match designator { + Designator::Field(name) => { + let mut resolved = typ; + if self.types.kind(resolved) == TypeKind::Array { + resolved = self.types.base_type(resolved)?; + } + resolved = self.resolve_struct_type(resolved); + let member = self.types.find_member(resolved, *name)?; + offset += member.offset; + typ = member.typ; + if idx + 1 == designators.len() { + bit_offset = member.bit_offset; + bit_width = member.bit_width; + storage_unit_size = member.storage_unit_size; + } else { + bit_offset = None; + bit_width = None; + storage_unit_size = None; + } + } + Designator::Index(index) => { + if self.types.kind(typ) != TypeKind::Array { + return None; + } + let elem_type = self.types.base_type(typ)?; + let elem_size = self.types.size_bits(elem_type) / 8; + offset += (*index as usize) * (elem_size as usize); + typ = elem_type; + bit_offset = None; + bit_width = None; + storage_unit_size = None; + } + } + } + + Some(ResolvedDesignator { + offset, + typ, + bit_offset, + bit_width, + storage_unit_size, + }) + } + + pub(crate) fn next_positional_member( + &self, + members: &[crate::types::StructMember], + is_union: bool, + current_field_idx: &mut usize, + ) -> Option { + if is_union { + if *current_field_idx > 0 { + return None; + } + let member = members.iter().find(|m| m.name != StringId::EMPTY)?; + *current_field_idx = members.len(); + return Some(MemberInfo { + offset: member.offset, + typ: member.typ, + bit_offset: member.bit_offset, + bit_width: member.bit_width, + storage_unit_size: member.storage_unit_size, + }); + } + + while *current_field_idx < members.len() { + let member = &members[*current_field_idx]; + if member.name == StringId::EMPTY && member.bit_width.is_some() { + *current_field_idx += 1; + continue; + } + if member.name != StringId::EMPTY || member.bit_width.is_none() { + *current_field_idx += 1; + return Some(MemberInfo { + offset: member.offset, + typ: member.typ, + bit_offset: member.bit_offset, + bit_width: member.bit_width, + storage_unit_size: member.storage_unit_size, + }); + } + *current_field_idx += 1; + } + + None + } + + /// Get the next positional member from an anonymous struct continuation. + /// Walks the stack of anonymous struct levels from innermost to outermost. + /// If the innermost level is exhausted, pops it and tries the next outer level. + /// When all levels are exhausted, clears the continuation and returns None. + pub(crate) fn get_anon_continuation_member( + &self, + cont: &mut Option, + _outer_members: &[crate::types::StructMember], + current_field_idx: &mut usize, + ) -> Option { + let c = cont.as_mut()?; + + loop { + let Some(level) = c.levels.last() else { + // All levels exhausted + *current_field_idx = c.outer_idx + 1; + *cont = None; + return None; + }; + let anon_type_id = level.anon_type; + let base_offset = level.base_offset; + let mut idx = level.inner_next_idx; + + let anon_type = self.types.get(anon_type_id); + let Some(composite) = anon_type.composite.as_ref() else { + c.levels.pop(); + continue; + }; + let members = composite.members.clone(); + + // Scan members at this level + let mut found_member = None; + let mut descend_into = None; + + while idx < members.len() { + let inner = &members[idx]; + // Skip unnamed bitfield padding + if inner.name == StringId::EMPTY && inner.bit_width.is_some() { + idx += 1; + continue; + } + // Nested anonymous aggregate — descend into it + if inner.name == StringId::EMPTY && inner.bit_width.is_none() { + let inner_type = self.types.get(inner.typ); + let is_nested_anon = + matches!(inner_type.kind, TypeKind::Struct | TypeKind::Union) + && inner_type + .composite + .as_ref() + .is_some_and(|comp| comp.tag.is_none()); + if is_nested_anon { + descend_into = Some((inner.typ, base_offset + inner.offset, idx + 1)); + break; + } + } + // Found a valid named member + found_member = Some((idx + 1, inner.clone())); + break; + } + + if let Some((next_idx, inner)) = found_member { + // Update the current level's index + c.levels.last_mut().unwrap().inner_next_idx = next_idx; + return Some(MemberInfo { + offset: base_offset + inner.offset, + typ: inner.typ, + bit_offset: inner.bit_offset, + bit_width: inner.bit_width, + storage_unit_size: inner.storage_unit_size, + }); + } + + if let Some((nested_type, nested_offset, next_idx)) = descend_into { + // Advance past the anon struct at this level, then descend + c.levels.last_mut().unwrap().inner_next_idx = next_idx; + c.levels.push(AnonLevel { + anon_type: nested_type, + base_offset: nested_offset, + inner_next_idx: 0, + }); + continue; + } + + // This level is exhausted — pop it + c.levels.pop(); + } + } + + pub(crate) fn member_index_for_designator( + &self, + members: &[crate::types::StructMember], + name: StringId, + ) -> Option { + for (idx, member) in members.iter().enumerate() { + if member.name == name { + return Some(MemberDesignatorResult::Direct(idx + 1)); + } + if member.name == StringId::EMPTY { + let member_type = self.types.get(member.typ); + let is_anon_aggregate = + matches!(member_type.kind, TypeKind::Struct | TypeKind::Union) + && member_type + .composite + .as_ref() + .is_some_and(|composite| composite.tag.is_none()); + if is_anon_aggregate { + // Recursively search for the field, building the nesting path + let mut path = Vec::new(); + if self.find_anon_field_path(member.typ, member.offset, name, &mut path) { + return Some(MemberDesignatorResult::Anonymous { + outer_idx: idx, + levels: path, + }); + } + } + } + } + + None + } + + /// Recursively search for `name` inside an anonymous aggregate, building + /// the path of `AnonLevel`s needed for positional continuation. + /// Returns true if the field was found. + pub(crate) fn find_anon_field_path( + &self, + anon_type: TypeId, + base_offset: usize, + name: StringId, + path: &mut Vec, + ) -> bool { + let typ = self.types.get(anon_type); + let Some(composite) = typ.composite.as_ref() else { + return false; + }; + for (inner_idx, inner_member) in composite.members.iter().enumerate() { + if inner_member.name == name { + // Found it directly at this level + path.push(AnonLevel { + anon_type, + base_offset, + inner_next_idx: inner_idx + 1, + }); + return true; + } + // Check if this is a nested anonymous aggregate + if inner_member.name == StringId::EMPTY { + let inner_type = self.types.get(inner_member.typ); + let is_nested_anon = matches!(inner_type.kind, TypeKind::Struct | TypeKind::Union) + && inner_type + .composite + .as_ref() + .is_some_and(|c| c.tag.is_none()); + if is_nested_anon { + // Push this level pointing PAST the nested anon struct. + // The inner level handles continuation within the nested anon; + // when it's exhausted, this level continues from the next member. + path.push(AnonLevel { + anon_type, + base_offset, + inner_next_idx: inner_idx + 1, + }); + if self.find_anon_field_path( + inner_member.typ, + base_offset + inner_member.offset, + name, + path, + ) { + return true; + } + path.pop(); // not found in this branch + } + } + } + false + } +} diff --git a/cc/ir/linearize_stmt.rs b/cc/ir/linearize_stmt.rs new file mode 100644 index 00000000..2aab2529 --- /dev/null +++ b/cc/ir/linearize_stmt.rs @@ -0,0 +1,1913 @@ +// +// Copyright (c) 2025-2026 Jeff Garzik +// +// This file is part of the posixutils-rs project covered under +// the MIT License. For the full license text, please see the LICENSE +// file in the root directory of this project. +// SPDX-License-Identifier: MIT + +//! Statement linearization + +use super::linearize::*; +use super::{ + AsmConstraint, AsmData, BasicBlockId, Initializer, Instruction, Opcode, Pseudo, PseudoId, +}; +use crate::diag::error; +use crate::parse::ast::{ + AsmOperand, BinaryOp, BlockItem, Declaration, Expr, ExprKind, ForInit, InitElement, + OffsetOfPath, Stmt, UnaryOp, +}; +use crate::strings::StringId; +use crate::types::{TypeId, TypeKind, TypeModifiers}; + +impl<'a> super::linearize::Linearizer<'a> { + pub(crate) fn linearize_stmt(&mut self, stmt: &Stmt) { + match stmt { + Stmt::Empty => {} + + Stmt::Expr(expr) => { + self.linearize_expr(expr); + } + + Stmt::Block(items) => { + self.push_scope(); + + for item in items { + match item { + BlockItem::Declaration(decl) => self.linearize_local_decl(decl), + BlockItem::Statement(s) => self.linearize_stmt(s), + } + } + + self.pop_scope(); + } + + Stmt::If { + cond, + then_stmt, + else_stmt, + } => { + self.linearize_if(cond, then_stmt, else_stmt.as_deref()); + } + + Stmt::While { cond, body } => { + self.linearize_while(cond, body); + } + + Stmt::DoWhile { body, cond } => { + self.linearize_do_while(body, cond); + } + + Stmt::For { + init, + cond, + post, + body, + } => { + self.linearize_for(init.as_ref(), cond.as_ref(), post.as_ref(), body); + } + + Stmt::Return(expr) => { + if let Some(e) = expr { + let expr_typ = self.expr_type(e); + // Get the function's actual return type for proper conversion + let func_ret_type = self + .current_func + .as_ref() + .map(|f| f.return_type) + .unwrap_or(expr_typ); + + if let Some(sret_ptr) = self.struct_return_ptr { + self.emit_sret_return(e, sret_ptr, self.struct_return_size); + } else if let Some(ret_type) = self.two_reg_return_type { + self.emit_two_reg_return(e, ret_type); + } else if self.types.is_complex(expr_typ) { + let addr = self.linearize_lvalue(e); + let typ_size = self.types.size_bits(func_ret_type); + self.emit(Instruction::ret_typed(Some(addr), func_ret_type, typ_size)); + } else { + let val = self.linearize_expr(e); + // Convert expression value to function's return type if needed + let converted_val = if expr_typ != func_ret_type + && self.types.kind(func_ret_type) != TypeKind::Void + { + self.emit_convert(val, expr_typ, func_ret_type) + } else { + val + }; + // Function types decay to pointers when returned + let typ_size = if self.types.kind(func_ret_type) == TypeKind::Function { + self.target.pointer_width + } else { + self.types.size_bits(func_ret_type) + }; + self.emit(Instruction::ret_typed( + Some(converted_val), + func_ret_type, + typ_size, + )); + } + } else { + self.emit(Instruction::ret(None)); + } + } + + Stmt::Break => { + if let Some(&target) = self.break_targets.last() { + if let Some(current) = self.current_bb { + self.emit(Instruction::br(target)); + self.link_bb(current, target); + } + } + } + + Stmt::Continue => { + if let Some(&target) = self.continue_targets.last() { + if let Some(current) = self.current_bb { + self.emit(Instruction::br(target)); + self.link_bb(current, target); + } + } + } + + Stmt::Goto(label) => { + let label_str = self.str(*label).to_string(); + let target = self.get_or_create_label(&label_str); + if let Some(current) = self.current_bb { + self.emit(Instruction::br(target)); + self.link_bb(current, target); + } + + // Set current_bb to None - any subsequent code until a label is dead + // emit() will safely skip when current_bb is None + self.current_bb = None; + } + + Stmt::Label { name, stmt } => { + let name_str = self.str(*name).to_string(); + let label_bb = self.get_or_create_label(&name_str); + + // If current block is not terminated, branch to label + if !self.is_terminated() { + if let Some(current) = self.current_bb { + self.emit(Instruction::br(label_bb)); + self.link_bb(current, label_bb); + } + } + + self.switch_bb(label_bb); + self.linearize_stmt(stmt); + } + + Stmt::Switch { expr, body } => { + self.linearize_switch(expr, body); + } + + Stmt::Case(_) | Stmt::Default => { + // Case/Default labels are handled by linearize_switch + // If we encounter them outside a switch, ignore them + } + + Stmt::Asm { + template, + outputs, + inputs, + clobbers, + goto_labels, + } => { + self.linearize_asm(template, outputs, inputs, clobbers, goto_labels); + } + } + } + + pub(crate) fn linearize_local_decl(&mut self, decl: &Declaration) { + for declarator in &decl.declarators { + let typ = declarator.typ; + + // Check if this is a static local variable + if declarator.storage_class.contains(TypeModifiers::STATIC) { + // Static local: create a global with unique name + self.linearize_static_local(declarator); + continue; + } + + // Check if this is a VLA (Variable Length Array) + if !declarator.vla_sizes.is_empty() { + // C99 6.7.8: VLAs cannot have initializers + if declarator.init.is_some() { + if let Some(pos) = self.current_pos { + error(pos, "variable length arrays cannot have initializers"); + } + } + self.linearize_vla_decl(declarator); + continue; + } + + // Create a symbol pseudo for this local variable (its address) + // Use unique name (name.id) to distinguish shadowed variables + let sym_id = self.alloc_pseudo(); + let name_str = self.symbol_name(declarator.symbol); + let unique_name = format!("{}.{}", name_str, sym_id.0); + let sym = Pseudo::sym(sym_id, unique_name.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(sym); + // Register with function's local variable tracking for SSA + // Pass the current basic block as the declaration block for scope-aware phi placement + let mods = self.types.modifiers(typ); + let is_volatile = mods.contains(TypeModifiers::VOLATILE); + let is_atomic = mods.contains(TypeModifiers::ATOMIC); + func.add_local( + &unique_name, + sym_id, + typ, + is_volatile, + is_atomic, + self.current_bb, + declarator.explicit_align, + ); + } + + // Track in linearizer's locals map using SymbolId as key + self.insert_local( + declarator.symbol, + LocalVarInfo { + sym: sym_id, + typ, + vla_size_sym: None, + vla_elem_type: None, + vla_dim_syms: vec![], + is_indirect: false, + }, + ); + + // If there's an initializer, emit Store(s) + if let Some(init) = &declarator.init { + if let ExprKind::InitList { elements } = &init.kind { + // Handle initializer list for arrays and structs + // C99 6.7.8p19: uninitialized members must be zero-initialized + // Zero the entire aggregate first, then apply explicit initializers + let type_kind = self.types.kind(typ); + if type_kind == TypeKind::Struct + || type_kind == TypeKind::Union + || type_kind == TypeKind::Array + { + self.emit_aggregate_zero(sym_id, typ); + } + self.linearize_init_list(sym_id, typ, elements); + } else if let ExprKind::StringLit(s) = &init.kind { + // String literal initialization of char array + // Copy the string bytes to the local array + if self.types.kind(typ) == TypeKind::Array { + let elem_type = self.types.base_type(typ).unwrap_or(self.types.char_id); + let elem_size = self.types.size_bits(elem_type); + + // Copy each byte from string literal to local array + for (i, byte) in s.bytes().enumerate() { + let byte_val = self.emit_const(byte as i128, elem_type); + self.emit(Instruction::store( + byte_val, sym_id, i as i64, elem_type, elem_size, + )); + } + // Store null terminator + let null_val = self.emit_const(0, elem_type); + self.emit(Instruction::store( + null_val, + sym_id, + s.chars().count() as i64, + elem_type, + elem_size, + )); + } else { + // Pointer initialized with string literal - store the address + let val = self.linearize_expr(init); + let init_type = self.expr_type(init); + let converted = self.emit_convert(val, init_type, typ); + let size = self.types.size_bits(typ); + self.emit(Instruction::store(converted, sym_id, 0, typ, size)); + } + } else if let ExprKind::WideStringLit(s) = &init.kind { + // Wide string literal initialization of wchar_t array + // Copy the wide string chars to the local array (4 bytes each) + if self.types.kind(typ) == TypeKind::Array { + let elem_type = self.types.base_type(typ).unwrap_or(self.types.int_id); + let elem_size = self.types.size_bits(elem_type); + let elem_bytes = (elem_size / 8) as i64; + + // Copy each wchar_t from wide string literal to local array + for (i, ch) in s.chars().enumerate() { + let ch_val = self.emit_const(ch as i128, elem_type); + self.emit(Instruction::store( + ch_val, + sym_id, + (i as i64) * elem_bytes, + elem_type, + elem_size, + )); + } + // Store null terminator + let null_val = self.emit_const(0, elem_type); + self.emit(Instruction::store( + null_val, + sym_id, + (s.chars().count() as i64) * elem_bytes, + elem_type, + elem_size, + )); + } else { + // Pointer initialized with wide string literal - store the address + let val = self.linearize_expr(init); + let init_type = self.expr_type(init); + let converted = self.emit_convert(val, init_type, typ); + let size = self.types.size_bits(typ); + self.emit(Instruction::store(converted, sym_id, 0, typ, size)); + } + } else if self.types.is_complex(typ) { + // Complex type initialization - linearize_expr returns an address + // to a temp containing the complex value. Copy from temp to local. + let value_addr = self.linearize_expr(init); + let base_typ = self.types.complex_base(typ); + let base_bits = self.types.size_bits(base_typ); + let base_bytes = (base_bits / 8) as i64; + + // Load real and imag parts from source temp + let val_real = self.alloc_pseudo(); + let val_imag = self.alloc_pseudo(); + self.emit(Instruction::load( + val_real, value_addr, 0, base_typ, base_bits, + )); + self.emit(Instruction::load( + val_imag, value_addr, base_bytes, base_typ, base_bits, + )); + + // Store to local variable + self.emit(Instruction::store(val_real, sym_id, 0, base_typ, base_bits)); + self.emit(Instruction::store( + val_imag, sym_id, base_bytes, base_typ, base_bits, + )); + } else { + // Check for large struct/union initialization (> 64 bits) + // linearize_expr returns an address for large aggregates + let type_kind = self.types.kind(typ); + let type_size = self.types.size_bits(typ); + if (type_kind == TypeKind::Struct || type_kind == TypeKind::Union) + && type_size > 64 + { + // Large struct/union init - source is an address, do block copy + let value_addr = self.linearize_expr(init); + let type_size_bytes = type_size / 8; + + self.emit_block_copy(sym_id, value_addr, type_size_bytes as i64); + } else { + // Simple scalar initializer + let val = self.linearize_expr(init); + // Convert the value to the target type (important for _Bool normalization) + let init_type = self.expr_type(init); + let converted = self.emit_convert(val, init_type, typ); + let size = self.types.size_bits(typ); + self.emit(Instruction::store(converted, sym_id, 0, typ, size)); + } + } + } + } + } + + /// Linearize a VLA (Variable Length Array) declaration + /// + /// VLAs are allocated on the stack at runtime using Alloca. + /// The size is computed as: product of all dimension sizes * sizeof(element_type) + /// + /// Unlike regular arrays where the symbol is the address of stack memory, + /// for VLAs we store the Alloca result (pointer) and then access it through + /// a pointer load. We create a pointer variable to hold the VLA address. + /// + /// We also create hidden local variables to store the dimension sizes + /// so that sizeof(vla) can be computed at runtime. + pub(crate) fn linearize_vla_decl(&mut self, declarator: &crate::parse::ast::InitDeclarator) { + let typ = declarator.typ; + + // Get the element type by stripping only VLA dimensions from the array type. + // For int arr[n][4], vla_sizes has 1 element, so we strip 1 dimension to get int[4]. + // For int arr[n][m], vla_sizes has 2 elements, so we strip 2 dimensions to get int. + let num_vla_dims = declarator.vla_sizes.len(); + let mut elem_type = typ; + for _ in 0..num_vla_dims { + if self.types.kind(elem_type) == TypeKind::Array { + elem_type = self.types.base_type(elem_type).unwrap_or(self.types.int_id); + } + } + let elem_size = self.types.size_bytes(elem_type) as i64; + + let ulong_type = self.types.ulong_id; + + // Evaluate all VLA size expressions, store each in a hidden local, + // and compute total element count. + // For int arr[n][m], we store n and m separately (for stride computation) + // and compute total_count = n * m. + let mut vla_dim_syms: Vec = Vec::new(); + let mut total_count: Option = None; + + for (dim_idx, vla_size_expr) in declarator.vla_sizes.iter().enumerate() { + let dim_size = self.linearize_expr(vla_size_expr); + + // Create a hidden local to store this dimension's size + // This is needed for runtime stride computation in multi-dimensional VLAs + let dim_sym_id = self.alloc_pseudo(); + let decl_name = self.symbol_name(declarator.symbol); + let dim_var_name = format!("__vla_dim{}_{}.{}", dim_idx, decl_name, dim_sym_id.0); + let dim_sym = Pseudo::sym(dim_sym_id, dim_var_name.clone()); + + if let Some(func) = &mut self.current_func { + func.add_pseudo(dim_sym); + func.add_local( + &dim_var_name, + dim_sym_id, + ulong_type, + false, // not volatile + false, // not atomic + self.current_bb, + None, // no explicit alignment + ); + } + + // Widen dimension size to 64-bit before storing + let dim_expr_typ = vla_size_expr.typ.unwrap_or(self.types.int_id); + let dim_size = self.emit_convert(dim_size, dim_expr_typ, ulong_type); + let store_dim_insn = Instruction::store(dim_size, dim_sym_id, 0, ulong_type, 64); + self.emit(store_dim_insn); + vla_dim_syms.push(dim_sym_id); + + // Update running total count + total_count = Some(match total_count { + None => dim_size, + Some(prev) => { + // Multiply: prev * dim_size + let result = self.alloc_pseudo(); + let mul_insn = Instruction::new(Opcode::Mul) + .with_target(result) + .with_src(prev) + .with_src(dim_size) + .with_size(64) + .with_type(self.types.ulong_id); + self.emit(mul_insn); + result + } + }); + } + let num_elements = total_count.expect("VLA must have at least one size expression"); + + // Create a hidden local variable to store the total number of elements + // This is needed for sizeof(vla) to work at runtime + let size_sym_id = self.alloc_pseudo(); + let vla_name = self.symbol_name(declarator.symbol); + let size_var_name = format!("__vla_size_{}.{}", vla_name, size_sym_id.0); + let size_sym = Pseudo::sym(size_sym_id, size_var_name.clone()); + + if let Some(func) = &mut self.current_func { + func.add_pseudo(size_sym); + func.add_local( + &size_var_name, + size_sym_id, + ulong_type, + false, // not volatile + false, // not atomic + self.current_bb, + None, // no explicit alignment + ); + } + + // Store num_elements into the hidden size variable + let store_size_insn = Instruction::store(num_elements, size_sym_id, 0, ulong_type, 64); + self.emit(store_size_insn); + + // Compute total size in bytes: num_elements * sizeof(element) + let elem_size_const = self.emit_const(elem_size as i128, self.types.long_id); + let total_size = self.alloc_pseudo(); + let mul_insn = Instruction::new(Opcode::Mul) + .with_target(total_size) + .with_src(num_elements) + .with_src(elem_size_const) + .with_size(64) + .with_type(self.types.ulong_id); + self.emit(mul_insn); + + // Emit Alloca instruction to allocate stack space + let alloca_result = self.alloc_pseudo(); + let alloca_insn = Instruction::new(Opcode::Alloca) + .with_target(alloca_result) + .with_src(total_size) + .with_type_and_size(self.types.void_ptr_id, 64); + self.emit(alloca_insn); + + // Create a symbol pseudo for the VLA pointer variable + // This symbol stores the pointer to the VLA memory (like a pointer variable) + let sym_id = self.alloc_pseudo(); + let sym_name = self.symbol_name(declarator.symbol); + let unique_name = format!("{}.{}", sym_name, sym_id.0); + let sym = Pseudo::sym(sym_id, unique_name.clone()); + + // Create a pointer type for the VLA (pointer to element type) + let ptr_type = self.types.pointer_to(elem_type); + + if let Some(func) = &mut self.current_func { + func.add_pseudo(sym); + // Register as a pointer variable, not as the array type + let mods = self.types.modifiers(typ); + let is_volatile = mods.contains(TypeModifiers::VOLATILE); + let is_atomic = mods.contains(TypeModifiers::ATOMIC); + func.add_local( + &unique_name, + sym_id, + ptr_type, + is_volatile, + is_atomic, + self.current_bb, + declarator.explicit_align, // VLA explicit alignment + ); + } + + // Store the Alloca result (pointer) into the VLA symbol + let store_insn = Instruction::store(alloca_result, sym_id, 0, ptr_type, 64); + self.emit(store_insn); + + // Track in linearizer's locals map with pointer type and VLA size info + // This makes arr[i] behave like ptr[i] - load ptr, then offset + self.insert_local( + declarator.symbol, + LocalVarInfo { + sym: sym_id, + typ: ptr_type, + vla_size_sym: Some(size_sym_id), + vla_elem_type: Some(elem_type), + vla_dim_syms, + is_indirect: false, + }, + ); + } + + /// Linearize a static local variable declaration + /// + /// Static locals have static storage duration but no linkage. + /// They are implemented as globals with unique names like `funcname.varname.N`. + /// Initialization happens once at program start (compile-time). + pub(crate) fn linearize_static_local( + &mut self, + declarator: &crate::parse::ast::InitDeclarator, + ) { + let name_str = self.symbol_name(declarator.symbol); + + // C99 6.7.4p3: A non-static inline function cannot define a non-const + // function-local static variable + if self.current_func_is_non_static_inline { + let is_const = self + .types + .modifiers(declarator.typ) + .contains(TypeModifiers::CONST); + if !is_const { + if let Some(pos) = self.current_pos { + error( + pos, + &format!( + "non-static inline function '{}' cannot define non-const static variable '{}'", + self.current_func_name, name_str + ), + ); + } + } + } + + // Generate unique global name: funcname.varname.counter + let global_name = format!( + "{}.{}.{}", + self.current_func_name, name_str, self.static_local_counter + ); + self.static_local_counter += 1; + + // Track mapping from local name to global name for this function's scope + // Use a key that includes function name to handle same-named statics in different functions + let key = format!("{}.{}", self.current_func_name, name_str); + self.static_locals.insert( + key, + StaticLocalInfo { + global_name: global_name.clone(), + typ: declarator.typ, + }, + ); + + // Also insert with the SymbolId for the current function scope + // This is used during expression linearization + self.insert_local( + declarator.symbol, + LocalVarInfo { + // Use a sentinel value - we'll handle static locals specially + sym: PseudoId(u32::MAX), + typ: declarator.typ, + vla_size_sym: None, + vla_elem_type: None, + vla_dim_syms: vec![], + is_indirect: false, + }, + ); + + // Determine initializer (static locals are initialized at compile time) + let init = declarator.init.as_ref().map_or(Initializer::None, |e| { + self.ast_init_to_ir(e, declarator.typ) + }); + + // Add as a global - static locals always have internal linkage + // Check for thread-local storage + let modifiers = self.types.modifiers(declarator.typ); + if modifiers.contains(TypeModifiers::THREAD_LOCAL) { + self.module.add_global_tls_aligned( + &global_name, + declarator.typ, + init, + declarator.explicit_align, + true, // static locals always have internal linkage + ); + } else { + self.module.add_global_aligned( + &global_name, + declarator.typ, + init, + declarator.explicit_align, + true, // static locals always have internal linkage + ); + } + } + + /// Linearize an initializer list for arrays or structs + pub(crate) fn linearize_init_list( + &mut self, + base_sym: PseudoId, + typ: TypeId, + elements: &[InitElement], + ) { + self.linearize_init_list_at_offset(base_sym, 0, typ, elements); + } + + /// Linearize an initializer list at a given base offset + pub(crate) fn linearize_init_list_at_offset( + &mut self, + base_sym: PseudoId, + base_offset: i64, + typ: TypeId, + elements: &[InitElement], + ) { + match self.types.kind(typ) { + TypeKind::Array => { + let elem_type = self.types.base_type(typ).unwrap_or(self.types.int_id); + let elem_size = self.types.size_bits(elem_type) / 8; + let elem_is_aggregate = matches!( + self.types.kind(elem_type), + TypeKind::Array | TypeKind::Struct | TypeKind::Union + ); + + let groups = self.group_array_init_elements(elements, elem_type); + for element_index in groups.indices { + let Some(list) = groups.element_lists.get(&element_index) else { + continue; + }; + let offset = base_offset + element_index * elem_size as i64; + // When a string literal initializes a char array element + // (e.g., char arr[3][4] = {"Sun", "Mon", "Tue"}), handle + // it as a string copy rather than recursing into individual + // char stores. The recursion would treat the string as a + // pointer instead of inline data. + let is_string_for_char_array = elem_is_aggregate + && list.len() == 1 + && matches!( + list[0].value.kind, + ExprKind::StringLit(_) | ExprKind::WideStringLit(_) + ) + && self.types.kind(elem_type) == TypeKind::Array; + if is_string_for_char_array { + // Emit byte-by-byte stores for the string content + if let ExprKind::StringLit(s) = &list[0].value.kind { + let char_type = self + .types + .base_type(elem_type) + .unwrap_or(self.types.char_id); + let char_bits = self.types.size_bits(char_type); + for (i, ch) in s.chars().enumerate() { + let byte_val = self.emit_const(ch as u8 as i128, self.types.int_id); + self.emit(Instruction::store( + byte_val, + base_sym, + offset + i as i64, + char_type, + char_bits, + )); + } + // Null terminator + zero fill + let arr_bytes = (self.types.size_bits(elem_type) / 8) as usize; + let str_len = s.chars().count(); + for i in str_len..arr_bytes { + let zero = self.emit_const(0, self.types.int_id); + self.emit(Instruction::store( + zero, + base_sym, + offset + i as i64, + char_type, + char_bits, + )); + } + } + continue; + } + if elem_is_aggregate { + self.linearize_init_list_at_offset(base_sym, offset, elem_type, list); + continue; + } + let Some(last) = list.last() else { + continue; + }; + let val = self.linearize_expr(&last.value); + let val_type = self.expr_type(&last.value); + let converted = self.emit_convert(val, val_type, elem_type); + let elem_size = self.types.size_bits(elem_type); + self.emit(Instruction::store( + converted, base_sym, offset, elem_type, elem_size, + )); + } + } + TypeKind::Struct | TypeKind::Union => { + // If the initializer is a single expression of the same struct + // type (e.g., `Py_complex in[1] = {a}` where `a` is Py_complex), + // do a block copy instead of field-by-field initialization. + if elements.len() == 1 + && elements[0].designators.is_empty() + && elements[0].value.typ.is_some() + { + let expr_type = self.expr_type(&elements[0].value); + let expr_kind = self.types.kind(expr_type); + if (expr_kind == TypeKind::Struct || expr_kind == TypeKind::Union) + && self.types.size_bits(expr_type) == self.types.size_bits(typ) + { + let src_addr = self.linearize_lvalue(&elements[0].value); + let target_size_bytes = self.types.size_bits(typ) / 8; + self.emit_block_copy_at_offset( + base_sym, + base_offset, + src_addr, + target_size_bytes as i64, + ); + return; + } + } + + let resolved_typ = self.resolve_struct_type(typ); + if let Some(composite) = self.types.get(resolved_typ).composite.as_ref() { + let members: Vec<_> = composite.members.clone(); + let is_union = self.types.kind(resolved_typ) == TypeKind::Union; + + let visits = + self.walk_struct_init_fields(resolved_typ, &members, is_union, elements); + + for visit in visits { + let offset = base_offset + visit.offset as i64; + let field_type = visit.typ; + + match visit.kind { + StructFieldVisitKind::BraceElision(sub_elements) => { + self.linearize_init_list_at_offset( + base_sym, + offset, + field_type, + &sub_elements, + ); + } + StructFieldVisitKind::Expr(expr) => { + if let (Some(bit_off), Some(bit_w), Some(storage_size)) = + (visit.bit_offset, visit.bit_width, visit.storage_unit_size) + { + let val = self.linearize_expr(&expr); + let val_type = self.expr_type(&expr); + let storage_type = self.bitfield_storage_type(storage_size); + let converted = self.emit_convert(val, val_type, storage_type); + self.emit_bitfield_store( + base_sym, + offset as usize, + bit_off, + bit_w, + storage_size, + converted, + ); + } else { + self.linearize_struct_field_init( + base_sym, offset, field_type, &expr, + ); + } + } + } + } + } + } + _ => { + if let Some(element) = elements.first() { + let val = self.linearize_expr(&element.value); + let val_type = self.expr_type(&element.value); + let converted = self.emit_convert(val, val_type, typ); + let typ_size = self.types.size_bits(typ); + self.emit(Instruction::store( + converted, + base_sym, + base_offset, + typ, + typ_size, + )); + } + } + } + } + + pub(crate) fn linearize_struct_field_init( + &mut self, + base_sym: PseudoId, + offset: i64, + field_type: TypeId, + value: &Expr, + ) { + if let ExprKind::InitList { + elements: nested_elems, + } = &value.kind + { + self.linearize_init_list_at_offset(base_sym, offset, field_type, nested_elems); + } else if let ExprKind::StringLit(s) = &value.kind { + if self.types.kind(field_type) == TypeKind::Array { + let elem_type = self + .types + .base_type(field_type) + .unwrap_or(self.types.char_id); + let elem_size = self.types.size_bits(elem_type); + + for (i, byte) in s.bytes().enumerate() { + let byte_val = self.emit_const(byte as i128, elem_type); + self.emit(Instruction::store( + byte_val, + base_sym, + offset + i as i64, + elem_type, + elem_size, + )); + } + let null_val = self.emit_const(0, elem_type); + self.emit(Instruction::store( + null_val, + base_sym, + offset + s.chars().count() as i64, + elem_type, + elem_size, + )); + } else { + let val = self.linearize_expr(value); + let val_type = self.expr_type(value); + let converted = self.emit_convert(val, val_type, field_type); + let size = self.types.size_bits(field_type); + self.emit(Instruction::store( + converted, base_sym, offset, field_type, size, + )); + } + } else { + let (actual_type, actual_size) = if self.types.kind(field_type) == TypeKind::Array { + let elem_type = self.types.base_type(field_type).unwrap_or(field_type); + (elem_type, self.types.size_bits(elem_type)) + } else { + (field_type, self.types.size_bits(field_type)) + }; + let val = self.linearize_expr(value); + let val_type = self.expr_type(value); + let converted = self.emit_convert(val, val_type, actual_type); + self.emit(Instruction::store( + converted, + base_sym, + offset, + actual_type, + actual_size, + )); + } + } + + pub(crate) fn linearize_if(&mut self, cond: &Expr, then_stmt: &Stmt, else_stmt: Option<&Stmt>) { + let cond_val = self.linearize_expr(cond); + + let then_bb = self.alloc_bb(); + let else_bb = self.alloc_bb(); + let merge_bb = self.alloc_bb(); + + // Conditional branch + if let Some(current) = self.current_bb { + if else_stmt.is_some() { + self.emit(Instruction::cbr(cond_val, then_bb, else_bb)); + } else { + self.emit(Instruction::cbr(cond_val, then_bb, merge_bb)); + } + self.link_bb(current, then_bb); + if else_stmt.is_some() { + self.link_bb(current, else_bb); + } else { + self.link_bb(current, merge_bb); + } + } + + // Then block + self.switch_bb(then_bb); + self.linearize_stmt(then_stmt); + self.link_to_merge_if_needed(merge_bb); + + // Else block + if let Some(else_s) = else_stmt { + self.switch_bb(else_bb); + self.linearize_stmt(else_s); + self.link_to_merge_if_needed(merge_bb); + } + + // Merge block + self.switch_bb(merge_bb); + } + + pub(crate) fn linearize_while(&mut self, cond: &Expr, body: &Stmt) { + let cond_bb = self.alloc_bb(); + let body_bb = self.alloc_bb(); + let exit_bb = self.alloc_bb(); + + // Jump to condition + if let Some(current) = self.current_bb { + if !self.is_terminated() { + self.emit(Instruction::br(cond_bb)); + self.link_bb(current, cond_bb); + } + } + + // Condition block + self.switch_bb(cond_bb); + let cond_val = self.linearize_expr(cond); + // After linearizing condition, current_bb may be different from cond_bb + // (e.g., if condition contains short-circuit operators like && or ||). + // Link the CURRENT block to body_bb and exit_bb. + if let Some(cond_end_bb) = self.current_bb { + self.emit(Instruction::cbr(cond_val, body_bb, exit_bb)); + self.link_bb(cond_end_bb, body_bb); + self.link_bb(cond_end_bb, exit_bb); + } + + // Body block + self.break_targets.push(exit_bb); + self.continue_targets.push(cond_bb); + + self.switch_bb(body_bb); + self.linearize_stmt(body); + if !self.is_terminated() { + // After linearizing body, current_bb may be different from body_bb + // (e.g., if body contains nested loops). Link the CURRENT block to cond_bb. + if let Some(current) = self.current_bb { + self.emit(Instruction::br(cond_bb)); + self.link_bb(current, cond_bb); + } + } + + self.break_targets.pop(); + self.continue_targets.pop(); + + // Exit block + self.switch_bb(exit_bb); + } + + pub(crate) fn linearize_do_while(&mut self, body: &Stmt, cond: &Expr) { + let body_bb = self.alloc_bb(); + let cond_bb = self.alloc_bb(); + let exit_bb = self.alloc_bb(); + + // Jump to body + if let Some(current) = self.current_bb { + if !self.is_terminated() { + self.emit(Instruction::br(body_bb)); + self.link_bb(current, body_bb); + } + } + + // Body block + self.break_targets.push(exit_bb); + self.continue_targets.push(cond_bb); + + self.switch_bb(body_bb); + self.linearize_stmt(body); + if !self.is_terminated() { + // After linearizing body, current_bb may be different from body_bb + if let Some(current) = self.current_bb { + self.emit(Instruction::br(cond_bb)); + self.link_bb(current, cond_bb); + } + } + + self.break_targets.pop(); + self.continue_targets.pop(); + + // Condition block + self.switch_bb(cond_bb); + let cond_val = self.linearize_expr(cond); + // After linearizing condition, current_bb may be different from cond_bb + // (e.g., if condition contains short-circuit operators like && or ||). + // Link the CURRENT block to body_bb and exit_bb. + if let Some(cond_end_bb) = self.current_bb { + self.emit(Instruction::cbr(cond_val, body_bb, exit_bb)); + self.link_bb(cond_end_bb, body_bb); + self.link_bb(cond_end_bb, exit_bb); + } + + // Exit block + self.switch_bb(exit_bb); + } + + pub(crate) fn linearize_for( + &mut self, + init: Option<&ForInit>, + cond: Option<&Expr>, + post: Option<&Expr>, + body: &Stmt, + ) { + // C99 for-loop declarations (e.g., for (int i = 0; ...)) are scoped to the loop. + self.push_scope(); + + // Init + if let Some(init) = init { + match init { + ForInit::Declaration(decl) => self.linearize_local_decl(decl), + ForInit::Expression(expr) => { + self.linearize_expr(expr); + } + } + } + + let cond_bb = self.alloc_bb(); + let body_bb = self.alloc_bb(); + let post_bb = self.alloc_bb(); + let exit_bb = self.alloc_bb(); + + // Jump to condition + if let Some(current) = self.current_bb { + if !self.is_terminated() { + self.emit(Instruction::br(cond_bb)); + self.link_bb(current, cond_bb); + } + } + + // Condition block + self.switch_bb(cond_bb); + if let Some(cond_expr) = cond { + let cond_val = self.linearize_expr(cond_expr); + // After linearizing condition, current_bb may be different from cond_bb + // (e.g., if condition contains short-circuit operators like && or ||). + // Link the CURRENT block to body_bb and exit_bb. + if let Some(cond_end_bb) = self.current_bb { + self.emit(Instruction::cbr(cond_val, body_bb, exit_bb)); + self.link_bb(cond_end_bb, body_bb); + self.link_bb(cond_end_bb, exit_bb); + } + } else { + // No condition = always true + self.emit(Instruction::br(body_bb)); + self.link_bb(cond_bb, body_bb); + // No link to exit_bb since we always enter the body + } + + // Body block + self.break_targets.push(exit_bb); + self.continue_targets.push(post_bb); + + self.switch_bb(body_bb); + self.linearize_stmt(body); + if !self.is_terminated() { + // After linearizing body, current_bb may be different from body_bb + if let Some(current) = self.current_bb { + self.emit(Instruction::br(post_bb)); + self.link_bb(current, post_bb); + } + } + + self.break_targets.pop(); + self.continue_targets.pop(); + + // Post block + self.switch_bb(post_bb); + if let Some(post_expr) = post { + self.linearize_expr(post_expr); + } + self.emit(Instruction::br(cond_bb)); + self.link_bb(post_bb, cond_bb); + + // Exit block + self.switch_bb(exit_bb); + + // Restore locals to remove for-loop-scoped declarations + self.pop_scope(); + } + + pub(crate) fn linearize_switch(&mut self, expr: &Expr, body: &Stmt) { + // Linearize the switch expression + let switch_val = self.linearize_expr(expr); + let expr_type = self.expr_type(expr); + let size = self.types.size_bits(expr_type); + + let exit_bb = self.alloc_bb(); + + // Push exit block for break handling + self.break_targets.push(exit_bb); + + // Collect case labels and create basic blocks for each + let (case_values, has_default) = self.collect_switch_cases(body); + let case_bbs: Vec = case_values.iter().map(|_| self.alloc_bb()).collect(); + let default_bb = if has_default { + Some(self.alloc_bb()) + } else { + None + }; + + // Build switch instruction with case -> block mapping + let switch_cases: Vec<(i64, BasicBlockId)> = case_values + .iter() + .zip(case_bbs.iter()) + .map(|(val, bb)| (*val, *bb)) + .collect(); + + // Default goes to default_bb if present, otherwise exit_bb + let default_target = default_bb.unwrap_or(exit_bb); + + // Emit switch instruction + self.emit(Instruction::switch_insn( + switch_val, + switch_cases.clone(), + Some(default_target), + size, + )); + + // Link CFG edges from current block to all case/default/exit blocks + if let Some(current) = self.current_bb { + for &(_, bb) in &switch_cases { + self.link_bb(current, bb); + } + self.link_bb(current, default_target); + if default_bb.is_none() { + self.link_bb(current, exit_bb); + } + } + + // Linearize body with case block switching + self.linearize_switch_body(body, &case_values, &case_bbs, default_bb); + + // If not terminated after body, jump to exit + if !self.is_terminated() { + if let Some(current) = self.current_bb { + self.emit(Instruction::br(exit_bb)); + self.link_bb(current, exit_bb); + } + } + + self.break_targets.pop(); + + // Exit block + self.switch_bb(exit_bb); + } + + /// Collect case values from switch body (must be a Block) + /// Returns (case_values, has_default) + pub(crate) fn collect_switch_cases(&self, body: &Stmt) -> (Vec, bool) { + let mut case_values = Vec::new(); + let mut has_default = false; + + if let Stmt::Block(items) = body { + for item in items { + if let BlockItem::Statement(stmt) = item { + self.collect_cases_from_stmt(stmt, &mut case_values, &mut has_default); + } + } + } + + (case_values, has_default) + } + + pub(crate) fn collect_cases_from_stmt( + &self, + stmt: &Stmt, + case_values: &mut Vec, + has_default: &mut bool, + ) { + match stmt { + Stmt::Case(expr) => { + // Extract constant value from case expression + if let Some(val) = self.eval_const_expr(expr) { + case_values.push(val as i64); // switch cases truncated to i64 + } + } + Stmt::Default => { + *has_default = true; + } + // Recursively check labeled statements + Stmt::Label { stmt, .. } => { + self.collect_cases_from_stmt(stmt, case_values, has_default); + } + _ => {} + } + } + + /// Evaluate a constant expression (for case labels, static initializers) + /// + /// C99 6.6 defines integer constant expressions. This function evaluates + /// expressions that can be computed at compile time. + pub(crate) fn eval_const_expr(&self, expr: &Expr) -> Option { + match &expr.kind { + ExprKind::IntLit(val) => Some(*val as i128), + ExprKind::Int128Lit(val) => Some(*val), + ExprKind::CharLit(c) => Some(*c as u8 as i8 as i128), + + ExprKind::Ident(symbol_id) => { + // Check if it's an enum constant + let sym = self.symbols.get(*symbol_id); + if sym.is_enum_constant() { + sym.enum_value.map(|v| v as i128) + } else { + None + } + } + + ExprKind::Unary { op, operand } => { + let val = self.eval_const_expr(operand)?; + match op { + UnaryOp::Neg => Some(val.wrapping_neg()), + UnaryOp::Not => Some(if val == 0 { 1 } else { 0 }), + UnaryOp::BitNot => Some(!val), + _ => None, + } + } + + ExprKind::Binary { op, left, right } => { + let l = self.eval_const_expr(left)?; + let r = self.eval_const_expr(right)?; + match op { + BinaryOp::Add => Some(l.wrapping_add(r)), + BinaryOp::Sub => Some(l.wrapping_sub(r)), + BinaryOp::Mul => Some(l.wrapping_mul(r)), + BinaryOp::Div => { + if r != 0 { + Some(l / r) + } else { + None + } + } + BinaryOp::Mod => { + if r != 0 { + Some(l % r) + } else { + None + } + } + BinaryOp::BitAnd => Some(l & r), + BinaryOp::BitOr => Some(l | r), + BinaryOp::BitXor => Some(l ^ r), + BinaryOp::Shl | BinaryOp::Shr => { + // Mask shift amount to match C semantics and target hardware behavior. + // 8/16/32-bit: mask 31, 64-bit: mask 63, 128-bit: mask 127. + let size_bits = left.typ.map(|t| self.types.size_bits(t)).unwrap_or(32); + let mask: i128 = if size_bits > 64 { + 127 + } else if size_bits > 32 { + 63 + } else { + 31 + }; + let shift_amt = (r & mask) as u32; + match op { + BinaryOp::Shl => Some(l << shift_amt), + BinaryOp::Shr => Some(l >> shift_amt), + _ => unreachable!(), + } + } + BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge => { + // Use unsigned comparison when either operand is unsigned. + // C promotes both to the common unsigned type for comparison. + let left_unsigned = left.typ.is_some_and(|t| { + self.types.modifiers(t).contains(TypeModifiers::UNSIGNED) + }); + let right_unsigned = right.typ.is_some_and(|t| { + self.types.modifiers(t).contains(TypeModifiers::UNSIGNED) + }); + let use_unsigned = left_unsigned || right_unsigned; + if use_unsigned { + let lu = l as u128; + let ru = r as u128; + Some(match op { + BinaryOp::Lt => { + if lu < ru { + 1 + } else { + 0 + } + } + BinaryOp::Le => { + if lu <= ru { + 1 + } else { + 0 + } + } + BinaryOp::Gt => { + if lu > ru { + 1 + } else { + 0 + } + } + BinaryOp::Ge => { + if lu >= ru { + 1 + } else { + 0 + } + } + _ => unreachable!(), + }) + } else { + Some(match op { + BinaryOp::Lt => { + if l < r { + 1 + } else { + 0 + } + } + BinaryOp::Le => { + if l <= r { + 1 + } else { + 0 + } + } + BinaryOp::Gt => { + if l > r { + 1 + } else { + 0 + } + } + BinaryOp::Ge => { + if l >= r { + 1 + } else { + 0 + } + } + _ => unreachable!(), + }) + } + } + BinaryOp::Eq => Some(if l == r { 1 } else { 0 }), + BinaryOp::Ne => Some(if l != r { 1 } else { 0 }), + BinaryOp::LogAnd => Some(if l != 0 && r != 0 { 1 } else { 0 }), + BinaryOp::LogOr => Some(if l != 0 || r != 0 { 1 } else { 0 }), + } + } + + ExprKind::Conditional { + cond, + then_expr, + else_expr, + } => { + let cond_val = self.eval_const_expr(cond)?; + if cond_val != 0 { + self.eval_const_expr(then_expr) + } else { + self.eval_const_expr(else_expr) + } + } + + // sizeof(type) - constant for complete types + ExprKind::SizeofType(type_id) => { + let size_bits = self.types.size_bits(*type_id); + Some((size_bits / 8) as i128) + } + + // sizeof(expr) - constant if expr type is complete + ExprKind::SizeofExpr(inner_expr) => { + if let Some(typ) = inner_expr.typ { + let size_bits = self.types.size_bits(typ); + Some((size_bits / 8) as i128) + } else { + None + } + } + + // _Alignof(type) - constant for complete types + ExprKind::AlignofType(type_id) => { + let align = self.types.alignment(*type_id); + Some(align as i128) + } + + // _Alignof(expr) - constant if expr type is complete + ExprKind::AlignofExpr(inner_expr) => { + if let Some(typ) = inner_expr.typ { + let align = self.types.alignment(typ); + Some(align as i128) + } else { + None + } + } + + // Cast to integer type - evaluate inner expression + ExprKind::Cast { expr: inner, .. } => self.eval_const_expr(inner), + + // __builtin_offsetof(type, member-designator) - compile-time constant + ExprKind::OffsetOf { type_id, path } => { + let mut offset: u64 = 0; + let mut current_type = *type_id; + + for element in path { + match element { + OffsetOfPath::Field(field_id) => { + let struct_type = self.resolve_struct_type(current_type); + if let Some(member_info) = + self.types.find_member(struct_type, *field_id) + { + offset += member_info.offset as u64; + current_type = member_info.typ; + } else { + return None; // Field not found + } + } + OffsetOfPath::Index(index) => { + if let Some(elem_type) = self.types.base_type(current_type) { + let elem_size = self.types.size_bytes(elem_type); + offset += (*index as u64) * (elem_size as u64); + current_type = elem_type; + } else { + return None; // Not an array type + } + } + } + } + + Some(offset as i128) + } + + _ => None, + } + } + + /// Evaluate a constant floating-point expression at compile time. + /// Handles float literals, negation, and binary arithmetic on floats. + pub(crate) fn eval_const_float_expr(&self, expr: &Expr) -> Option { + match &expr.kind { + ExprKind::FloatLit(v) => Some(*v), + ExprKind::IntLit(v) => Some(*v as f64), + ExprKind::CharLit(c) => Some(*c as i64 as f64), + + ExprKind::Unary { op, operand } => { + let val = self.eval_const_float_expr(operand)?; + match op { + UnaryOp::Neg => Some(-val), + _ => None, + } + } + + ExprKind::Binary { op, left, right } => { + let l = self.eval_const_float_expr(left)?; + let r = self.eval_const_float_expr(right)?; + match op { + BinaryOp::Add => Some(l + r), + BinaryOp::Sub => Some(l - r), + BinaryOp::Mul => Some(l * r), + BinaryOp::Div => Some(l / r), + _ => None, + } + } + + ExprKind::Cast { expr: inner, .. } => self.eval_const_float_expr(inner), + + _ => None, + } + } + + /// Evaluate a static address expression (for initializers like `&symbol.field`) + /// + /// Returns Some((symbol_name, offset)) if the expression is a valid static address, + /// or None if it can't be computed at compile time. + pub(crate) fn eval_static_address(&self, expr: &Expr) -> Option<(String, i64)> { + match &expr.kind { + // Simple identifier: &symbol + ExprKind::Ident(symbol_id) => { + let name_str = self.symbol_name(*symbol_id); + // Check if this is a static local variable + let key = format!("{}.{}", self.current_func_name, name_str); + if let Some(static_info) = self.static_locals.get(&key) { + Some((static_info.global_name.clone(), 0)) + } else { + Some((name_str, 0)) + } + } + + // Member access: expr.member + ExprKind::Member { expr: base, member } => { + // Recursively evaluate the base address + let (name, base_offset) = self.eval_static_address(base)?; + + // Get the offset of the member in the struct + let base_type = base.typ?; + let struct_type = self.resolve_struct_type(base_type); + let member_info = self.types.find_member(struct_type, *member)?; + + Some((name, base_offset + member_info.offset as i64)) + } + + // Arrow access: expr->member (pointer dereference + member access) + // Can be a static address when the pointer is a static address-of expression + // (e.g., (&static_struct.field)->subfield in CPython macros) + ExprKind::Arrow { expr: base, member } => { + let (name, base_offset) = self.eval_static_address(base)?; + let ptr_type = base.typ?; + let pointee_type = self.types.base_type(ptr_type)?; + let struct_type = self.resolve_struct_type(pointee_type); + let member_info = self.types.find_member(struct_type, *member)?; + Some((name, base_offset + member_info.offset as i64)) + } + + // Array subscript: array[index] + ExprKind::Index { array, index } => { + // Recursively evaluate the base address + let (name, base_offset) = self.eval_static_address(array)?; + + // Get the index as a constant + let idx = self.eval_const_expr(index)?; + + // Get the element size + let array_type = array.typ?; + let elem_type = self.types.base_type(array_type)?; + let elem_size = self.types.size_bytes(elem_type) as i64; + + Some((name, base_offset + idx as i64 * elem_size)) + } + + // Address-of: &expr → same as evaluating expr as static address + ExprKind::Unary { + op: UnaryOp::AddrOf, + operand, + } => self.eval_static_address(operand), + + // Cast - evaluate the inner expression + ExprKind::Cast { expr: inner, .. } => self.eval_static_address(inner), + + // Binary add/sub with pointer operand: ptr + int or ptr - int + ExprKind::Binary { + op: op @ (BinaryOp::Add | BinaryOp::Sub), + left, + right, + } => { + // Determine which side is the pointer and which is the integer + let (ptr_expr, int_expr, is_sub) = if left.typ.is_some_and(|t| { + self.types.kind(t) == TypeKind::Pointer || self.types.kind(t) == TypeKind::Array + }) { + (left.as_ref(), right.as_ref(), *op == BinaryOp::Sub) + } else if right.typ.is_some_and(|t| { + self.types.kind(t) == TypeKind::Pointer || self.types.kind(t) == TypeKind::Array + }) && *op == BinaryOp::Add + { + (right.as_ref(), left.as_ref(), false) + } else { + return None; + }; + + let (name, base_offset) = self.eval_static_address(ptr_expr)?; + let int_val = self.eval_const_expr(int_expr)?; + + // Scale by pointee size for pointer arithmetic + let pointee_size = ptr_expr + .typ + .and_then(|t| self.types.base_type(t)) + .map(|t| self.types.size_bytes(t) as i64) + .unwrap_or(1); + let byte_offset = if is_sub { + base_offset - int_val as i64 * pointee_size + } else { + base_offset + int_val as i64 * pointee_size + }; + + Some((name, byte_offset)) + } + + _ => None, + } + } + + /// Linearize switch body, switching basic blocks at case/default labels + pub(crate) fn linearize_switch_body( + &mut self, + body: &Stmt, + case_values: &[i64], + case_bbs: &[BasicBlockId], + default_bb: Option, + ) { + let mut case_idx = 0; + + if let Stmt::Block(items) = body { + for item in items { + match item { + BlockItem::Declaration(decl) => self.linearize_local_decl(decl), + BlockItem::Statement(stmt) => { + self.linearize_switch_stmt( + stmt, + case_values, + case_bbs, + default_bb, + &mut case_idx, + ); + } + } + } + } + } + + pub(crate) fn linearize_switch_stmt( + &mut self, + stmt: &Stmt, + case_values: &[i64], + case_bbs: &[BasicBlockId], + default_bb: Option, + case_idx: &mut usize, + ) { + match stmt { + Stmt::Case(expr) => { + // Find the matching case block + if let Some(val) = self.eval_const_expr(expr) { + if let Some(idx) = case_values.iter().position(|v| *v as i128 == val) { + let case_bb = case_bbs[idx]; + + // Fall through from previous case if not terminated + if !self.is_terminated() { + if let Some(current) = self.current_bb { + self.emit(Instruction::br(case_bb)); + self.link_bb(current, case_bb); + } + } + + self.switch_bb(case_bb); + *case_idx = idx + 1; + } + } + } + Stmt::Default => { + if let Some(def_bb) = default_bb { + // Fall through from previous case if not terminated + if !self.is_terminated() { + if let Some(current) = self.current_bb { + self.emit(Instruction::br(def_bb)); + self.link_bb(current, def_bb); + } + } + + self.switch_bb(def_bb); + } + } + _ => { + // Regular statement - linearize it + self.linearize_stmt(stmt); + } + } + } + + // ======================================================================== + // Inline assembly linearization + // ======================================================================== + + /// Linearize an inline assembly statement + /// Check if an expression is a simple identifier that's a parameter (in var_map) + /// Returns Some((name, pseudo)) if it is, None otherwise + pub(crate) fn get_param_if_ident(&self, expr: &Expr) -> Option<(String, PseudoId)> { + if let ExprKind::Ident(symbol_id) = &expr.kind { + let name_str = self.symbol_name(*symbol_id); + if let Some(&pseudo) = self.var_map.get(&name_str) { + return Some((name_str, pseudo)); + } + } + None + } + + pub(crate) fn linearize_asm( + &mut self, + template: &str, + outputs: &[AsmOperand], + inputs: &[AsmOperand], + clobbers: &[String], + goto_labels: &[StringId], + ) { + let mut ir_outputs = Vec::new(); + let mut ir_inputs = Vec::new(); + // Track which outputs are parameters (need var_map update instead of store) + let mut param_outputs: Vec> = Vec::new(); + + // Process output operands + for op in outputs { + // Create a pseudo for the output + let pseudo = self.alloc_pseudo(); + + // Parse constraint to get flags + let (_is_memory, is_readwrite, _matching) = self.parse_asm_constraint(&op.constraint); + + // Get symbolic name if present + let name = op.name.map(|n| self.str(n).to_string()); + + // Check if this output is a parameter (SSA value, not memory location) + let param_info = self.get_param_if_ident(&op.expr); + + let typ = self.expr_type(&op.expr); + let size = self.types.size_bits(typ); + + // For read-write outputs ("+r"), load the initial value into the SAME pseudo + // so that input and output use the same register + if is_readwrite { + if let Some((_, param_pseudo)) = ¶m_info { + // Parameter: copy value directly (no memory address) + self.emit( + Instruction::new(Opcode::Copy) + .with_target(pseudo) + .with_src(*param_pseudo) + .with_type(typ) + .with_size(size), + ); + } else { + // Local or global: load from memory address + let addr = self.linearize_lvalue(&op.expr); + self.emit(Instruction::load(pseudo, addr, 0, typ, size)); + } + + // Also add as input, using the SAME pseudo and marking as matching + // the output. Per GCC convention, '+' creates one operand number + // shared by both output and input. + ir_inputs.push(AsmConstraint { + pseudo, // Same pseudo as output - ensures same register + name: name.clone(), + matching_output: Some(ir_outputs.len()), // matches the output about to be pushed + constraint: op.constraint.clone(), + size, + }); + } + + ir_outputs.push(AsmConstraint { + pseudo, + name, + matching_output: None, + constraint: op.constraint.clone(), + size, + }); + + // Track if this is a parameter output + param_outputs.push(param_info.map(|(name, _)| name)); + } + + // Process input operands + for op in inputs { + let (is_memory, _, matching) = self.parse_asm_constraint(&op.constraint); + + // Get symbolic name if present + let name = op.name.map(|n| self.str(n).to_string()); + + let typ = self.expr_type(&op.expr); + let size = self.types.size_bits(typ); + + // For matching constraints (like "0"), we need to load the input value + // into the matched output's pseudo so they use the same register + let pseudo = if let Some(match_idx) = matching { + if match_idx < ir_outputs.len() { + // Use the matched output's pseudo + let out_pseudo = ir_outputs[match_idx].pseudo; + // Load the input value into the output's pseudo + let val = self.linearize_expr(&op.expr); + // Copy val to out_pseudo so they share the same register + self.emit( + Instruction::new(Opcode::Copy) + .with_target(out_pseudo) + .with_src(val) + .with_type(typ) + .with_size(size), + ); + out_pseudo + } else { + self.linearize_expr(&op.expr) + } + } else if is_memory { + // For memory operands, get the address + self.linearize_lvalue(&op.expr) + } else { + // For register operands, evaluate the expression + self.linearize_expr(&op.expr) + }; + + ir_inputs.push(AsmConstraint { + pseudo, + name, + matching_output: matching, + constraint: op.constraint.clone(), + size, + }); + } + + // Process goto labels - map label names to BasicBlockIds + let ir_goto_labels: Vec<(BasicBlockId, String)> = goto_labels + .iter() + .map(|label_id| { + let label_name = self.str(*label_id).to_string(); + let bb = self.get_or_create_label(&label_name); + (bb, label_name) + }) + .collect(); + + // Create the asm data + let asm_data = AsmData { + template: template.to_string(), + outputs: ir_outputs.clone(), + inputs: ir_inputs, + clobbers: clobbers.to_vec(), + goto_labels: ir_goto_labels.clone(), + }; + + // Emit the asm instruction + self.emit(Instruction::asm(asm_data)); + + // For asm goto: add edges to all possible label targets + // The asm may jump to any of these labels, so control flow can go there + if !ir_goto_labels.is_empty() { + if let Some(current) = self.current_bb { + // After the asm instruction, we need a basic block for fall-through + // and edges to all goto targets + let fall_through = self.alloc_bb(); + + // Add edges to all goto label targets + for (target_bb, _) in &ir_goto_labels { + self.link_bb(current, *target_bb); + } + + // Add edge to fall-through (normal case when asm doesn't jump) + self.link_bb(current, fall_through); + + // Emit an explicit branch to the fallthrough block + // This is necessary because the asm goto acts as a conditional terminator + // Without this, code would fall through to whatever block comes next in layout + self.emit(Instruction::br(fall_through)); + + // Switch to fall-through block for subsequent instructions + self.current_bb = Some(fall_through); + } + } + + // Store outputs back to their destinations + // store(value, addr, ...) - value first, then address + for (i, op) in outputs.iter().enumerate() { + let out_pseudo = ir_outputs[i].pseudo; + + // Check if this output is a parameter (update var_map instead of memory store) + if let Some(param_name) = ¶m_outputs[i] { + // Parameter: update var_map with the new SSA value + self.var_map.insert(param_name.clone(), out_pseudo); + } else { + // Local or global: store to memory address + let addr = self.linearize_lvalue(&op.expr); + let typ = self.expr_type(&op.expr); + let size = self.types.size_bits(typ); + self.emit(Instruction::store(out_pseudo, addr, 0, typ, size)); + } + } + } + + /// Parse an asm constraint string to extract flags + /// Returns (is_memory, is_readwrite, matching_output) + /// Note: Early clobber (&) is parsed but not used since our simple register + /// allocator doesn't share registers between inputs and outputs anyway + pub(crate) fn parse_asm_constraint(&self, constraint: &str) -> (bool, bool, Option) { + let mut is_memory = false; + let mut is_readwrite = false; + let mut matching = None; + + for c in constraint.chars() { + match c { + '+' => is_readwrite = true, + '&' | '=' | '%' => {} // Early clobber, output-only, commutative + 'r' | 'a' | 'b' | 'c' | 'd' | 'S' | 'D' => {} // Register constraints + 'm' | 'o' | 'V' | 'Q' => is_memory = true, + 'i' | 'n' | 'g' | 'X' => {} // Immediate, general + '0'..='9' => matching = Some((c as u8 - b'0') as usize), + _ => {} // Ignore unknown constraints + } + } + + (is_memory, is_readwrite, matching) + } + + pub(crate) fn get_or_create_label(&mut self, name: &str) -> BasicBlockId { + if let Some(&bb) = self.label_map.get(name) { + bb + } else { + let bb = self.alloc_bb(); + self.label_map.insert(name.to_string(), bb); + // Set label name on the block + let block = self.get_or_create_bb(bb); + block.label = Some(name.to_string()); + bb + } + } +} diff --git a/cc/ir/mod.rs b/cc/ir/mod.rs index a5038132..b7c2f977 100644 --- a/cc/ir/mod.rs +++ b/cc/ir/mod.rs @@ -19,6 +19,9 @@ pub mod dominate; pub mod inline; pub mod instcombine; pub mod linearize; +mod linearize_emit; +mod linearize_init; +mod linearize_stmt; pub mod lower; pub mod ssa; @@ -453,7 +456,7 @@ impl fmt::Display for MemoryOrder { // ============================================================================ /// Unique ID for a pseudo (virtual register) -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct PseudoId(pub u32); impl fmt::Display for PseudoId { @@ -463,9 +466,10 @@ impl fmt::Display for PseudoId { } /// Type of pseudo value -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub enum PseudoKind { /// Void (no value) + #[default] Void, /// Undefined value Undef, @@ -484,7 +488,7 @@ pub enum PseudoKind { } /// A pseudo (virtual register or value) in SSA form -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Pseudo { pub id: PseudoId, pub kind: PseudoKind, @@ -498,16 +502,6 @@ impl PartialEq for Pseudo { } } -impl Default for Pseudo { - fn default() -> Self { - Self { - id: PseudoId(0), - kind: PseudoKind::Void, - name: None, - } - } -} - impl Pseudo { /// Create an undefined pseudo pub fn undef(id: PseudoId) -> Self { @@ -1722,7 +1716,7 @@ impl GlobalDef { // ============================================================================ /// A module containing multiple functions -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Module { /// Functions pub functions: Vec, @@ -1749,22 +1743,6 @@ pub struct Module { } impl Module { - /// Create a new module - pub fn new() -> Self { - Self { - functions: Vec::new(), - globals: Vec::new(), - strings: Vec::new(), - wide_strings: Vec::new(), - debug: false, - source_files: Vec::new(), - extern_symbols: HashSet::new(), - extern_tls_symbols: HashSet::new(), - comp_dir: None, - source_name: None, - } - } - /// Add a function pub fn add_function(&mut self, func: Function) { self.functions.push(func); @@ -1857,12 +1835,6 @@ impl Module { } } -impl Default for Module { - fn default() -> Self { - Self::new() - } -} - impl fmt::Display for Module { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Globals @@ -2060,7 +2032,7 @@ mod tests { #[test] fn test_module() { let types = TypeTable::new(&Target::host()); - let mut module = Module::new(); + let mut module = Module::default(); module.add_global("counter", types.int_id, Initializer::Int(0)); @@ -2073,7 +2045,7 @@ mod tests { #[test] fn test_module_extern_symbols() { - let mut module = Module::new(); + let mut module = Module::default(); // New module should have empty extern_symbols assert!(module.extern_symbols.is_empty()); @@ -2181,7 +2153,7 @@ mod tests { #[test] fn test_add_global_aligned_tentative_definition() { let types = TypeTable::new(&Target::host()); - let mut module = Module::new(); + let mut module = Module::default(); // Add a tentative definition (no initializer) module.add_global_aligned("x", types.int_id, Initializer::None, None, false); @@ -2198,7 +2170,7 @@ mod tests { #[test] fn test_add_global_aligned_non_tentative_not_replaced() { let types = TypeTable::new(&Target::host()); - let mut module = Module::new(); + let mut module = Module::default(); // Add a real definition (with initializer) module.add_global_aligned("x", types.int_id, Initializer::Int(10), None, false); @@ -2212,7 +2184,7 @@ mod tests { #[test] fn test_add_global_tls_aligned_tentative_definition() { let types = TypeTable::new(&Target::host()); - let mut module = Module::new(); + let mut module = Module::default(); // Add a TLS tentative definition module.add_global_tls_aligned("tls_var", types.int_id, Initializer::None, None, false); diff --git a/cc/ir/test_linearize.rs b/cc/ir/test_linearize.rs index 52ab2e6f..6b03f6bb 100644 --- a/cc/ir/test_linearize.rs +++ b/cc/ir/test_linearize.rs @@ -15,8 +15,8 @@ use super::*; use crate::parse::ast::{ - AssignOp, BinaryOp, BlockItem, Declaration, Designator, ExprKind, ExternalDecl, FunctionDef, - InitDeclarator, InitElement, Parameter, UnaryOp, + AssignOp, BinaryOp, BlockItem, Declaration, Designator, ExprKind, ExternalDecl, ForInit, + FunctionDef, InitDeclarator, InitElement, Parameter, Stmt, UnaryOp, }; use crate::strings::StringTable; use crate::symbol::Symbol; diff --git a/cc/parse/ast.rs b/cc/parse/ast.rs index b2492595..a468e2c1 100644 --- a/cc/parse/ast.rs +++ b/cc/parse/ast.rs @@ -1032,27 +1032,17 @@ pub enum ExternalDecl { } /// A translation unit (entire source file) -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct TranslationUnit { pub items: Vec, } impl TranslationUnit { - pub fn new() -> Self { - TranslationUnit { items: Vec::new() } - } - pub fn add(&mut self, item: ExternalDecl) { self.items.push(item); } } -impl Default for TranslationUnit { - fn default() -> Self { - Self::new() - } -} - // ============================================================================ // Tests // ============================================================================ @@ -1311,7 +1301,7 @@ mod tests { let x_sym = symbols .declare(Symbol::variable(x_name, types.int_id, 0)) .unwrap(); - let mut tu = TranslationUnit::new(); + let mut tu = TranslationUnit::default(); // Add a declaration let decl = Declaration::simple(x_sym, types.int_id, None); diff --git a/cc/parse/expression.rs b/cc/parse/expression.rs index fd97274b..614b6d36 100644 --- a/cc/parse/expression.rs +++ b/cc/parse/expression.rs @@ -1731,6 +1731,951 @@ impl<'a> Parser<'a> { } } + /// Try to parse a builtin function expression. + /// Returns `Some(result)` if `name_id` is a recognized builtin, `None` otherwise. + fn parse_builtin_expr( + &mut self, + name_id: StringId, + token_pos: Position, + ) -> Option> { + match name_id { + crate::kw::BUILTIN_VA_START => Some((|| { + // __builtin_va_start(ap, last_param) + self.expect_special(b'(')?; + let ap = self.parse_assignment_expr()?; + self.expect_special(b',')?; + // Second arg is a parameter name + let last_param = self.expect_identifier()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::VaStart { + ap: Box::new(ap), + last_param, + }, + self.types.void_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_VA_ARG => Some((|| { + // __builtin_va_arg(ap, type) + self.expect_special(b'(')?; + let ap = self.parse_assignment_expr()?; + self.expect_special(b',')?; + // Second arg is a type + let arg_type = self.parse_type_name()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::VaArg { + ap: Box::new(ap), + arg_type, + }, + arg_type, + token_pos, + )) + })()), + crate::kw::BUILTIN_VA_END => Some((|| { + // __builtin_va_end(ap) + self.expect_special(b'(')?; + let ap = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::VaEnd { ap: Box::new(ap) }, + self.types.void_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_VA_COPY => Some((|| { + // __builtin_va_copy(dest, src) + self.expect_special(b'(')?; + let dest = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let src = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::VaCopy { + dest: Box::new(dest), + src: Box::new(src), + }, + self.types.void_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_BSWAP16 => Some((|| { + // __builtin_bswap16(x) - returns uint16_t + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Bswap16 { arg: Box::new(arg) }, + self.types.ushort_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_BSWAP32 => Some((|| { + // __builtin_bswap32(x) - returns uint32_t + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Bswap32 { arg: Box::new(arg) }, + self.types.uint_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_BSWAP64 => Some((|| { + // __builtin_bswap64(x) - returns uint64_t + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Bswap64 { arg: Box::new(arg) }, + self.types.ulonglong_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_CTZ => Some((|| { + // __builtin_ctz(x) - returns int, counts trailing zeros in unsigned int + // Result is undefined if x is 0 + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Ctz { arg: Box::new(arg) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_CTZL => Some((|| { + // __builtin_ctzl(x) - returns int, counts trailing zeros in unsigned long + // Result is undefined if x is 0 + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Ctzl { arg: Box::new(arg) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_CTZLL => Some((|| { + // __builtin_ctzll(x) - returns int, counts trailing zeros in unsigned long long + // Result is undefined if x is 0 + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Ctzll { arg: Box::new(arg) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_CLZ => Some((|| { + // __builtin_clz(x) - returns int, counts leading zeros in unsigned int + // Result is undefined if x is 0 + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Clz { arg: Box::new(arg) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_CLZL => Some((|| { + // __builtin_clzl(x) - returns int, counts leading zeros in unsigned long + // Result is undefined if x is 0 + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Clzl { arg: Box::new(arg) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_CLZLL => Some((|| { + // __builtin_clzll(x) - returns int, counts leading zeros in unsigned long long + // Result is undefined if x is 0 + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Clzll { arg: Box::new(arg) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_POPCOUNT => Some((|| { + // __builtin_popcount(x) - returns int, counts set bits in unsigned int + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Popcount { arg: Box::new(arg) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_POPCOUNTL => Some((|| { + // __builtin_popcountl(x) - returns int, counts set bits in unsigned long + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Popcountl { arg: Box::new(arg) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_POPCOUNTLL => Some((|| { + // __builtin_popcountll(x) - returns int, counts set bits in unsigned long long + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Popcountll { arg: Box::new(arg) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_ALLOCA => Some((|| { + // __builtin_alloca(size) - returns void* + self.expect_special(b'(')?; + let size = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Alloca { + size: Box::new(size), + }, + self.types.void_ptr_id, + token_pos, + )) + })()), + // Memory builtins - generate calls to C library functions + crate::kw::BUILTIN_MEMSET => Some((|| { + // __builtin_memset(dest, c, n) - returns void* + self.expect_special(b'(')?; + let dest = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let c = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let n = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Memset { + dest: Box::new(dest), + c: Box::new(c), + n: Box::new(n), + }, + self.types.void_ptr_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_MEMCPY => Some((|| { + // __builtin_memcpy(dest, src, n) - returns void* + self.expect_special(b'(')?; + let dest = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let src = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let n = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Memcpy { + dest: Box::new(dest), + src: Box::new(src), + n: Box::new(n), + }, + self.types.void_ptr_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_MEMMOVE => Some((|| { + // __builtin_memmove(dest, src, n) - returns void* + self.expect_special(b'(')?; + let dest = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let src = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let n = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Memmove { + dest: Box::new(dest), + src: Box::new(src), + n: Box::new(n), + }, + self.types.void_ptr_id, + token_pos, + )) + })()), + // Infinity builtins - return float constants + crate::kw::BUILTIN_INF | crate::kw::BUILTIN_HUGE_VAL => Some((|| { + self.expect_special(b'(')?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::FloatLit(f64::INFINITY), + self.types.double_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_INFF | crate::kw::BUILTIN_HUGE_VALF => Some((|| { + self.expect_special(b'(')?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::FloatLit(f64::INFINITY), + self.types.float_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_INFL | crate::kw::BUILTIN_HUGE_VALL => Some((|| { + self.expect_special(b'(')?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::FloatLit(f64::INFINITY), + self.types.longdouble_id, + token_pos, + )) + })()), + // NaN builtins - returns quiet NaN + // The string argument is typically empty "" for quiet NaN + crate::kw::BUILTIN_NAN | crate::kw::BUILTIN_NANS => Some((|| { + self.expect_special(b'(')?; + let _arg = self.parse_assignment_expr()?; // string argument (ignored) + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::FloatLit(f64::NAN), + self.types.double_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_NANF | crate::kw::BUILTIN_NANSF => Some((|| { + self.expect_special(b'(')?; + let _arg = self.parse_assignment_expr()?; // string argument (ignored) + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::FloatLit(f64::NAN), + self.types.float_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_NANL | crate::kw::BUILTIN_NANSL => Some((|| { + self.expect_special(b'(')?; + let _arg = self.parse_assignment_expr()?; // string argument (ignored) + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::FloatLit(f64::NAN), + self.types.longdouble_id, + token_pos, + )) + })()), + // FLT_ROUNDS - returns current rounding mode (1 = to nearest) + crate::kw::BUILTIN_FLT_ROUNDS => Some((|| { + self.expect_special(b'(')?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::IntLit(1), // IEEE 754 default: round to nearest + self.types.int_id, + token_pos, + )) + })()), + // Fabs builtins - absolute value for floats + crate::kw::BUILTIN_FABS => Some((|| { + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Fabs { arg: Box::new(arg) }, + self.types.double_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_FABSF => Some((|| { + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Fabsf { arg: Box::new(arg) }, + self.types.float_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_FABSL => Some((|| { + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Fabsl { arg: Box::new(arg) }, + self.types.longdouble_id, + token_pos, + )) + })()), + // Signbit builtins - test sign bit of floats + crate::kw::BUILTIN_SIGNBIT => Some((|| { + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Signbit { arg: Box::new(arg) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_SIGNBITF => Some((|| { + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Signbitf { arg: Box::new(arg) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_SIGNBITL => Some((|| { + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Signbitl { arg: Box::new(arg) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_UNREACHABLE => Some((|| { + // __builtin_unreachable() - marks code as unreachable + // Takes no arguments, returns void + // Behavior is undefined if actually reached at runtime + self.expect_special(b'(')?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Unreachable, + self.types.void_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_CONSTANT_P => Some((|| { + // __builtin_constant_p(expr) - returns 1 if expr is a constant, 0 otherwise + // This is evaluated at compile time, not runtime + self.expect_special(b'(')?; + let arg = self.parse_assignment_expr()?; + self.expect_special(b')')?; + // Check if the argument is a constant expression + let is_constant = self.eval_const_expr(&arg).is_some(); + Ok(Self::typed_expr( + ExprKind::IntLit(if is_constant { 1 } else { 0 }), + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_EXPECT => Some((|| { + // __builtin_expect(expr, c) - branch prediction hint + // Returns expr, the second argument is the expected value (for optimization hints) + // We just return expr since we don't do branch prediction optimization + self.expect_special(b'(')?; + let expr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let _expected = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(expr) + })()), + crate::kw::BUILTIN_ASSUME_ALIGNED => Some((|| { + // __builtin_assume_aligned(ptr, align) or + // __builtin_assume_aligned(ptr, align, offset) + // Returns ptr, hints that ptr is aligned to align bytes + // We just return ptr since we don't do alignment optimization + self.expect_special(b'(')?; + let ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let _align = self.parse_assignment_expr()?; + // Optional third argument (offset) + if self.peek_special() == Some(b',' as u32) { + self.expect_special(b',')?; + let _offset = self.parse_assignment_expr()?; + } + self.expect_special(b')')?; + Ok(ptr) + })()), + crate::kw::BUILTIN_PREFETCH => Some((|| { + // __builtin_prefetch(addr) or + // __builtin_prefetch(addr, rw) or + // __builtin_prefetch(addr, rw, locality) + // Prefetch data at addr into cache - no-op for correctness + self.expect_special(b'(')?; + let _addr = self.parse_assignment_expr()?; + // Optional rw argument (0=read, 1=write) + if self.peek_special() == Some(b',' as u32) { + self.expect_special(b',')?; + let _rw = self.parse_assignment_expr()?; + // Optional locality argument (0-3) + if self.peek_special() == Some(b',' as u32) { + self.expect_special(b',')?; + let _locality = self.parse_assignment_expr()?; + } + } + self.expect_special(b')')?; + // Returns void - just return a void expression + Ok(Self::typed_expr( + ExprKind::IntLit(0), + self.types.void_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_TYPES_COMPATIBLE_P => Some((|| { + // __builtin_types_compatible_p(type1, type2) - returns 1 if types are compatible + // This is evaluated at compile time, ignoring top-level qualifiers + self.expect_special(b'(')?; + let type1 = self.parse_type_name()?; + self.expect_special(b',')?; + let type2 = self.parse_type_name()?; + self.expect_special(b')')?; + // Check type compatibility (ignoring qualifiers) + let compatible = self.types.types_compatible(type1, type2); + Ok(Self::typed_expr( + ExprKind::IntLit(if compatible { 1 } else { 0 }), + self.types.int_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_FRAME_ADDRESS => Some((|| { + // __builtin_frame_address(level) - returns void*, address of frame at level + // Level 0 is the current frame, 1 is the caller's frame, etc. + // Returns NULL for invalid levels (beyond stack bounds) + self.expect_special(b'(')?; + let level = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::FrameAddress { + level: Box::new(level), + }, + self.types.void_ptr_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_RETURN_ADDRESS => Some((|| { + // __builtin_return_address(level) - returns void*, return address at level + // Level 0 is the current function's return address + // Returns NULL for invalid levels (beyond stack bounds) + self.expect_special(b'(')?; + let level = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::ReturnAddress { + level: Box::new(level), + }, + self.types.void_ptr_id, + token_pos, + )) + })()), + crate::kw::SETJMP | crate::kw::SETJMP2 => Some((|| { + // setjmp(env) - saves execution context, returns int + // Returns 0 on direct call, non-zero when returning via longjmp + self.expect_special(b'(')?; + let env = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Setjmp { env: Box::new(env) }, + self.types.int_id, + token_pos, + )) + })()), + crate::kw::LONGJMP | crate::kw::LONGJMP2 => Some((|| { + // longjmp(env, val) - restores execution context (never returns) + // Causes corresponding setjmp to return val (or 1 if val == 0) + self.expect_special(b'(')?; + let env = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let val = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::Longjmp { + env: Box::new(env), + val: Box::new(val), + }, + self.types.void_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_OFFSETOF | crate::kw::OFFSETOF => Some((|| { + // __builtin_offsetof(type, member-designator) + // Returns the byte offset of a member within a struct/union + // member-designator can be .field or [index] chains + self.expect_special(b'(')?; + // Parse the type name + let type_id = self.parse_type_name()?; + self.expect_special(b',')?; + // Parse member-designator starting with field name (no dot prefix for first field) + // Subsequent components use .field or [index] syntax + let mut path = Vec::new(); + // Expect identifier for first member + let first_field = self.expect_identifier()?; + path.push(OffsetOfPath::Field(first_field)); + // Parse subsequent designators + loop { + if self.is_special(b'.') { + self.advance(); + let field = self.expect_identifier()?; + path.push(OffsetOfPath::Field(field)); + } else if self.is_special(b'[') { + self.advance(); + // Parse constant expression for index + let index_expr = self.parse_conditional_expr()?; + let index_pos = index_expr.pos; + self.expect_special(b']')?; + // Evaluate as constant - offsetof requires compile-time constant + let index_val = self.eval_const_expr(&index_expr).ok_or_else(|| { + ParseError::new( + "array index in offsetof must be a constant expression", + index_pos, + ) + })?; + path.push(OffsetOfPath::Index(index_val as i64)); + } else { + break; + } + } + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::OffsetOf { type_id, path }, + self.types.ulong_id, // size_t is typically unsigned long + token_pos, + )) + })()), + // ================================================================ + // Atomic builtins (Clang __c11_atomic_* for C11 stdatomic.h) + // ================================================================ + crate::kw::C11_ATOMIC_INIT => Some((|| { + // __c11_atomic_init(ptr, val) - initialize atomic (no ordering) + self.expect_special(b'(')?; + let ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let val = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::C11AtomicInit { + ptr: Box::new(ptr), + val: Box::new(val), + }, + self.types.void_id, + token_pos, + )) + })()), + crate::kw::C11_ATOMIC_LOAD => Some((|| { + // __c11_atomic_load(ptr, order) - returns *ptr atomically + self.expect_special(b'(')?; + let ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let order = self.parse_assignment_expr()?; + self.expect_special(b')')?; + // Result type is the pointed-to type + let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + Ok(Self::typed_expr( + ExprKind::C11AtomicLoad { + ptr: Box::new(ptr), + order: Box::new(order), + }, + result_type, + token_pos, + )) + })()), + crate::kw::C11_ATOMIC_STORE => Some((|| { + // __c11_atomic_store(ptr, val, order) - *ptr = val atomically + self.expect_special(b'(')?; + let ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let val = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let order = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::C11AtomicStore { + ptr: Box::new(ptr), + val: Box::new(val), + order: Box::new(order), + }, + self.types.void_id, + token_pos, + )) + })()), + crate::kw::C11_ATOMIC_EXCHANGE => Some((|| { + // __c11_atomic_exchange(ptr, val, order) - swap and return old + self.expect_special(b'(')?; + let ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let val = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let order = self.parse_assignment_expr()?; + self.expect_special(b')')?; + // Result type is the pointed-to type + let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + Ok(Self::typed_expr( + ExprKind::C11AtomicExchange { + ptr: Box::new(ptr), + val: Box::new(val), + order: Box::new(order), + }, + result_type, + token_pos, + )) + })()), + crate::kw::C11_ATOMIC_COMPARE_EXCHANGE_STRONG => Some((|| { + // __c11_atomic_compare_exchange_strong(ptr, expected, desired, succ, fail) + // Note: fail_order is parsed but ignored (we use succ_order for both) + self.expect_special(b'(')?; + let ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let expected = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let desired = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let succ_order = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let _fail_order = self.parse_assignment_expr()?; + self.expect_special(b')')?; + // Returns bool (_Bool) + Ok(Self::typed_expr( + ExprKind::C11AtomicCompareExchangeStrong { + ptr: Box::new(ptr), + expected: Box::new(expected), + desired: Box::new(desired), + succ_order: Box::new(succ_order), + }, + self.types.bool_id, + token_pos, + )) + })()), + crate::kw::C11_ATOMIC_COMPARE_EXCHANGE_WEAK => Some((|| { + // __c11_atomic_compare_exchange_weak(ptr, expected, desired, succ, fail) + // Note: Implemented as strong (no spurious failures) + self.expect_special(b'(')?; + let ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let expected = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let desired = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let succ_order = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let _fail_order = self.parse_assignment_expr()?; + self.expect_special(b')')?; + // Returns bool (_Bool) + Ok(Self::typed_expr( + ExprKind::C11AtomicCompareExchangeWeak { + ptr: Box::new(ptr), + expected: Box::new(expected), + desired: Box::new(desired), + succ_order: Box::new(succ_order), + }, + self.types.bool_id, + token_pos, + )) + })()), + crate::kw::C11_ATOMIC_FETCH_ADD => Some((|| { + // __c11_atomic_fetch_add(ptr, val, order) - add and return old + self.expect_special(b'(')?; + let ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let val = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let order = self.parse_assignment_expr()?; + self.expect_special(b')')?; + // Result type is the pointed-to type + let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + Ok(Self::typed_expr( + ExprKind::C11AtomicFetchAdd { + ptr: Box::new(ptr), + val: Box::new(val), + order: Box::new(order), + }, + result_type, + token_pos, + )) + })()), + crate::kw::C11_ATOMIC_FETCH_SUB => Some((|| { + // __c11_atomic_fetch_sub(ptr, val, order) - subtract and return old + self.expect_special(b'(')?; + let ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let val = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let order = self.parse_assignment_expr()?; + self.expect_special(b')')?; + // Result type is the pointed-to type + let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + Ok(Self::typed_expr( + ExprKind::C11AtomicFetchSub { + ptr: Box::new(ptr), + val: Box::new(val), + order: Box::new(order), + }, + result_type, + token_pos, + )) + })()), + crate::kw::C11_ATOMIC_FETCH_AND => Some((|| { + // __c11_atomic_fetch_and(ptr, val, order) - AND and return old + self.expect_special(b'(')?; + let ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let val = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let order = self.parse_assignment_expr()?; + self.expect_special(b')')?; + // Result type is the pointed-to type + let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + Ok(Self::typed_expr( + ExprKind::C11AtomicFetchAnd { + ptr: Box::new(ptr), + val: Box::new(val), + order: Box::new(order), + }, + result_type, + token_pos, + )) + })()), + crate::kw::C11_ATOMIC_FETCH_OR => Some((|| { + // __c11_atomic_fetch_or(ptr, val, order) - OR and return old + self.expect_special(b'(')?; + let ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let val = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let order = self.parse_assignment_expr()?; + self.expect_special(b')')?; + // Result type is the pointed-to type + let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + Ok(Self::typed_expr( + ExprKind::C11AtomicFetchOr { + ptr: Box::new(ptr), + val: Box::new(val), + order: Box::new(order), + }, + result_type, + token_pos, + )) + })()), + crate::kw::C11_ATOMIC_FETCH_XOR => Some((|| { + // __c11_atomic_fetch_xor(ptr, val, order) - XOR and return old + self.expect_special(b'(')?; + let ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let val = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let order = self.parse_assignment_expr()?; + self.expect_special(b')')?; + // Result type is the pointed-to type + let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); + let result_type = self.types.base_type(ptr_type).unwrap_or(self.types.int_id); + Ok(Self::typed_expr( + ExprKind::C11AtomicFetchXor { + ptr: Box::new(ptr), + val: Box::new(val), + order: Box::new(order), + }, + result_type, + token_pos, + )) + })()), + crate::kw::C11_ATOMIC_THREAD_FENCE => Some((|| { + // __c11_atomic_thread_fence(order) - memory fence + self.expect_special(b'(')?; + let order = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::C11AtomicThreadFence { + order: Box::new(order), + }, + self.types.void_id, + token_pos, + )) + })()), + crate::kw::C11_ATOMIC_SIGNAL_FENCE => Some((|| { + // __c11_atomic_signal_fence(order) - compiler barrier + self.expect_special(b'(')?; + let order = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::C11AtomicSignalFence { + order: Box::new(order), + }, + self.types.void_id, + token_pos, + )) + })()), + crate::kw::BUILTIN_OBJECT_SIZE => Some((|| { + // __builtin_object_size(ptr, type) - returns (size_t)-1 + // at compile time without optimization (conservative "don't know") + self.expect_special(b'(')?; + let _ptr = self.parse_assignment_expr()?; + self.expect_special(b',')?; + let _otype = self.parse_assignment_expr()?; + self.expect_special(b')')?; + Ok(Self::typed_expr( + ExprKind::IntLit(-1), + self.types.ulong_id, + token_pos, + )) + })()), + _ => { + let name_str = self.idents.get_opt(name_id).unwrap_or(""); + if name_str.starts_with("__builtin___") { + Some((|| { + // Fortified builtins: __builtin___snprintf_chk etc. + // Strip __builtin_ prefix → __snprintf_chk, which is a + // real libc function (declared by macOS/glibc headers). + let real_name = &name_str["__builtin_".len()..]; + // Parse arguments first (must consume tokens regardless) + self.expect_special(b'(')?; + let mut args = Vec::new(); + if !self.is_special(b')') { + args.push(self.parse_assignment_expr()?); + while self.is_special(b',') { + self.advance(); + args.push(self.parse_assignment_expr()?); + } + } + self.expect_special(b')')?; + // Look up the real function by its de-prefixed name + let real_name_id = self.idents.lookup(real_name); + let symbol_id = real_name_id.and_then(|id| { + self.symbols + .lookup_id(id, crate::symbol::Namespace::Ordinary) + }); + if let Some(symbol_id) = symbol_id { + let func_type = self.symbols.get(symbol_id).typ; + let ret_type = + self.types.base_type(func_type).unwrap_or(self.types.int_id); + let func_expr = + Self::typed_expr(ExprKind::Ident(symbol_id), func_type, token_pos); + return Ok(Self::typed_expr( + ExprKind::Call { + func: Box::new(func_expr), + args, + }, + ret_type, + token_pos, + )); + } + // Not declared — return 0 as fallback + diag::error(token_pos, &format!("undeclared function '{}'", real_name)); + Ok(Self::typed_expr( + ExprKind::IntLit(0), + self.types.int_id, + token_pos, + )) + })()) + } else { + None + } + } + } + } + fn parse_primary_expr(&mut self) -> ParseResult { match self.peek() { TokenType::Number => { @@ -1749,961 +2694,9 @@ impl<'a> Parser<'a> { if let TokenValue::Ident(id) = &token.value { let name_id = *id; - // Check for varargs builtins that need special parsing - match name_id { - crate::kw::BUILTIN_VA_START => { - // __builtin_va_start(ap, last_param) - self.expect_special(b'(')?; - let ap = self.parse_assignment_expr()?; - self.expect_special(b',')?; - // Second arg is a parameter name - let last_param = self.expect_identifier()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::VaStart { - ap: Box::new(ap), - last_param, - }, - self.types.void_id, - token_pos, - )); - } - crate::kw::BUILTIN_VA_ARG => { - // __builtin_va_arg(ap, type) - self.expect_special(b'(')?; - let ap = self.parse_assignment_expr()?; - self.expect_special(b',')?; - // Second arg is a type - let arg_type = self.parse_type_name()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::VaArg { - ap: Box::new(ap), - arg_type, - }, - arg_type, - token_pos, - )); - } - crate::kw::BUILTIN_VA_END => { - // __builtin_va_end(ap) - self.expect_special(b'(')?; - let ap = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::VaEnd { ap: Box::new(ap) }, - self.types.void_id, - token_pos, - )); - } - crate::kw::BUILTIN_VA_COPY => { - // __builtin_va_copy(dest, src) - self.expect_special(b'(')?; - let dest = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let src = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::VaCopy { - dest: Box::new(dest), - src: Box::new(src), - }, - self.types.void_id, - token_pos, - )); - } - crate::kw::BUILTIN_BSWAP16 => { - // __builtin_bswap16(x) - returns uint16_t - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Bswap16 { arg: Box::new(arg) }, - self.types.ushort_id, - token_pos, - )); - } - crate::kw::BUILTIN_BSWAP32 => { - // __builtin_bswap32(x) - returns uint32_t - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Bswap32 { arg: Box::new(arg) }, - self.types.uint_id, - token_pos, - )); - } - crate::kw::BUILTIN_BSWAP64 => { - // __builtin_bswap64(x) - returns uint64_t - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Bswap64 { arg: Box::new(arg) }, - self.types.ulonglong_id, - token_pos, - )); - } - crate::kw::BUILTIN_CTZ => { - // __builtin_ctz(x) - returns int, counts trailing zeros in unsigned int - // Result is undefined if x is 0 - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Ctz { arg: Box::new(arg) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_CTZL => { - // __builtin_ctzl(x) - returns int, counts trailing zeros in unsigned long - // Result is undefined if x is 0 - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Ctzl { arg: Box::new(arg) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_CTZLL => { - // __builtin_ctzll(x) - returns int, counts trailing zeros in unsigned long long - // Result is undefined if x is 0 - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Ctzll { arg: Box::new(arg) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_CLZ => { - // __builtin_clz(x) - returns int, counts leading zeros in unsigned int - // Result is undefined if x is 0 - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Clz { arg: Box::new(arg) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_CLZL => { - // __builtin_clzl(x) - returns int, counts leading zeros in unsigned long - // Result is undefined if x is 0 - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Clzl { arg: Box::new(arg) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_CLZLL => { - // __builtin_clzll(x) - returns int, counts leading zeros in unsigned long long - // Result is undefined if x is 0 - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Clzll { arg: Box::new(arg) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_POPCOUNT => { - // __builtin_popcount(x) - returns int, counts set bits in unsigned int - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Popcount { arg: Box::new(arg) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_POPCOUNTL => { - // __builtin_popcountl(x) - returns int, counts set bits in unsigned long - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Popcountl { arg: Box::new(arg) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_POPCOUNTLL => { - // __builtin_popcountll(x) - returns int, counts set bits in unsigned long long - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Popcountll { arg: Box::new(arg) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_ALLOCA => { - // __builtin_alloca(size) - returns void* - self.expect_special(b'(')?; - let size = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Alloca { - size: Box::new(size), - }, - self.types.void_ptr_id, - token_pos, - )); - } - // Memory builtins - generate calls to C library functions - crate::kw::BUILTIN_MEMSET => { - // __builtin_memset(dest, c, n) - returns void* - self.expect_special(b'(')?; - let dest = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let c = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let n = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Memset { - dest: Box::new(dest), - c: Box::new(c), - n: Box::new(n), - }, - self.types.void_ptr_id, - token_pos, - )); - } - crate::kw::BUILTIN_MEMCPY => { - // __builtin_memcpy(dest, src, n) - returns void* - self.expect_special(b'(')?; - let dest = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let src = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let n = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Memcpy { - dest: Box::new(dest), - src: Box::new(src), - n: Box::new(n), - }, - self.types.void_ptr_id, - token_pos, - )); - } - crate::kw::BUILTIN_MEMMOVE => { - // __builtin_memmove(dest, src, n) - returns void* - self.expect_special(b'(')?; - let dest = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let src = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let n = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Memmove { - dest: Box::new(dest), - src: Box::new(src), - n: Box::new(n), - }, - self.types.void_ptr_id, - token_pos, - )); - } - // Infinity builtins - return float constants - crate::kw::BUILTIN_INF | crate::kw::BUILTIN_HUGE_VAL => { - self.expect_special(b'(')?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::FloatLit(f64::INFINITY), - self.types.double_id, - token_pos, - )); - } - crate::kw::BUILTIN_INFF | crate::kw::BUILTIN_HUGE_VALF => { - self.expect_special(b'(')?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::FloatLit(f64::INFINITY), - self.types.float_id, - token_pos, - )); - } - crate::kw::BUILTIN_INFL | crate::kw::BUILTIN_HUGE_VALL => { - self.expect_special(b'(')?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::FloatLit(f64::INFINITY), - self.types.longdouble_id, - token_pos, - )); - } - // NaN builtins - returns quiet NaN - // The string argument is typically empty "" for quiet NaN - crate::kw::BUILTIN_NAN | crate::kw::BUILTIN_NANS => { - self.expect_special(b'(')?; - let _arg = self.parse_assignment_expr()?; // string argument (ignored) - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::FloatLit(f64::NAN), - self.types.double_id, - token_pos, - )); - } - crate::kw::BUILTIN_NANF | crate::kw::BUILTIN_NANSF => { - self.expect_special(b'(')?; - let _arg = self.parse_assignment_expr()?; // string argument (ignored) - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::FloatLit(f64::NAN), - self.types.float_id, - token_pos, - )); - } - crate::kw::BUILTIN_NANL | crate::kw::BUILTIN_NANSL => { - self.expect_special(b'(')?; - let _arg = self.parse_assignment_expr()?; // string argument (ignored) - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::FloatLit(f64::NAN), - self.types.longdouble_id, - token_pos, - )); - } - // FLT_ROUNDS - returns current rounding mode (1 = to nearest) - crate::kw::BUILTIN_FLT_ROUNDS => { - self.expect_special(b'(')?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::IntLit(1), // IEEE 754 default: round to nearest - self.types.int_id, - token_pos, - )); - } - // Fabs builtins - absolute value for floats - crate::kw::BUILTIN_FABS => { - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Fabs { arg: Box::new(arg) }, - self.types.double_id, - token_pos, - )); - } - crate::kw::BUILTIN_FABSF => { - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Fabsf { arg: Box::new(arg) }, - self.types.float_id, - token_pos, - )); - } - crate::kw::BUILTIN_FABSL => { - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Fabsl { arg: Box::new(arg) }, - self.types.longdouble_id, - token_pos, - )); - } - // Signbit builtins - test sign bit of floats - crate::kw::BUILTIN_SIGNBIT => { - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Signbit { arg: Box::new(arg) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_SIGNBITF => { - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Signbitf { arg: Box::new(arg) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_SIGNBITL => { - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Signbitl { arg: Box::new(arg) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_UNREACHABLE => { - // __builtin_unreachable() - marks code as unreachable - // Takes no arguments, returns void - // Behavior is undefined if actually reached at runtime - self.expect_special(b'(')?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Unreachable, - self.types.void_id, - token_pos, - )); - } - crate::kw::BUILTIN_CONSTANT_P => { - // __builtin_constant_p(expr) - returns 1 if expr is a constant, 0 otherwise - // This is evaluated at compile time, not runtime - self.expect_special(b'(')?; - let arg = self.parse_assignment_expr()?; - self.expect_special(b')')?; - // Check if the argument is a constant expression - let is_constant = self.eval_const_expr(&arg).is_some(); - return Ok(Self::typed_expr( - ExprKind::IntLit(if is_constant { 1 } else { 0 }), - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_EXPECT => { - // __builtin_expect(expr, c) - branch prediction hint - // Returns expr, the second argument is the expected value (for optimization hints) - // We just return expr since we don't do branch prediction optimization - self.expect_special(b'(')?; - let expr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let _expected = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(expr); - } - crate::kw::BUILTIN_ASSUME_ALIGNED => { - // __builtin_assume_aligned(ptr, align) or - // __builtin_assume_aligned(ptr, align, offset) - // Returns ptr, hints that ptr is aligned to align bytes - // We just return ptr since we don't do alignment optimization - self.expect_special(b'(')?; - let ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let _align = self.parse_assignment_expr()?; - // Optional third argument (offset) - if self.peek_special() == Some(b',' as u32) { - self.expect_special(b',')?; - let _offset = self.parse_assignment_expr()?; - } - self.expect_special(b')')?; - return Ok(ptr); - } - crate::kw::BUILTIN_PREFETCH => { - // __builtin_prefetch(addr) or - // __builtin_prefetch(addr, rw) or - // __builtin_prefetch(addr, rw, locality) - // Prefetch data at addr into cache - no-op for correctness - self.expect_special(b'(')?; - let _addr = self.parse_assignment_expr()?; - // Optional rw argument (0=read, 1=write) - if self.peek_special() == Some(b',' as u32) { - self.expect_special(b',')?; - let _rw = self.parse_assignment_expr()?; - // Optional locality argument (0-3) - if self.peek_special() == Some(b',' as u32) { - self.expect_special(b',')?; - let _locality = self.parse_assignment_expr()?; - } - } - self.expect_special(b')')?; - // Returns void - just return a void expression - return Ok(Self::typed_expr( - ExprKind::IntLit(0), - self.types.void_id, - token_pos, - )); - } - crate::kw::BUILTIN_TYPES_COMPATIBLE_P => { - // __builtin_types_compatible_p(type1, type2) - returns 1 if types are compatible - // This is evaluated at compile time, ignoring top-level qualifiers - self.expect_special(b'(')?; - let type1 = self.parse_type_name()?; - self.expect_special(b',')?; - let type2 = self.parse_type_name()?; - self.expect_special(b')')?; - // Check type compatibility (ignoring qualifiers) - let compatible = self.types.types_compatible(type1, type2); - return Ok(Self::typed_expr( - ExprKind::IntLit(if compatible { 1 } else { 0 }), - self.types.int_id, - token_pos, - )); - } - crate::kw::BUILTIN_FRAME_ADDRESS => { - // __builtin_frame_address(level) - returns void*, address of frame at level - // Level 0 is the current frame, 1 is the caller's frame, etc. - // Returns NULL for invalid levels (beyond stack bounds) - self.expect_special(b'(')?; - let level = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::FrameAddress { - level: Box::new(level), - }, - self.types.void_ptr_id, - token_pos, - )); - } - crate::kw::BUILTIN_RETURN_ADDRESS => { - // __builtin_return_address(level) - returns void*, return address at level - // Level 0 is the current function's return address - // Returns NULL for invalid levels (beyond stack bounds) - self.expect_special(b'(')?; - let level = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::ReturnAddress { - level: Box::new(level), - }, - self.types.void_ptr_id, - token_pos, - )); - } - crate::kw::SETJMP | crate::kw::SETJMP2 => { - // setjmp(env) - saves execution context, returns int - // Returns 0 on direct call, non-zero when returning via longjmp - self.expect_special(b'(')?; - let env = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Setjmp { env: Box::new(env) }, - self.types.int_id, - token_pos, - )); - } - crate::kw::LONGJMP | crate::kw::LONGJMP2 => { - // longjmp(env, val) - restores execution context (never returns) - // Causes corresponding setjmp to return val (or 1 if val == 0) - self.expect_special(b'(')?; - let env = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let val = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::Longjmp { - env: Box::new(env), - val: Box::new(val), - }, - self.types.void_id, - token_pos, - )); - } - crate::kw::BUILTIN_OFFSETOF | crate::kw::OFFSETOF => { - // __builtin_offsetof(type, member-designator) - // Returns the byte offset of a member within a struct/union - // member-designator can be .field or [index] chains - self.expect_special(b'(')?; - - // Parse the type name - let type_id = self.parse_type_name()?; - - self.expect_special(b',')?; - - // Parse member-designator starting with field name (no dot prefix for first field) - // Subsequent components use .field or [index] syntax - let mut path = Vec::new(); - - // Expect identifier for first member - let first_field = self.expect_identifier()?; - path.push(OffsetOfPath::Field(first_field)); - - // Parse subsequent designators - loop { - if self.is_special(b'.') { - self.advance(); - let field = self.expect_identifier()?; - path.push(OffsetOfPath::Field(field)); - } else if self.is_special(b'[') { - self.advance(); - // Parse constant expression for index - let index_expr = self.parse_conditional_expr()?; - let index_pos = index_expr.pos; - self.expect_special(b']')?; - // Evaluate as constant - offsetof requires compile-time constant - let index_val = - self.eval_const_expr(&index_expr).ok_or_else(|| { - ParseError::new( - "array index in offsetof must be a constant expression", - index_pos, - ) - })?; - path.push(OffsetOfPath::Index(index_val as i64)); - } else { - break; - } - } - - self.expect_special(b')')?; - - return Ok(Self::typed_expr( - ExprKind::OffsetOf { type_id, path }, - self.types.ulong_id, // size_t is typically unsigned long - token_pos, - )); - } - // ================================================================ - // Atomic builtins (Clang __c11_atomic_* for C11 stdatomic.h) - // ================================================================ - crate::kw::C11_ATOMIC_INIT => { - // __c11_atomic_init(ptr, val) - initialize atomic (no ordering) - self.expect_special(b'(')?; - let ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let val = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::C11AtomicInit { - ptr: Box::new(ptr), - val: Box::new(val), - }, - self.types.void_id, - token_pos, - )); - } - crate::kw::C11_ATOMIC_LOAD => { - // __c11_atomic_load(ptr, order) - returns *ptr atomically - self.expect_special(b'(')?; - let ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let order = self.parse_assignment_expr()?; - self.expect_special(b')')?; - // Result type is the pointed-to type - let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); - let result_type = - self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - return Ok(Self::typed_expr( - ExprKind::C11AtomicLoad { - ptr: Box::new(ptr), - order: Box::new(order), - }, - result_type, - token_pos, - )); - } - crate::kw::C11_ATOMIC_STORE => { - // __c11_atomic_store(ptr, val, order) - *ptr = val atomically - self.expect_special(b'(')?; - let ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let val = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let order = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::C11AtomicStore { - ptr: Box::new(ptr), - val: Box::new(val), - order: Box::new(order), - }, - self.types.void_id, - token_pos, - )); - } - crate::kw::C11_ATOMIC_EXCHANGE => { - // __c11_atomic_exchange(ptr, val, order) - swap and return old - self.expect_special(b'(')?; - let ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let val = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let order = self.parse_assignment_expr()?; - self.expect_special(b')')?; - // Result type is the pointed-to type - let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); - let result_type = - self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - return Ok(Self::typed_expr( - ExprKind::C11AtomicExchange { - ptr: Box::new(ptr), - val: Box::new(val), - order: Box::new(order), - }, - result_type, - token_pos, - )); - } - crate::kw::C11_ATOMIC_COMPARE_EXCHANGE_STRONG => { - // __c11_atomic_compare_exchange_strong(ptr, expected, desired, succ, fail) - // Note: fail_order is parsed but ignored (we use succ_order for both) - self.expect_special(b'(')?; - let ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let expected = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let desired = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let succ_order = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let _fail_order = self.parse_assignment_expr()?; - self.expect_special(b')')?; - // Returns bool (_Bool) - return Ok(Self::typed_expr( - ExprKind::C11AtomicCompareExchangeStrong { - ptr: Box::new(ptr), - expected: Box::new(expected), - desired: Box::new(desired), - succ_order: Box::new(succ_order), - }, - self.types.bool_id, - token_pos, - )); - } - crate::kw::C11_ATOMIC_COMPARE_EXCHANGE_WEAK => { - // __c11_atomic_compare_exchange_weak(ptr, expected, desired, succ, fail) - // Note: Implemented as strong (no spurious failures) - self.expect_special(b'(')?; - let ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let expected = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let desired = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let succ_order = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let _fail_order = self.parse_assignment_expr()?; - self.expect_special(b')')?; - // Returns bool (_Bool) - return Ok(Self::typed_expr( - ExprKind::C11AtomicCompareExchangeWeak { - ptr: Box::new(ptr), - expected: Box::new(expected), - desired: Box::new(desired), - succ_order: Box::new(succ_order), - }, - self.types.bool_id, - token_pos, - )); - } - crate::kw::C11_ATOMIC_FETCH_ADD => { - // __c11_atomic_fetch_add(ptr, val, order) - add and return old - self.expect_special(b'(')?; - let ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let val = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let order = self.parse_assignment_expr()?; - self.expect_special(b')')?; - // Result type is the pointed-to type - let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); - let result_type = - self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - return Ok(Self::typed_expr( - ExprKind::C11AtomicFetchAdd { - ptr: Box::new(ptr), - val: Box::new(val), - order: Box::new(order), - }, - result_type, - token_pos, - )); - } - crate::kw::C11_ATOMIC_FETCH_SUB => { - // __c11_atomic_fetch_sub(ptr, val, order) - subtract and return old - self.expect_special(b'(')?; - let ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let val = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let order = self.parse_assignment_expr()?; - self.expect_special(b')')?; - // Result type is the pointed-to type - let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); - let result_type = - self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - return Ok(Self::typed_expr( - ExprKind::C11AtomicFetchSub { - ptr: Box::new(ptr), - val: Box::new(val), - order: Box::new(order), - }, - result_type, - token_pos, - )); - } - crate::kw::C11_ATOMIC_FETCH_AND => { - // __c11_atomic_fetch_and(ptr, val, order) - AND and return old - self.expect_special(b'(')?; - let ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let val = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let order = self.parse_assignment_expr()?; - self.expect_special(b')')?; - // Result type is the pointed-to type - let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); - let result_type = - self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - return Ok(Self::typed_expr( - ExprKind::C11AtomicFetchAnd { - ptr: Box::new(ptr), - val: Box::new(val), - order: Box::new(order), - }, - result_type, - token_pos, - )); - } - crate::kw::C11_ATOMIC_FETCH_OR => { - // __c11_atomic_fetch_or(ptr, val, order) - OR and return old - self.expect_special(b'(')?; - let ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let val = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let order = self.parse_assignment_expr()?; - self.expect_special(b')')?; - // Result type is the pointed-to type - let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); - let result_type = - self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - return Ok(Self::typed_expr( - ExprKind::C11AtomicFetchOr { - ptr: Box::new(ptr), - val: Box::new(val), - order: Box::new(order), - }, - result_type, - token_pos, - )); - } - crate::kw::C11_ATOMIC_FETCH_XOR => { - // __c11_atomic_fetch_xor(ptr, val, order) - XOR and return old - self.expect_special(b'(')?; - let ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let val = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let order = self.parse_assignment_expr()?; - self.expect_special(b')')?; - // Result type is the pointed-to type - let ptr_type = ptr.typ.unwrap_or(self.types.void_ptr_id); - let result_type = - self.types.base_type(ptr_type).unwrap_or(self.types.int_id); - return Ok(Self::typed_expr( - ExprKind::C11AtomicFetchXor { - ptr: Box::new(ptr), - val: Box::new(val), - order: Box::new(order), - }, - result_type, - token_pos, - )); - } - crate::kw::C11_ATOMIC_THREAD_FENCE => { - // __c11_atomic_thread_fence(order) - memory fence - self.expect_special(b'(')?; - let order = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::C11AtomicThreadFence { - order: Box::new(order), - }, - self.types.void_id, - token_pos, - )); - } - crate::kw::C11_ATOMIC_SIGNAL_FENCE => { - // __c11_atomic_signal_fence(order) - compiler barrier - self.expect_special(b'(')?; - let order = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::C11AtomicSignalFence { - order: Box::new(order), - }, - self.types.void_id, - token_pos, - )); - } - crate::kw::BUILTIN_OBJECT_SIZE => { - // __builtin_object_size(ptr, type) - returns (size_t)-1 - // at compile time without optimization (conservative "don't know") - self.expect_special(b'(')?; - let _ptr = self.parse_assignment_expr()?; - self.expect_special(b',')?; - let _otype = self.parse_assignment_expr()?; - self.expect_special(b')')?; - return Ok(Self::typed_expr( - ExprKind::IntLit(-1), - self.types.ulong_id, - token_pos, - )); - } - _ => { - let name_str = self.idents.get_opt(name_id).unwrap_or(""); - if name_str.starts_with("__builtin___") { - // Fortified builtins: __builtin___snprintf_chk etc. - // Strip __builtin_ prefix → __snprintf_chk, which is a - // real libc function (declared by macOS/glibc headers). - let real_name = &name_str["__builtin_".len()..]; - // Parse arguments first (must consume tokens regardless) - self.expect_special(b'(')?; - let mut args = Vec::new(); - if !self.is_special(b')') { - args.push(self.parse_assignment_expr()?); - while self.is_special(b',') { - self.advance(); - args.push(self.parse_assignment_expr()?); - } - } - self.expect_special(b')')?; - // Look up the real function by its de-prefixed name - let real_name_id = self.idents.lookup(real_name); - let symbol_id = real_name_id.and_then(|id| { - self.symbols - .lookup_id(id, crate::symbol::Namespace::Ordinary) - }); - if let Some(symbol_id) = symbol_id { - let func_type = self.symbols.get(symbol_id).typ; - let ret_type = self - .types - .base_type(func_type) - .unwrap_or(self.types.int_id); - let func_expr = Self::typed_expr( - ExprKind::Ident(symbol_id), - func_type, - token_pos, - ); - return Ok(Self::typed_expr( - ExprKind::Call { - func: Box::new(func_expr), - args, - }, - ret_type, - token_pos, - )); - } - // Not declared — return 0 as fallback - diag::error( - token_pos, - &format!("undeclared function '{}'", real_name), - ); - return Ok(Self::typed_expr( - ExprKind::IntLit(0), - self.types.int_id, - token_pos, - )); - } - } + // Try builtin dispatch first + if let Some(result) = self.parse_builtin_expr(name_id, token_pos) { + return result; } // Look up symbol to get type (during parsing, symbol is in scope) diff --git a/cc/parse/parser.rs b/cc/parse/parser.rs index 39621595..cf90748a 100644 --- a/cc/parse/parser.rs +++ b/cc/parse/parser.rs @@ -2925,7 +2925,7 @@ impl Parser<'_> { /// Parse a translation unit (top-level) pub fn parse_translation_unit(&mut self) -> ParseResult { - let mut tu = TranslationUnit::new(); + let mut tu = TranslationUnit::default(); self.skip_stream_tokens(); diff --git a/cc/tests/codegen/misc.rs b/cc/tests/codegen/misc.rs index 4239a1b2..31591431 100644 --- a/cc/tests/codegen/misc.rs +++ b/cc/tests/codegen/misc.rs @@ -3886,6 +3886,43 @@ int main(void) { ); } +/// Bug AL: Inlining a function with a MEMORY-class struct parameter (>32 bytes, +/// passed on stack) caused symaddr of the Arg pseudo to produce a pointer-to-pointer +/// instead of the struct address. The fix converts symaddr-on-Arg to copy during +/// inlining, since call_args already provides the struct address. +#[test] +fn codegen_inline_large_struct_param() { + let code = r#" +typedef struct { + long a[16]; // 128 bytes, MEMORY class on x86-64 +} BigStruct; + +int convert(void *obj, BigStruct *out) { + out->a[0] = 42; + out->a[1] = 99; + return 1; +} + +long use_big(int how, BigStruct s) { + return s.a[0] + s.a[1] + how; +} + +int main() { + BigStruct local; + int ok = convert((void*)0x1234, &local); + if (!ok) return 1; + long result = use_big(10, local); + // 42 + 99 + 10 = 151 + if (result != 151) return 2; + return 0; +} +"#; + assert_eq!( + compile_and_run_optimized("codegen_inline_large_struct_param", code), + 0 + ); +} + // ============================================================================ // __int128 codegen tests // ============================================================================