From 3d659fdd84bbbbed882c1a3acb1feb402887cd43 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Sun, 7 Dec 2025 14:30:35 -0500 Subject: [PATCH 1/7] [cc] initial optimizations and opt infra --- cc/dce.rs | 423 +++++++++++++++++++ cc/instcombine.rs | 663 ++++++++++++++++++++++++++++++ cc/main.rs | 12 + cc/opt.rs | 158 +++++++ cc/tests/common/mod.rs | 25 +- cc/tests/features/mod.rs | 1 + cc/tests/features/optimization.rs | 184 +++++++++ 7 files changed, 1457 insertions(+), 9 deletions(-) create mode 100644 cc/dce.rs create mode 100644 cc/instcombine.rs create mode 100644 cc/opt.rs create mode 100644 cc/tests/features/optimization.rs diff --git a/cc/dce.rs b/cc/dce.rs new file mode 100644 index 00000000..6d2b504e --- /dev/null +++ b/cc/dce.rs @@ -0,0 +1,423 @@ +// +// Copyright (c) 2024 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 +// +// Dead Code Elimination (DCE) pass for pcc C99 compiler +// +// This pass removes instructions whose results are never used. +// It uses a mark-sweep algorithm: +// 1. Mark "root" instructions (those with side effects) +// 2. Transitively mark all instructions that roots depend on +// 3. Delete all unmarked instructions +// + +use crate::ir::{BasicBlockId, Function, Instruction, Opcode, PseudoId}; +use std::collections::{HashSet, VecDeque}; + +// ============================================================================ +// Main Entry Point +// ============================================================================ + +/// Run the DCE pass on a function. +/// Returns true if any changes were made. +pub fn run(func: &mut Function) -> bool { + let mut changed = false; + + // Run both phases + changed |= eliminate_dead_code(func); + changed |= remove_unreachable_blocks(func); + + changed +} + +// ============================================================================ +// Dead Code Elimination +// ============================================================================ + +/// Check if an opcode is a "root" (has side effects, cannot be deleted). +fn is_root(op: Opcode) -> bool { + matches!( + op, + Opcode::Ret + | Opcode::Br + | Opcode::Cbr + | Opcode::Switch + | Opcode::Store + | Opcode::Call + | Opcode::Entry + | Opcode::VaStart + | Opcode::VaEnd + | Opcode::VaCopy + | Opcode::VaArg + | Opcode::Alloca + ) +} + +/// Get all pseudo IDs used by an instruction (operands). +fn get_uses(insn: &Instruction) -> Vec { + let mut uses = Vec::new(); + + // Source operands + uses.extend(insn.src.iter().copied()); + + // Phi sources + for (_, pseudo) in &insn.phi_list { + uses.push(*pseudo); + } + + // Switch value (stored in target for switch) + if insn.op == Opcode::Switch { + if let Some(target) = insn.target { + uses.push(target); + } + } + + uses +} + +/// Find the instruction that defines a pseudo. +/// Returns (block_index, instruction_index) if found. +fn find_def(func: &Function, id: PseudoId) -> Option<(usize, usize)> { + for (bb_idx, bb) in func.blocks.iter().enumerate() { + for (insn_idx, insn) in bb.insns.iter().enumerate() { + if insn.target == Some(id) { + return Some((bb_idx, insn_idx)); + } + } + } + None +} + +/// Eliminate dead code using mark-sweep algorithm. +fn eliminate_dead_code(func: &mut Function) -> bool { + let mut live: HashSet = HashSet::new(); + let mut worklist: VecDeque = VecDeque::new(); + + // Phase 1: Mark roots and their operands as live + for bb in &func.blocks { + for insn in &bb.insns { + if is_root(insn.op) { + // Mark all operands of root instructions as live + for id in get_uses(insn) { + if live.insert(id) { + worklist.push_back(id); + } + } + } + } + } + + // Phase 2: Propagate liveness transitively + while let Some(id) = worklist.pop_front() { + // Find the instruction that defines this pseudo + if let Some((bb_idx, insn_idx)) = find_def(func, id) { + let insn = &func.blocks[bb_idx].insns[insn_idx]; + + // Mark all operands of the defining instruction as live + for use_id in get_uses(insn) { + if live.insert(use_id) { + worklist.push_back(use_id); + } + } + } + } + + // Phase 3: Delete dead instructions (convert to Nop) + let mut changed = false; + for bb in &mut func.blocks { + for insn in &mut bb.insns { + // Skip roots - they're always live + if is_root(insn.op) { + continue; + } + + // Skip Nop - already dead + if insn.op == Opcode::Nop { + continue; + } + + // If this instruction has a target that's not live, it's dead + if let Some(target) = insn.target { + if !live.contains(&target) { + // Convert to Nop + insn.op = Opcode::Nop; + insn.src.clear(); + insn.target = None; + insn.phi_list.clear(); + changed = true; + } + } + } + } + + changed +} + +// ============================================================================ +// Unreachable Block Removal +// ============================================================================ + +/// Compute the set of reachable block IDs starting from entry. +fn compute_reachable(func: &Function) -> HashSet { + let mut reachable = HashSet::new(); + let mut worklist = VecDeque::new(); + + worklist.push_back(func.entry); + + while let Some(bb_id) = worklist.pop_front() { + if !reachable.insert(bb_id) { + continue; // Already visited + } + + // Find this block and add its successors + if let Some(bb) = func.get_block(bb_id) { + for child in &bb.children { + if !reachable.contains(child) { + worklist.push_back(*child); + } + } + } + } + + reachable +} + +/// Remove unreachable blocks from the function. +fn remove_unreachable_blocks(func: &mut Function) -> bool { + let reachable = compute_reachable(func); + let before = func.blocks.len(); + + // Remove unreachable blocks + func.blocks.retain(|bb| reachable.contains(&bb.id)); + + // Update parent/child references to remove dead blocks + let unreachable: HashSet<_> = func + .blocks + .iter() + .map(|bb| bb.id) + .collect::>() + .difference(&reachable) + .copied() + .collect(); + + for bb in &mut func.blocks { + bb.parents.retain(|p| !unreachable.contains(p)); + bb.children.retain(|c| !unreachable.contains(c)); + } + + func.blocks.len() < before +} + +// ============================================================================ +// Tests +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + use crate::ir::{BasicBlock, Instruction, Pseudo}; + use crate::types::TypeTable; + + fn make_simple_func() -> Function { + let types = TypeTable::new(); + let mut func = Function::new("test", types.int_id); + + func.add_pseudo(Pseudo::reg(PseudoId(0), 0)); + func.add_pseudo(Pseudo::reg(PseudoId(1), 1)); + func.add_pseudo(Pseudo::val(PseudoId(2), 42)); + + let mut bb = BasicBlock::new(BasicBlockId(0)); + bb.add_insn(Instruction::new(Opcode::Entry)); + // Live: used by ret + bb.add_insn(Instruction::binop( + Opcode::Add, + PseudoId(0), + PseudoId(2), + PseudoId(2), + types.int_id, + 32, + )); + // Dead: result unused + bb.add_insn(Instruction::binop( + Opcode::Mul, + PseudoId(1), + PseudoId(2), + PseudoId(2), + types.int_id, + 32, + )); + bb.add_insn(Instruction::ret(Some(PseudoId(0)))); + func.add_block(bb); + func.entry = BasicBlockId(0); + + func + } + + #[test] + fn test_dead_instruction_removed() { + let mut func = make_simple_func(); + + // Before: Add (live), Mul (dead) + assert_eq!(func.blocks[0].insns[1].op, Opcode::Add); + assert_eq!(func.blocks[0].insns[2].op, Opcode::Mul); + + let changed = run(&mut func); + assert!(changed); + + // After: Add is still Add, Mul is now Nop + assert_eq!(func.blocks[0].insns[1].op, Opcode::Add); + assert_eq!(func.blocks[0].insns[2].op, Opcode::Nop); + } + + #[test] + fn test_live_instruction_preserved() { + let types = TypeTable::new(); + let mut func = Function::new("test", types.int_id); + + func.add_pseudo(Pseudo::reg(PseudoId(0), 0)); + func.add_pseudo(Pseudo::val(PseudoId(1), 10)); + func.add_pseudo(Pseudo::val(PseudoId(2), 20)); + + let mut bb = BasicBlock::new(BasicBlockId(0)); + bb.add_insn(Instruction::new(Opcode::Entry)); + bb.add_insn(Instruction::binop( + Opcode::Add, + PseudoId(0), + PseudoId(1), + PseudoId(2), + types.int_id, + 32, + )); + bb.add_insn(Instruction::ret(Some(PseudoId(0)))); + func.add_block(bb); + func.entry = BasicBlockId(0); + + let changed = run(&mut func); + assert!(!changed); // No changes - Add is used + + assert_eq!(func.blocks[0].insns[1].op, Opcode::Add); + } + + #[test] + fn test_transitive_liveness() { + let types = TypeTable::new(); + let mut func = Function::new("test", types.int_id); + + func.add_pseudo(Pseudo::reg(PseudoId(0), 0)); + func.add_pseudo(Pseudo::reg(PseudoId(1), 1)); + func.add_pseudo(Pseudo::val(PseudoId(2), 5)); + + let mut bb = BasicBlock::new(BasicBlockId(0)); + bb.add_insn(Instruction::new(Opcode::Entry)); + // %0 = add %2, %2 (live because %1 uses it) + bb.add_insn(Instruction::binop( + Opcode::Add, + PseudoId(0), + PseudoId(2), + PseudoId(2), + types.int_id, + 32, + )); + // %1 = mul %0, %2 (live because ret uses it) + bb.add_insn(Instruction::binop( + Opcode::Mul, + PseudoId(1), + PseudoId(0), + PseudoId(2), + types.int_id, + 32, + )); + bb.add_insn(Instruction::ret(Some(PseudoId(1)))); + func.add_block(bb); + func.entry = BasicBlockId(0); + + let changed = run(&mut func); + assert!(!changed); // Both Add and Mul are transitively live + + assert_eq!(func.blocks[0].insns[1].op, Opcode::Add); + assert_eq!(func.blocks[0].insns[2].op, Opcode::Mul); + } + + #[test] + fn test_unreachable_block_removed() { + let types = TypeTable::new(); + let mut func = Function::new("test", types.int_id); + + // Entry block + let mut bb0 = BasicBlock::new(BasicBlockId(0)); + bb0.children = vec![BasicBlockId(1)]; + bb0.add_insn(Instruction::new(Opcode::Entry)); + bb0.add_insn(Instruction::br(BasicBlockId(1))); + + // Reachable block + let mut bb1 = BasicBlock::new(BasicBlockId(1)); + bb1.parents = vec![BasicBlockId(0)]; + bb1.add_insn(Instruction::ret(None)); + + // Unreachable block (no path from entry) + let mut bb2 = BasicBlock::new(BasicBlockId(2)); + bb2.add_insn(Instruction::ret(None)); + + func.add_block(bb0); + func.add_block(bb1); + func.add_block(bb2); + func.entry = BasicBlockId(0); + + assert_eq!(func.blocks.len(), 3); + + let changed = run(&mut func); + assert!(changed); + + assert_eq!(func.blocks.len(), 2); + assert!(func.get_block(BasicBlockId(0)).is_some()); + assert!(func.get_block(BasicBlockId(1)).is_some()); + assert!(func.get_block(BasicBlockId(2)).is_none()); + } + + #[test] + fn test_is_root() { + assert!(is_root(Opcode::Ret)); + assert!(is_root(Opcode::Store)); + assert!(is_root(Opcode::Call)); + assert!(is_root(Opcode::Br)); + assert!(is_root(Opcode::Cbr)); + + assert!(!is_root(Opcode::Add)); + assert!(!is_root(Opcode::Mul)); + assert!(!is_root(Opcode::Load)); + assert!(!is_root(Opcode::Phi)); + } + + #[test] + fn test_store_is_live() { + let types = TypeTable::new(); + let mut func = Function::new("test", types.int_id); + + func.add_pseudo(Pseudo::reg(PseudoId(0), 0)); + func.add_pseudo(Pseudo::val(PseudoId(1), 42)); + func.add_pseudo(Pseudo::sym(PseudoId(2), "x".to_string())); + + let mut bb = BasicBlock::new(BasicBlockId(0)); + bb.add_insn(Instruction::new(Opcode::Entry)); + // Store has side effects - should be kept + bb.add_insn(Instruction::store( + PseudoId(1), + PseudoId(2), + 0, + types.int_id, + 32, + )); + bb.add_insn(Instruction::ret(None)); + func.add_block(bb); + func.entry = BasicBlockId(0); + + let changed = run(&mut func); + assert!(!changed); // Store is a root, not dead + + assert_eq!(func.blocks[0].insns[1].op, Opcode::Store); + } +} diff --git a/cc/instcombine.rs b/cc/instcombine.rs new file mode 100644 index 00000000..0f5fd27d --- /dev/null +++ b/cc/instcombine.rs @@ -0,0 +1,663 @@ +// +// Copyright (c) 2024 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 +// +// InstCombine pass for pcc C99 compiler +// +// This pass performs instruction combining optimizations: +// - Constant folding: evaluate operations on constants at compile time +// - Algebraic simplification: x + 0 -> x, x * 1 -> x, etc. +// - Identity patterns: x - x -> 0, x ^ x -> 0, etc. +// + +use crate::ir::{Function, Instruction, Opcode, Pseudo, PseudoId, PseudoKind}; + +// ============================================================================ +// Main Entry Point +// ============================================================================ + +/// Run the InstCombine pass on a function. +/// Returns true if any changes were made. +pub fn run(func: &mut Function) -> bool { + let mut changed = false; + + for bb in &mut func.blocks { + for insn in &mut bb.insns { + if let Some(new_insn) = try_simplify(insn, &func.pseudos) { + *insn = new_insn; + changed = true; + } + } + } + + changed +} + +// ============================================================================ +// Simplification Dispatch +// ============================================================================ + +/// Try to simplify an instruction. Returns Some(new_instruction) if simplified. +fn try_simplify(insn: &Instruction, pseudos: &[Pseudo]) -> Option { + match insn.op { + // Integer arithmetic + Opcode::Add => simplify_add(insn, pseudos), + Opcode::Sub => simplify_sub(insn, pseudos), + Opcode::Mul => simplify_mul(insn, pseudos), + Opcode::DivS | Opcode::DivU => simplify_div(insn, pseudos), + Opcode::ModS | Opcode::ModU => simplify_mod(insn, pseudos), + + // Shifts + Opcode::Shl => simplify_shift(insn, pseudos), + Opcode::Lsr => simplify_shift(insn, pseudos), + Opcode::Asr => simplify_shift(insn, pseudos), + + // Bitwise + Opcode::And => simplify_and(insn, pseudos), + Opcode::Or => simplify_or(insn, pseudos), + Opcode::Xor => simplify_xor(insn, pseudos), + + // Comparisons + Opcode::SetEq + | Opcode::SetNe + | Opcode::SetLt + | Opcode::SetLe + | Opcode::SetGt + | Opcode::SetGe + | Opcode::SetB + | Opcode::SetBe + | Opcode::SetA + | Opcode::SetAe => simplify_cmp(insn, pseudos), + + // Unary + Opcode::Neg => simplify_neg(insn, pseudos), + Opcode::Not => simplify_not(insn, pseudos), + + _ => None, + } +} + +// ============================================================================ +// Helper: Get constant value from pseudos list +// ============================================================================ + +fn get_const(pseudos: &[Pseudo], id: PseudoId) -> Option { + pseudos + .iter() + .find(|p| p.id == id) + .and_then(|p| match &p.kind { + PseudoKind::Val(v) => Some(*v), + _ => None, + }) +} + +/// Create a Copy instruction that copies a constant source. +/// For constant folding, we need to have an existing constant pseudo to copy from. +/// Since we can't easily create new constants here, we return None if we can't +/// find a matching constant. This is handled by returning None from the +/// simplify functions when both operands are constants but we can't fold. +fn make_const_result(insn: &Instruction, src_const: PseudoId) -> Instruction { + // Copy from the constant pseudo + Instruction { + op: Opcode::Copy, + target: insn.target, + src: vec![src_const], + typ: insn.typ, + size: insn.size, + ..Default::default() + } +} + +/// Create a Copy instruction that copies one value to another. +fn make_copy(insn: &Instruction, src: PseudoId) -> Instruction { + Instruction { + op: Opcode::Copy, + target: insn.target, + src: vec![src], + typ: insn.typ, + size: insn.size, + ..Default::default() + } +} + +// ============================================================================ +// Add Simplification +// ============================================================================ + +fn simplify_add(insn: &Instruction, pseudos: &[Pseudo]) -> Option { + if insn.src.len() != 2 { + return None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + match (val1, val2) { + // Algebraic: x + 0 -> x + (None, Some(0)) => Some(make_copy(insn, src1)), + + // Algebraic: 0 + x -> x + (Some(0), None) => Some(make_copy(insn, src2)), + + // Note: We can't fold constants (a + b -> result) because we can't + // create new constant pseudos without modifying the function's pseudo list. + // This would require passing &mut Function or a more complex design. + _ => None, + } +} + +// ============================================================================ +// Sub Simplification +// ============================================================================ + +fn simplify_sub(insn: &Instruction, pseudos: &[Pseudo]) -> Option { + if insn.src.len() != 2 { + return None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + let val2 = get_const(pseudos, src2); + + // Note: x - x -> 0 requires creating a zero constant, which we can't do + // without modifying the function's pseudo list. Skip for MVP. + + // Algebraic: x - 0 -> x + if val2 == Some(0) { + return Some(make_copy(insn, src1)); + } + + None +} + +// ============================================================================ +// Mul Simplification +// ============================================================================ + +fn simplify_mul(insn: &Instruction, pseudos: &[Pseudo]) -> Option { + if insn.src.len() != 2 { + return None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + match (val1, val2) { + // Algebraic: x * 0 -> 0 (copy from the zero constant) + (None, Some(0)) => Some(make_const_result(insn, src2)), + (Some(0), None) => Some(make_const_result(insn, src1)), + + // Algebraic: x * 1 -> x + (None, Some(1)) => Some(make_copy(insn, src1)), + (Some(1), None) => Some(make_copy(insn, src2)), + + _ => None, + } +} + +// ============================================================================ +// Div Simplification +// ============================================================================ + +fn simplify_div(insn: &Instruction, pseudos: &[Pseudo]) -> Option { + if insn.src.len() != 2 { + return None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + match (val1, val2) { + // Algebraic: x / 1 -> x + (None, Some(1)) => Some(make_copy(insn, src1)), + + // Algebraic: 0 / x -> 0 (copy from the zero constant) + (Some(0), None) => Some(make_const_result(insn, src1)), + + _ => None, + } +} + +// ============================================================================ +// Mod Simplification +// ============================================================================ + +fn simplify_mod(insn: &Instruction, pseudos: &[Pseudo]) -> Option { + if insn.src.len() != 2 { + return None; + } + + let src1 = insn.src[0]; + let val1 = get_const(pseudos, src1); + + // Algebraic: 0 % x -> 0 (copy from the zero constant) + if val1 == Some(0) { + return Some(make_const_result(insn, src1)); + } + + // Note: x % 1 -> 0 requires a zero constant we may not have + None +} + +// ============================================================================ +// Shift Simplification +// ============================================================================ + +fn simplify_shift(insn: &Instruction, pseudos: &[Pseudo]) -> Option { + if insn.src.len() != 2 { + return None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + match (val1, val2) { + // Algebraic: x << 0 -> x, x >> 0 -> x + (None, Some(0)) => Some(make_copy(insn, src1)), + + // Algebraic: 0 << n -> 0, 0 >> n -> 0 (copy from zero constant) + (Some(0), None) => Some(make_const_result(insn, src1)), + + _ => None, + } +} + +// ============================================================================ +// And Simplification +// ============================================================================ + +fn simplify_and(insn: &Instruction, pseudos: &[Pseudo]) -> Option { + if insn.src.len() != 2 { + return None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + // Identity: x & x -> x + if src1 == src2 { + return Some(make_copy(insn, src1)); + } + + match (val1, val2) { + // Algebraic: x & 0 -> 0 (copy from zero constant) + (None, Some(0)) => Some(make_const_result(insn, src2)), + (Some(0), None) => Some(make_const_result(insn, src1)), + + // Algebraic: x & -1 -> x (all bits set) + (None, Some(-1)) => Some(make_copy(insn, src1)), + (Some(-1), None) => Some(make_copy(insn, src2)), + + _ => None, + } +} + +// ============================================================================ +// Or Simplification +// ============================================================================ + +fn simplify_or(insn: &Instruction, pseudos: &[Pseudo]) -> Option { + if insn.src.len() != 2 { + return None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + // Identity: x | x -> x + if src1 == src2 { + return Some(make_copy(insn, src1)); + } + + match (val1, val2) { + // Algebraic: x | 0 -> x + (None, Some(0)) => Some(make_copy(insn, src1)), + (Some(0), None) => Some(make_copy(insn, src2)), + + // Algebraic: x | -1 -> -1 (copy from the -1 constant) + (None, Some(-1)) => Some(make_const_result(insn, src2)), + (Some(-1), None) => Some(make_const_result(insn, src1)), + + _ => None, + } +} + +// ============================================================================ +// Xor Simplification +// ============================================================================ + +fn simplify_xor(insn: &Instruction, pseudos: &[Pseudo]) -> Option { + if insn.src.len() != 2 { + return None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + // Note: x ^ x -> 0 requires creating a zero constant, skip for MVP + + match (val1, val2) { + // Algebraic: x ^ 0 -> x + (None, Some(0)) => Some(make_copy(insn, src1)), + (Some(0), None) => Some(make_copy(insn, src2)), + + _ => None, + } +} + +// ============================================================================ +// Comparison Simplification +// ============================================================================ + +fn simplify_cmp(insn: &Instruction, _pseudos: &[Pseudo]) -> Option { + if insn.src.len() != 2 { + return None; + } + + // Note: x cmp x patterns require creating 0 or 1 constants, skip for MVP + // Note: Constant folding also requires creating new constants, skip for MVP + + None +} + +// ============================================================================ +// Unary Simplification +// ============================================================================ + +fn simplify_neg(insn: &Instruction, _pseudos: &[Pseudo]) -> Option { + if insn.src.len() != 1 { + return None; + } + + // Note: Constant folding requires creating new constants, skip for MVP + None +} + +fn simplify_not(insn: &Instruction, _pseudos: &[Pseudo]) -> Option { + if insn.src.len() != 1 { + return None; + } + + // Note: Constant folding requires creating new constants, skip for MVP + None +} + +// ============================================================================ +// Tests +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + use crate::ir::{BasicBlock, BasicBlockId}; + use crate::types::TypeTable; + + fn make_test_func_with_insn(insn: Instruction, pseudos: Vec) -> Function { + let types = TypeTable::new(); + let mut func = Function::new("test", types.int_id); + + for p in pseudos { + func.add_pseudo(p); + } + + let mut bb = BasicBlock::new(BasicBlockId(0)); + bb.add_insn(Instruction::new(Opcode::Entry)); + bb.add_insn(insn); + bb.add_insn(Instruction::ret(None)); + func.add_block(bb); + func.entry = BasicBlockId(0); + + func + } + + #[test] + fn test_add_zero_right() { + // x + 0 -> x + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Add, + PseudoId(2), + PseudoId(0), // x + PseudoId(1), // 0 + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::reg(PseudoId(0), 0), + Pseudo::val(PseudoId(1), 0), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + assert_eq!(result_insn.src, vec![PseudoId(0)]); + } + + #[test] + fn test_add_zero_left() { + // 0 + x -> x + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Add, + PseudoId(2), + PseudoId(0), // 0 + PseudoId(1), // x + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::val(PseudoId(0), 0), + Pseudo::reg(PseudoId(1), 1), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + assert_eq!(result_insn.src, vec![PseudoId(1)]); + } + + #[test] + fn test_mul_zero() { + // x * 0 -> 0 (copy from zero constant) + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Mul, + PseudoId(2), + PseudoId(0), + PseudoId(1), // 0 + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::reg(PseudoId(0), 0), + Pseudo::val(PseudoId(1), 0), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + assert_eq!(result_insn.src, vec![PseudoId(1)]); // Copy from zero constant + } + + #[test] + fn test_mul_one() { + // x * 1 -> x + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Mul, + PseudoId(2), + PseudoId(0), + PseudoId(1), // 1 + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::reg(PseudoId(0), 0), + Pseudo::val(PseudoId(1), 1), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + assert_eq!(result_insn.src, vec![PseudoId(0)]); + } + + #[test] + fn test_and_self() { + // x & x -> x + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::And, + PseudoId(1), + PseudoId(0), + PseudoId(0), + types.int_id, + 32, + ); + let pseudos = vec![Pseudo::reg(PseudoId(0), 0), Pseudo::reg(PseudoId(1), 1)]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + assert_eq!(result_insn.src, vec![PseudoId(0)]); + } + + #[test] + fn test_or_self() { + // x | x -> x + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Or, + PseudoId(1), + PseudoId(0), + PseudoId(0), + types.int_id, + 32, + ); + let pseudos = vec![Pseudo::reg(PseudoId(0), 0), Pseudo::reg(PseudoId(1), 1)]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + assert_eq!(result_insn.src, vec![PseudoId(0)]); + } + + #[test] + fn test_xor_zero() { + // x ^ 0 -> x + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Xor, + PseudoId(2), + PseudoId(0), + PseudoId(1), // 0 + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::reg(PseudoId(0), 0), + Pseudo::val(PseudoId(1), 0), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + assert_eq!(result_insn.src, vec![PseudoId(0)]); + } + + #[test] + fn test_no_change_for_non_const() { + // x + y (neither const) -> no change + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Add, + PseudoId(2), + PseudoId(0), + PseudoId(1), + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::reg(PseudoId(0), 0), + Pseudo::reg(PseudoId(1), 1), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(!changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Add); + } + + #[test] + fn test_no_const_folding() { + // 2 + 3 -> no change (MVP doesn't create new constants) + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Add, + PseudoId(2), + PseudoId(0), // 2 + PseudoId(1), // 3 + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::val(PseudoId(0), 2), + Pseudo::val(PseudoId(1), 3), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(!changed); // MVP doesn't fold constants + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Add); + } +} diff --git a/cc/main.rs b/cc/main.rs index 2c6f4203..ff34660d 100644 --- a/cc/main.rs +++ b/cc/main.rs @@ -10,11 +10,14 @@ // mod arch; +mod dce; mod diag; mod dominate; +mod instcombine; mod ir; mod linearize; mod lower; +mod opt; mod os; mod parse; mod ssa; @@ -111,6 +114,10 @@ struct Args { /// Target triple (e.g., aarch64-apple-darwin, x86_64-unknown-linux-gnu) #[arg(long = "target", value_name = "triple", help = gettext("Target triple for cross-compilation"))] target: Option, + + /// Optimization level (0=none, 1=basic optimizations) + #[arg(short = 'O', default_value = "0", value_name = "level", help = gettext("Optimization level"))] + opt_level: u32, } fn process_file( @@ -221,6 +228,11 @@ fn process_file( Some(display_path), ); + // Optimize IR (if enabled) + if args.opt_level > 0 { + opt::optimize_module(&mut module, args.opt_level); + } + if args.dump_ir { print!("{}", module); return Ok(()); diff --git a/cc/opt.rs b/cc/opt.rs new file mode 100644 index 00000000..35840aa6 --- /dev/null +++ b/cc/opt.rs @@ -0,0 +1,158 @@ +// +// Copyright (c) 2024 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 +// +// Optimization infrastructure for pcc C99 compiler +// +// This module provides the optimization pass runner and common utilities +// used by optimization passes (InstCombine, DCE, etc.). +// + +use crate::dce; +use crate::instcombine; +use crate::ir::{Function, Module}; + +#[cfg(test)] +use crate::ir::{Pseudo, PseudoId, PseudoKind}; + +/// Maximum iterations for the optimization fixed-point loop. +/// Prevents infinite loops if passes keep making changes. +const MAX_ITERATIONS: usize = 10; + +// ============================================================================ +// Helper Functions (used by tests and future passes) +// ============================================================================ + +/// Get a pseudo by its ID from a function's pseudo list. +#[cfg(test)] +fn get_pseudo(func: &Function, id: PseudoId) -> Option<&Pseudo> { + func.pseudos.iter().find(|p| p.id == id) +} + +/// Get the constant integer value of a pseudo, if it is a constant. +#[cfg(test)] +fn get_const_val(func: &Function, id: PseudoId) -> Option { + get_pseudo(func, id).and_then(|p| match &p.kind { + PseudoKind::Val(v) => Some(*v), + _ => None, + }) +} + +/// Get the constant float value of a pseudo, if it is a float constant. +#[cfg(test)] +fn get_const_fval(func: &Function, id: PseudoId) -> Option { + get_pseudo(func, id).and_then(|p| match &p.kind { + PseudoKind::FVal(v) => Some(*v), + _ => None, + }) +} + +/// Check if a pseudo is a constant (integer or float). +#[cfg(test)] +fn is_const(func: &Function, id: PseudoId) -> bool { + get_pseudo(func, id) + .map(|p| matches!(&p.kind, PseudoKind::Val(_) | PseudoKind::FVal(_))) + .unwrap_or(false) +} + +// ============================================================================ +// Pass Runner +// ============================================================================ + +/// Optimize a module at the given optimization level. +/// +/// Level 0: No optimization +/// Level 1+: Run InstCombine + DCE passes +pub fn optimize_module(module: &mut Module, level: u32) { + if level == 0 { + return; + } + + for func in &mut module.functions { + optimize_function(func); + } +} + +/// Optimize a single function by running passes until fixed point. +fn optimize_function(func: &mut Function) { + for _ in 0..MAX_ITERATIONS { + let ic_changed = instcombine::run(func); + let dce_changed = dce::run(func); + + if !ic_changed && !dce_changed { + break; + } + } +} + +// ============================================================================ +// Tests +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + use crate::ir::{BasicBlock, BasicBlockId, Instruction, Opcode}; + use crate::types::TypeTable; + + fn make_test_func() -> Function { + let types = TypeTable::new(); + let mut func = Function::new("test", types.int_id); + + // Add some pseudos + func.add_pseudo(Pseudo::val(PseudoId(0), 42)); + func.add_pseudo(Pseudo::reg(PseudoId(1), 1)); + func.add_pseudo(Pseudo::fval(PseudoId(2), 3.14)); + + // Add a basic block + let mut bb = BasicBlock::new(BasicBlockId(0)); + bb.add_insn(Instruction::new(Opcode::Entry)); + bb.add_insn(Instruction::ret(None)); + func.add_block(bb); + func.entry = BasicBlockId(0); + + func + } + + #[test] + fn test_get_pseudo() { + let func = make_test_func(); + + assert!(get_pseudo(&func, PseudoId(0)).is_some()); + assert!(get_pseudo(&func, PseudoId(1)).is_some()); + assert!(get_pseudo(&func, PseudoId(99)).is_none()); + } + + #[test] + fn test_get_const_val() { + let func = make_test_func(); + + assert_eq!(get_const_val(&func, PseudoId(0)), Some(42)); + assert_eq!(get_const_val(&func, PseudoId(1)), None); // register, not const + assert_eq!(get_const_val(&func, PseudoId(2)), None); // float, not int + assert_eq!(get_const_val(&func, PseudoId(99)), None); // doesn't exist + } + + #[test] + fn test_get_const_fval() { + let func = make_test_func(); + + assert_eq!(get_const_fval(&func, PseudoId(2)), Some(3.14)); + assert_eq!(get_const_fval(&func, PseudoId(0)), None); // int, not float + assert_eq!(get_const_fval(&func, PseudoId(1)), None); // register + } + + #[test] + fn test_is_const() { + let func = make_test_func(); + + assert!(is_const(&func, PseudoId(0))); // int constant + assert!(is_const(&func, PseudoId(2))); // float constant + assert!(!is_const(&func, PseudoId(1))); // register + assert!(!is_const(&func, PseudoId(99))); // doesn't exist + } +} diff --git a/cc/tests/common/mod.rs b/cc/tests/common/mod.rs index 94b3ceaa..c02c2825 100644 --- a/cc/tests/common/mod.rs +++ b/cc/tests/common/mod.rs @@ -98,20 +98,27 @@ pub fn cleanup_exe(exe_file: &Option) { /// Compile inline C code and run, returning exit code /// Temp files are cleaned up automatically via tempfile pub fn compile_and_run(name: &str, content: &str) -> i32 { + compile_and_run_with_opts(name, content, &[]) +} + +/// Compile inline C code with optimization and run, returning exit code +pub fn compile_and_run_optimized(name: &str, content: &str) -> i32 { + compile_and_run_with_opts(name, content, &["-O1".to_string()]) +} + +/// Compile inline C code with extra options and run, returning exit code +/// Temp files are cleaned up automatically via tempfile +pub fn compile_and_run_with_opts(name: &str, content: &str, extra_opts: &[String]) -> i32 { let c_file = create_c_file(name, content); let c_path = c_file.path().to_path_buf(); let exe_path = std::env::temp_dir().join(format!("pcc_exe_{}", name)); - let output = run_test_base( - "pcc", - &vec![ - "-o".to_string(), - exe_path.to_string_lossy().to_string(), - c_path.to_string_lossy().to_string(), - ], - &[], - ); + let mut args = vec!["-o".to_string(), exe_path.to_string_lossy().to_string()]; + args.extend(extra_opts.iter().cloned()); + args.push(c_path.to_string_lossy().to_string()); + + let output = run_test_base("pcc", &args, &[]); if !output.status.success() { eprintln!( diff --git a/cc/tests/features/mod.rs b/cc/tests/features/mod.rs index 99faefbb..9cb8c0c9 100644 --- a/cc/tests/features/mod.rs +++ b/cc/tests/features/mod.rs @@ -14,6 +14,7 @@ mod bswap; mod constant_p; mod debug; mod has_feature; +mod optimization; mod storage; mod types_compatible; mod varargs; diff --git a/cc/tests/features/optimization.rs b/cc/tests/features/optimization.rs new file mode 100644 index 00000000..60671a3c --- /dev/null +++ b/cc/tests/features/optimization.rs @@ -0,0 +1,184 @@ +// +// Copyright (c) 2024 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 +// +// Tests for compiler optimization passes (-O flag) +// +// These tests verify that optimizations don't break program correctness. +// Tests are aggregated to reduce compile/link cycles. +// +// Note: MVP InstCombine only does algebraic simplifications (x + 0 -> x, etc.) +// and does NOT do constant folding (2 + 3 -> 5) because that would require +// creating new constant pseudos. +// + +use crate::common::compile_and_run_optimized; + +// ============================================================================ +// Basic optimization correctness tests +// ============================================================================ + +/// Test that -O doesn't break basic arithmetic and control flow +/// Focuses on algebraic simplifications that the MVP supports +#[test] +fn optimization_basic_correctness() { + let code = r#" +int main(void) { + // Basic arithmetic (should not be broken by optimizer) + int a = 2 + 3; + int b = 10 - 10; + int c = 4 * 1; + int d = 0 + 7; + + if (a != 5) return 1; + if (b != 0) return 2; + if (c != 4) return 3; + if (d != 7) return 4; + + // Algebraic simplifications (MVP supports these) + int x = 42; + int y = x + 0; // Should simplify to x + int z = x * 1; // Should simplify to x + int w = x - 0; // Should simplify to x + + if (y != 42) return 5; + if (z != 42) return 6; + if (w != 42) return 7; + + // Identity patterns (MVP supports x & x -> x, x | x -> x) + int r = x & x; // Should be x + int s = x | x; // Should be x + + if (r != 42) return 10; + if (s != 42) return 11; + + // Bitwise with zero constants + int u = x | 0; // Should be x + int v = x ^ 0; // Should be x + + if (u != 42) return 13; + if (v != 42) return 14; + + // Shifts by zero + int sh1 = x << 0; // Should be x + int sh2 = x >> 0; // Should be x + + if (sh1 != 42) return 15; + if (sh2 != 42) return 16; + + return 0; +} +"#; + assert_eq!(compile_and_run_optimized("opt_basic", code), 0); +} + +/// Test optimization with loops (dead code inside loops) +#[test] +fn optimization_loops() { + let code = r#" +int main(void) { + int sum = 0; + + // Loop with potential dead code + for (int i = 0; i < 10; i++) { + int live = i + 1; // Live: contributes to sum + sum += live; + } + + // sum should be 1+2+3+4+5+6+7+8+9+10 = 55 + if (sum != 55) return 1; + + // While loop + int count = 0; + int j = 5; + while (j > 0) { + int unused = j + 0; // Simplifies to j, but unused (DCE candidate) + count++; + j--; + } + + if (count != 5) return 2; + + return 0; +} +"#; + assert_eq!(compile_and_run_optimized("opt_loops", code), 0); +} + +/// Test optimization with function calls (calls must not be eliminated) +#[test] +fn optimization_function_calls() { + let code = r#" +int counter = 0; + +int side_effect(int x) { + counter++; + return x * 2; +} + +int main(void) { + // Call with unused result - must still execute for side effect + int unused = side_effect(5); + + // Call with used result + int used = side_effect(10); + if (used != 20) return 1; + + // Counter should be 2 (both calls executed) + if (counter != 2) return 2; + + return 0; +} +"#; + assert_eq!(compile_and_run_optimized("opt_calls", code), 0); +} + +/// Test that comparisons work correctly with optimization +#[test] +fn optimization_comparisons() { + let code = r#" +int main(void) { + int x = 5; + int y = 5; + int z = 10; + + // Basic comparisons should still work + if (x != y) return 1; + if (x == z) return 2; + if (!(x < z)) return 3; + if (!(z > x)) return 4; + if (x > z) return 5; + if (z < x) return 6; + + return 0; +} +"#; + assert_eq!(compile_and_run_optimized("opt_cmp", code), 0); +} + +/// Test that stores are not eliminated (side effects) +#[test] +fn optimization_stores_preserved() { + let code = r#" +int global_var = 0; + +int main(void) { + // Stores to global must be preserved + global_var = 42; + + // Local that's written but read later + int local = 10; + local = local + 5; + + if (global_var != 42) return 1; + if (local != 15) return 2; + + return 0; +} +"#; + assert_eq!(compile_and_run_optimized("opt_stores", code), 0); +} From fbaefcf768404586fe2357c9d3e13ea3672f543f Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Sun, 7 Dec 2025 15:04:18 -0500 Subject: [PATCH 2/7] [cc] constant folding opt --- cc/README.md | 1 + cc/TODO.md | 179 ++++++++ cc/instcombine.rs | 1080 ++++++++++++++++++++++++++++++++++++++------- cc/ir.rs | 22 + 4 files changed, 1113 insertions(+), 169 deletions(-) diff --git a/cc/README.md b/cc/README.md index 91086642..0c71bd01 100644 --- a/cc/README.md +++ b/cc/README.md @@ -99,6 +99,7 @@ Not yet implemented: - multi-register returns (for structs larger than 8 bytes) - -fverbose-asm - Complex initializers +- constant expression evaluation - VLAs (variable-length arrays) - top builtins to implement: __builtin_expect diff --git a/cc/TODO.md b/cc/TODO.md index a3908d01..ae6ad99f 100644 --- a/cc/TODO.md +++ b/cc/TODO.md @@ -498,3 +498,182 @@ _Noreturn void exit(int status); 2. Type system: mark function type as noreturn 3. Semantic: warn if function can return 4. Codegen: can omit function epilogue, enable optimizations + +--- + +## Optimization Passes + +### Overview + +The compiler uses a sparse-style SSA IR, which is well-suited for classical optimizations. Passes are run iteratively until a fixed point is reached. + +### Pass 1: SCCP - Sparse Conditional Constant Propagation + +**Status:** Not implemented + +**What it does:** +- Propagate constants through the CFG, only along reachable paths +- If a branch's condition is constant, mark only that successor reachable +- Lattice: `{UNDEF, CONST(c), UNKNOWN}` +- Forward dataflow across the CFG updating φs + +**Simpler alternative:** Global constant propagation without conditional pruning. + +### Pass 2: CFG Simplification + +**Status:** Not implemented + +**What it does:** +- Convert constant-condition branches into unconditional jumps +- Merge simple blocks: A → B where A's only successor is B and B's only predecessor is A +- Remove jumps to jumps (fallthrough simplification) + +**Implementation:** +- After DCE, walk the CFG +- If branch/switch has constant condition → replace with direct jump +- If block just jumps, no φ, no side effects → inline successor or merge + +### Pass 3: Copy Propagation & SSA Cleanup + +**Status:** Not implemented + +**What it does:** +- `t1 = x; y = t1;` → `y = x` +- φ simplifications: `t = φ(x, x, x)` → `t = x` + +**Implementation:** +- Use SSA def-use chains +- For each `t = x` where x is SSA value and t isn't address-taken, replace uses of t with x +- For φ-nodes where all incoming operands are same → replace uses and remove φ +- Run DCE afterwards + +### Pass 4: Local CSE / Value Numbering + +**Status:** Not implemented + +**What it does:** +- Inside a block, recognize when re-computing same pure expression +- `t1 = a + b; t2 = a + b;` → `t2 = t1` + +**Implementation:** +- For each block: maintain map `(opcode, operand1, operand2, type) → SSA value` +- When expression already in map, reuse prior value +- Limit to pure operations: arithmetic, logical ops, comparisons + +### Pass 5: GVN - Global Value Numbering + +**Status:** Not implemented (optional, bigger investment) + +**What it does:** +- Deduplicate computations across blocks, not just inside one +- More global version of CSE respecting control flow and dominance + +**Implementation:** +- Walk in dominator order +- Assign "value numbers" to expressions +- Equivalent expressions sharing same number are merged + +### Pass 6: Conservative Function Inlining + +**Status:** Not implemented + +**What it does:** +- Inline small, non-recursive, non-varargs functions into callers +- Exposes new optimization opportunities + +**Constraints:** +- Only inline static/internal functions +- Limit by IR size: e.g. ≤ N instructions, ≤ M basic blocks +- Reject varargs, VLAs, alloca, weird control flow + +**After inlining:** Re-run InstCombine → SCCP → DCE + +### Pass 7: LICM - Loop-Invariant Code Motion + +**Status:** Not implemented + +**What it does:** +- Hoist computations that are pure and loop-invariant + +**Implementation:** +- Detect natural loops via back edges and dominators +- Only hoist arithmetic/logical ops whose operands are defined outside loop +- Don't hoist loads/stores without alias analysis + +### Pass 8: Loop Canonicalization & Strength Reduction + +**Status:** Not implemented (low priority) + +**What it does:** +- Normalize induction variables: `for (i = 0; i < n; ++i)` style +- Replace multiplications with additions + +**Implementation:** +- Identify induction variables: φ node in loop header with one incoming from preheader, one from latch +- Handle simple patterns: `i = φ(i0, i + c)` +- Turn `base + i * c` patterns into increments + +### Suggested Pass Pipeline + +``` +IR generation + SSA construction + ↓ +Early InstCombine (constant folding + algebraic) + ↓ +SCCP (or simple global const prop) + ↓ +DCE + unreachable block removal + ↓ +CFG simplification + ↓ +Copy propagation + φ simplification → DCE + ↓ +Local CSE → InstCombine + ↓ +[Later] GVN → DCE/CFG simplify + ↓ +[Later] Conservative inlining → re-run passes 2-7 + ↓ +[Later] LICM + loop opts → final InstCombine + DCE +``` + +### Implementation Priority + +| Priority | Pass | Complexity | Impact | +|----------|------|------------|--------| +| 1 | CFG simplify | Low | Medium | +| 2 | Copy/φ cleanup | Low | Medium | +| 3 | Local CSE | Medium | Medium | +| 4 | SCCP | Medium | High | +| 5 | GVN | High | Medium | +| 6 | Inlining | High | High | +| 7 | LICM | Medium | Medium | +| 8 | Loop opts | High | Low | + +--- + +## Assembly Peephole Optimizations + +### Overview + +Post-codegen peephole optimizations on the generated assembly. These are low-complexity, high-impact micro-optimizations. + +### Potential Optimizations + +| Pattern | Optimization | +|---------|--------------| +| `mov %rax, %rax` | Delete (no-op move) | +| `mov %rax, %rbx; mov %rbx, %rax` | Delete second (useless copy-back) | +| `add $0, %rax` | Delete (no-op add) | +| `sub $0, %rax` | Delete (no-op sub) | +| `imul $1, %rax, %rax` | Delete (multiply by 1) | +| `xor %rax, %rax; mov $0, %rax` | Keep only xor (shorter encoding) | +| `cmp $0, %rax; je L` | `test %rax, %rax; je L` (shorter) | +| `mov $imm, %rax; add %rax, %rbx` | `add $imm, %rbx` if imm fits | + +### Implementation Approach + +1. Parse LIR instructions before emission +2. Pattern match on instruction sequences (2-3 instruction windows) +3. Replace with optimized sequences +4. Run multiple passes until no changes diff --git a/cc/instcombine.rs b/cc/instcombine.rs index 0f5fd27d..3ea987bb 100644 --- a/cc/instcombine.rs +++ b/cc/instcombine.rs @@ -15,6 +15,21 @@ // use crate::ir::{Function, Instruction, Opcode, Pseudo, PseudoId, PseudoKind}; +use crate::types::TypeId; + +// ============================================================================ +// Simplification Result +// ============================================================================ + +/// Result of trying to simplify an instruction +enum Simplification { + /// No simplification possible + None, + /// Copy from an existing pseudo (algebraic identity) + CopyFrom(PseudoId), + /// Create a new constant with this value and copy from it + FoldToConst(i64), +} // ============================================================================ // Main Entry Point @@ -25,15 +40,39 @@ use crate::ir::{Function, Instruction, Opcode, Pseudo, PseudoId, PseudoKind}; pub fn run(func: &mut Function) -> bool { let mut changed = false; - for bb in &mut func.blocks { - for insn in &mut bb.insns { - if let Some(new_insn) = try_simplify(insn, &func.pseudos) { - *insn = new_insn; - changed = true; + // Collect all simplifications first (to avoid borrow conflicts) + let mut simplifications: Vec<(usize, usize, Simplification)> = Vec::new(); + + for (bb_idx, bb) in func.blocks.iter().enumerate() { + for (insn_idx, insn) in bb.insns.iter().enumerate() { + let result = try_simplify(insn, &func.pseudos); + if !matches!(result, Simplification::None) { + simplifications.push((bb_idx, insn_idx, result)); } } } + // Apply simplifications + for (bb_idx, insn_idx, simplification) in simplifications { + // Extract necessary data from the instruction before any mutation + let (target, typ, size) = { + let insn = &func.blocks[bb_idx].insns[insn_idx]; + (insn.target, insn.typ, insn.size) + }; + + let new_insn = match simplification { + Simplification::None => continue, + Simplification::CopyFrom(src) => make_copy_from_parts(target, typ, size, src), + Simplification::FoldToConst(value) => { + // Create a new constant pseudo + let const_id = func.create_const_pseudo(value); + make_copy_from_parts(target, typ, size, const_id) + } + }; + func.blocks[bb_idx].insns[insn_idx] = new_insn; + changed = true; + } + changed } @@ -41,8 +80,8 @@ pub fn run(func: &mut Function) -> bool { // Simplification Dispatch // ============================================================================ -/// Try to simplify an instruction. Returns Some(new_instruction) if simplified. -fn try_simplify(insn: &Instruction, pseudos: &[Pseudo]) -> Option { +/// Try to simplify an instruction. Returns the simplification to apply. +fn try_simplify(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { match insn.op { // Integer arithmetic Opcode::Add => simplify_add(insn, pseudos), @@ -52,9 +91,9 @@ fn try_simplify(insn: &Instruction, pseudos: &[Pseudo]) -> Option { Opcode::ModS | Opcode::ModU => simplify_mod(insn, pseudos), // Shifts - Opcode::Shl => simplify_shift(insn, pseudos), - Opcode::Lsr => simplify_shift(insn, pseudos), - Opcode::Asr => simplify_shift(insn, pseudos), + Opcode::Shl => simplify_shl(insn, pseudos), + Opcode::Lsr => simplify_lsr(insn, pseudos), + Opcode::Asr => simplify_asr(insn, pseudos), // Bitwise Opcode::And => simplify_and(insn, pseudos), @@ -62,22 +101,22 @@ fn try_simplify(insn: &Instruction, pseudos: &[Pseudo]) -> Option { Opcode::Xor => simplify_xor(insn, pseudos), // Comparisons - Opcode::SetEq - | Opcode::SetNe - | Opcode::SetLt - | Opcode::SetLe - | Opcode::SetGt - | Opcode::SetGe - | Opcode::SetB - | Opcode::SetBe - | Opcode::SetA - | Opcode::SetAe => simplify_cmp(insn, pseudos), + Opcode::SetEq => simplify_seteq(insn, pseudos), + Opcode::SetNe => simplify_setne(insn, pseudos), + Opcode::SetLt => simplify_setlt(insn, pseudos), + Opcode::SetLe => simplify_setle(insn, pseudos), + Opcode::SetGt => simplify_setgt(insn, pseudos), + Opcode::SetGe => simplify_setge(insn, pseudos), + Opcode::SetB => simplify_setb(insn, pseudos), + Opcode::SetBe => simplify_setbe(insn, pseudos), + Opcode::SetA => simplify_seta(insn, pseudos), + Opcode::SetAe => simplify_setae(insn, pseudos), // Unary Opcode::Neg => simplify_neg(insn, pseudos), Opcode::Not => simplify_not(insn, pseudos), - _ => None, + _ => Simplification::None, } } @@ -95,31 +134,19 @@ fn get_const(pseudos: &[Pseudo], id: PseudoId) -> Option { }) } -/// Create a Copy instruction that copies a constant source. -/// For constant folding, we need to have an existing constant pseudo to copy from. -/// Since we can't easily create new constants here, we return None if we can't -/// find a matching constant. This is handled by returning None from the -/// simplify functions when both operands are constants but we can't fold. -fn make_const_result(insn: &Instruction, src_const: PseudoId) -> Instruction { - // Copy from the constant pseudo - Instruction { - op: Opcode::Copy, - target: insn.target, - src: vec![src_const], - typ: insn.typ, - size: insn.size, - ..Default::default() - } -} - -/// Create a Copy instruction that copies one value to another. -fn make_copy(insn: &Instruction, src: PseudoId) -> Instruction { +/// Create a Copy instruction from extracted parts. +fn make_copy_from_parts( + target: Option, + typ: Option, + size: u32, + src: PseudoId, +) -> Instruction { Instruction { op: Opcode::Copy, - target: insn.target, + target, src: vec![src], - typ: insn.typ, - size: insn.size, + typ, + size, ..Default::default() } } @@ -128,9 +155,9 @@ fn make_copy(insn: &Instruction, src: PseudoId) -> Instruction { // Add Simplification // ============================================================================ -fn simplify_add(insn: &Instruction, pseudos: &[Pseudo]) -> Option { +fn simplify_add(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { if insn.src.len() != 2 { - return None; + return Simplification::None; } let src1 = insn.src[0]; @@ -139,16 +166,16 @@ fn simplify_add(insn: &Instruction, pseudos: &[Pseudo]) -> Option { let val2 = get_const(pseudos, src2); match (val1, val2) { + // Constant folding: a + b -> (a + b) + (Some(a), Some(b)) => Simplification::FoldToConst(a.wrapping_add(b)), + // Algebraic: x + 0 -> x - (None, Some(0)) => Some(make_copy(insn, src1)), + (None, Some(0)) => Simplification::CopyFrom(src1), // Algebraic: 0 + x -> x - (Some(0), None) => Some(make_copy(insn, src2)), + (Some(0), None) => Simplification::CopyFrom(src2), - // Note: We can't fold constants (a + b -> result) because we can't - // create new constant pseudos without modifying the function's pseudo list. - // This would require passing &mut Function or a more complex design. - _ => None, + _ => Simplification::None, } } @@ -156,33 +183,37 @@ fn simplify_add(insn: &Instruction, pseudos: &[Pseudo]) -> Option { // Sub Simplification // ============================================================================ -fn simplify_sub(insn: &Instruction, pseudos: &[Pseudo]) -> Option { +fn simplify_sub(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { if insn.src.len() != 2 { - return None; + return Simplification::None; } let src1 = insn.src[0]; let src2 = insn.src[1]; + let val1 = get_const(pseudos, src1); let val2 = get_const(pseudos, src2); - // Note: x - x -> 0 requires creating a zero constant, which we can't do - // without modifying the function's pseudo list. Skip for MVP. + match (val1, val2) { + // Constant folding: a - b -> (a - b) + (Some(a), Some(b)) => Simplification::FoldToConst(a.wrapping_sub(b)), + + // Algebraic: x - 0 -> x + (None, Some(0)) => Simplification::CopyFrom(src1), - // Algebraic: x - 0 -> x - if val2 == Some(0) { - return Some(make_copy(insn, src1)); + _ => Simplification::None, } - None + // Identity: x - x -> 0 is handled by constant folding if both are constants, + // but for same-pseudo case we need a separate check } // ============================================================================ // Mul Simplification // ============================================================================ -fn simplify_mul(insn: &Instruction, pseudos: &[Pseudo]) -> Option { +fn simplify_mul(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { if insn.src.len() != 2 { - return None; + return Simplification::None; } let src1 = insn.src[0]; @@ -191,15 +222,18 @@ fn simplify_mul(insn: &Instruction, pseudos: &[Pseudo]) -> Option { let val2 = get_const(pseudos, src2); match (val1, val2) { - // Algebraic: x * 0 -> 0 (copy from the zero constant) - (None, Some(0)) => Some(make_const_result(insn, src2)), - (Some(0), None) => Some(make_const_result(insn, src1)), + // Constant folding: a * b -> (a * b) + (Some(a), Some(b)) => Simplification::FoldToConst(a.wrapping_mul(b)), + + // Algebraic: x * 0 -> 0 + (None, Some(0)) => Simplification::FoldToConst(0), + (Some(0), None) => Simplification::FoldToConst(0), // Algebraic: x * 1 -> x - (None, Some(1)) => Some(make_copy(insn, src1)), - (Some(1), None) => Some(make_copy(insn, src2)), + (None, Some(1)) => Simplification::CopyFrom(src1), + (Some(1), None) => Simplification::CopyFrom(src2), - _ => None, + _ => Simplification::None, } } @@ -207,9 +241,9 @@ fn simplify_mul(insn: &Instruction, pseudos: &[Pseudo]) -> Option { // Div Simplification // ============================================================================ -fn simplify_div(insn: &Instruction, pseudos: &[Pseudo]) -> Option { +fn simplify_div(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { if insn.src.len() != 2 { - return None; + return Simplification::None; } let src1 = insn.src[0]; @@ -218,13 +252,22 @@ fn simplify_div(insn: &Instruction, pseudos: &[Pseudo]) -> Option { let val2 = get_const(pseudos, src2); match (val1, val2) { + // Constant folding: a / b -> (a / b) (avoid div by zero) + (Some(a), Some(b)) if b != 0 => { + if insn.op == Opcode::DivS { + Simplification::FoldToConst(a.wrapping_div(b)) + } else { + Simplification::FoldToConst((a as u64).wrapping_div(b as u64) as i64) + } + } + // Algebraic: x / 1 -> x - (None, Some(1)) => Some(make_copy(insn, src1)), + (None, Some(1)) => Simplification::CopyFrom(src1), - // Algebraic: 0 / x -> 0 (copy from the zero constant) - (Some(0), None) => Some(make_const_result(insn, src1)), + // Algebraic: 0 / x -> 0 + (Some(0), None) => Simplification::FoldToConst(0), - _ => None, + _ => Simplification::None, } } @@ -232,30 +275,95 @@ fn simplify_div(insn: &Instruction, pseudos: &[Pseudo]) -> Option { // Mod Simplification // ============================================================================ -fn simplify_mod(insn: &Instruction, pseudos: &[Pseudo]) -> Option { +fn simplify_mod(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { if insn.src.len() != 2 { - return None; + return Simplification::None; } let src1 = insn.src[0]; + let src2 = insn.src[1]; let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); - // Algebraic: 0 % x -> 0 (copy from the zero constant) - if val1 == Some(0) { - return Some(make_const_result(insn, src1)); - } + match (val1, val2) { + // Constant folding: a % b -> (a % b) (avoid mod by zero) + (Some(a), Some(b)) if b != 0 => { + if insn.op == Opcode::ModS { + Simplification::FoldToConst(a.wrapping_rem(b)) + } else { + Simplification::FoldToConst((a as u64).wrapping_rem(b as u64) as i64) + } + } + + // Algebraic: 0 % x -> 0 + (Some(0), None) => Simplification::FoldToConst(0), - // Note: x % 1 -> 0 requires a zero constant we may not have - None + // Algebraic: x % 1 -> 0 + (None, Some(1)) => Simplification::FoldToConst(0), + + _ => Simplification::None, + } } // ============================================================================ -// Shift Simplification +// Shift Simplifications // ============================================================================ -fn simplify_shift(insn: &Instruction, pseudos: &[Pseudo]) -> Option { +fn simplify_shl(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { + if insn.src.len() != 2 { + return Simplification::None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + match (val1, val2) { + // Constant folding: a << b + (Some(a), Some(b)) if (0..64).contains(&b) => { + Simplification::FoldToConst(a.wrapping_shl(b as u32)) + } + + // Algebraic: x << 0 -> x + (None, Some(0)) => Simplification::CopyFrom(src1), + + // Algebraic: 0 << n -> 0 + (Some(0), None) => Simplification::FoldToConst(0), + + _ => Simplification::None, + } +} + +fn simplify_lsr(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { + if insn.src.len() != 2 { + return Simplification::None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + match (val1, val2) { + // Constant folding: a >> b (logical/unsigned) + (Some(a), Some(b)) if (0..64).contains(&b) => { + Simplification::FoldToConst((a as u64).wrapping_shr(b as u32) as i64) + } + + // Algebraic: x >> 0 -> x + (None, Some(0)) => Simplification::CopyFrom(src1), + + // Algebraic: 0 >> n -> 0 + (Some(0), None) => Simplification::FoldToConst(0), + + _ => Simplification::None, + } +} + +fn simplify_asr(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { if insn.src.len() != 2 { - return None; + return Simplification::None; } let src1 = insn.src[0]; @@ -264,13 +372,18 @@ fn simplify_shift(insn: &Instruction, pseudos: &[Pseudo]) -> Option let val2 = get_const(pseudos, src2); match (val1, val2) { - // Algebraic: x << 0 -> x, x >> 0 -> x - (None, Some(0)) => Some(make_copy(insn, src1)), + // Constant folding: a >> b (arithmetic/signed) + (Some(a), Some(b)) if (0..64).contains(&b) => { + Simplification::FoldToConst(a.wrapping_shr(b as u32)) + } + + // Algebraic: x >> 0 -> x + (None, Some(0)) => Simplification::CopyFrom(src1), - // Algebraic: 0 << n -> 0, 0 >> n -> 0 (copy from zero constant) - (Some(0), None) => Some(make_const_result(insn, src1)), + // Algebraic: 0 >> n -> 0 + (Some(0), None) => Simplification::FoldToConst(0), - _ => None, + _ => Simplification::None, } } @@ -278,9 +391,9 @@ fn simplify_shift(insn: &Instruction, pseudos: &[Pseudo]) -> Option // And Simplification // ============================================================================ -fn simplify_and(insn: &Instruction, pseudos: &[Pseudo]) -> Option { +fn simplify_and(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { if insn.src.len() != 2 { - return None; + return Simplification::None; } let src1 = insn.src[0]; @@ -290,19 +403,22 @@ fn simplify_and(insn: &Instruction, pseudos: &[Pseudo]) -> Option { // Identity: x & x -> x if src1 == src2 { - return Some(make_copy(insn, src1)); + return Simplification::CopyFrom(src1); } match (val1, val2) { - // Algebraic: x & 0 -> 0 (copy from zero constant) - (None, Some(0)) => Some(make_const_result(insn, src2)), - (Some(0), None) => Some(make_const_result(insn, src1)), + // Constant folding: a & b + (Some(a), Some(b)) => Simplification::FoldToConst(a & b), + + // Algebraic: x & 0 -> 0 + (None, Some(0)) => Simplification::FoldToConst(0), + (Some(0), None) => Simplification::FoldToConst(0), // Algebraic: x & -1 -> x (all bits set) - (None, Some(-1)) => Some(make_copy(insn, src1)), - (Some(-1), None) => Some(make_copy(insn, src2)), + (None, Some(-1)) => Simplification::CopyFrom(src1), + (Some(-1), None) => Simplification::CopyFrom(src2), - _ => None, + _ => Simplification::None, } } @@ -310,9 +426,9 @@ fn simplify_and(insn: &Instruction, pseudos: &[Pseudo]) -> Option { // Or Simplification // ============================================================================ -fn simplify_or(insn: &Instruction, pseudos: &[Pseudo]) -> Option { +fn simplify_or(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { if insn.src.len() != 2 { - return None; + return Simplification::None; } let src1 = insn.src[0]; @@ -322,19 +438,22 @@ fn simplify_or(insn: &Instruction, pseudos: &[Pseudo]) -> Option { // Identity: x | x -> x if src1 == src2 { - return Some(make_copy(insn, src1)); + return Simplification::CopyFrom(src1); } match (val1, val2) { + // Constant folding: a | b + (Some(a), Some(b)) => Simplification::FoldToConst(a | b), + // Algebraic: x | 0 -> x - (None, Some(0)) => Some(make_copy(insn, src1)), - (Some(0), None) => Some(make_copy(insn, src2)), + (None, Some(0)) => Simplification::CopyFrom(src1), + (Some(0), None) => Simplification::CopyFrom(src2), - // Algebraic: x | -1 -> -1 (copy from the -1 constant) - (None, Some(-1)) => Some(make_const_result(insn, src2)), - (Some(-1), None) => Some(make_const_result(insn, src1)), + // Algebraic: x | -1 -> -1 + (None, Some(-1)) => Simplification::FoldToConst(-1), + (Some(-1), None) => Simplification::FoldToConst(-1), - _ => None, + _ => Simplification::None, } } @@ -342,9 +461,9 @@ fn simplify_or(insn: &Instruction, pseudos: &[Pseudo]) -> Option { // Xor Simplification // ============================================================================ -fn simplify_xor(insn: &Instruction, pseudos: &[Pseudo]) -> Option { +fn simplify_xor(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { if insn.src.len() != 2 { - return None; + return Simplification::None; } let src1 = insn.src[0]; @@ -352,52 +471,282 @@ fn simplify_xor(insn: &Instruction, pseudos: &[Pseudo]) -> Option { let val1 = get_const(pseudos, src1); let val2 = get_const(pseudos, src2); - // Note: x ^ x -> 0 requires creating a zero constant, skip for MVP + // Identity: x ^ x -> 0 + if src1 == src2 { + return Simplification::FoldToConst(0); + } match (val1, val2) { + // Constant folding: a ^ b + (Some(a), Some(b)) => Simplification::FoldToConst(a ^ b), + // Algebraic: x ^ 0 -> x - (None, Some(0)) => Some(make_copy(insn, src1)), - (Some(0), None) => Some(make_copy(insn, src2)), + (None, Some(0)) => Simplification::CopyFrom(src1), + (Some(0), None) => Simplification::CopyFrom(src2), - _ => None, + _ => Simplification::None, } } // ============================================================================ -// Comparison Simplification +// Comparison Simplifications // ============================================================================ -fn simplify_cmp(insn: &Instruction, _pseudos: &[Pseudo]) -> Option { +fn simplify_seteq(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { + if insn.src.len() != 2 { + return Simplification::None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + + // Identity: x == x -> 1 + if src1 == src2 { + return Simplification::FoldToConst(1); + } + + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + if let (Some(a), Some(b)) = (val1, val2) { + Simplification::FoldToConst(if a == b { 1 } else { 0 }) + } else { + Simplification::None + } +} + +fn simplify_setne(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { + if insn.src.len() != 2 { + return Simplification::None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + + // Identity: x != x -> 0 + if src1 == src2 { + return Simplification::FoldToConst(0); + } + + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + if let (Some(a), Some(b)) = (val1, val2) { + Simplification::FoldToConst(if a != b { 1 } else { 0 }) + } else { + Simplification::None + } +} + +fn simplify_setlt(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { + if insn.src.len() != 2 { + return Simplification::None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + + // Identity: x < x -> 0 + if src1 == src2 { + return Simplification::FoldToConst(0); + } + + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + if let (Some(a), Some(b)) = (val1, val2) { + Simplification::FoldToConst(if a < b { 1 } else { 0 }) + } else { + Simplification::None + } +} + +fn simplify_setle(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { + if insn.src.len() != 2 { + return Simplification::None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + + // Identity: x <= x -> 1 + if src1 == src2 { + return Simplification::FoldToConst(1); + } + + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + if let (Some(a), Some(b)) = (val1, val2) { + Simplification::FoldToConst(if a <= b { 1 } else { 0 }) + } else { + Simplification::None + } +} + +fn simplify_setgt(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { + if insn.src.len() != 2 { + return Simplification::None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + + // Identity: x > x -> 0 + if src1 == src2 { + return Simplification::FoldToConst(0); + } + + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + if let (Some(a), Some(b)) = (val1, val2) { + Simplification::FoldToConst(if a > b { 1 } else { 0 }) + } else { + Simplification::None + } +} + +fn simplify_setge(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { + if insn.src.len() != 2 { + return Simplification::None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + + // Identity: x >= x -> 1 + if src1 == src2 { + return Simplification::FoldToConst(1); + } + + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + if let (Some(a), Some(b)) = (val1, val2) { + Simplification::FoldToConst(if a >= b { 1 } else { 0 }) + } else { + Simplification::None + } +} + +// Unsigned comparisons +fn simplify_setb(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { + if insn.src.len() != 2 { + return Simplification::None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + + if src1 == src2 { + return Simplification::FoldToConst(0); + } + + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + if let (Some(a), Some(b)) = (val1, val2) { + Simplification::FoldToConst(if (a as u64) < (b as u64) { 1 } else { 0 }) + } else { + Simplification::None + } +} + +fn simplify_setbe(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { + if insn.src.len() != 2 { + return Simplification::None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + + if src1 == src2 { + return Simplification::FoldToConst(1); + } + + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + if let (Some(a), Some(b)) = (val1, val2) { + Simplification::FoldToConst(if (a as u64) <= (b as u64) { 1 } else { 0 }) + } else { + Simplification::None + } +} + +fn simplify_seta(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { + if insn.src.len() != 2 { + return Simplification::None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + + if src1 == src2 { + return Simplification::FoldToConst(0); + } + + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); + + if let (Some(a), Some(b)) = (val1, val2) { + Simplification::FoldToConst(if (a as u64) > (b as u64) { 1 } else { 0 }) + } else { + Simplification::None + } +} + +fn simplify_setae(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { if insn.src.len() != 2 { - return None; + return Simplification::None; + } + + let src1 = insn.src[0]; + let src2 = insn.src[1]; + + if src1 == src2 { + return Simplification::FoldToConst(1); } - // Note: x cmp x patterns require creating 0 or 1 constants, skip for MVP - // Note: Constant folding also requires creating new constants, skip for MVP + let val1 = get_const(pseudos, src1); + let val2 = get_const(pseudos, src2); - None + if let (Some(a), Some(b)) = (val1, val2) { + Simplification::FoldToConst(if (a as u64) >= (b as u64) { 1 } else { 0 }) + } else { + Simplification::None + } } // ============================================================================ -// Unary Simplification +// Unary Simplifications // ============================================================================ -fn simplify_neg(insn: &Instruction, _pseudos: &[Pseudo]) -> Option { +fn simplify_neg(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { if insn.src.len() != 1 { - return None; + return Simplification::None; } - // Note: Constant folding requires creating new constants, skip for MVP - None + let src = insn.src[0]; + if let Some(val) = get_const(pseudos, src) { + Simplification::FoldToConst(val.wrapping_neg()) + } else { + Simplification::None + } } -fn simplify_not(insn: &Instruction, _pseudos: &[Pseudo]) -> Option { +fn simplify_not(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { if insn.src.len() != 1 { - return None; + return Simplification::None; } - // Note: Constant folding requires creating new constants, skip for MVP - None + let src = insn.src[0]; + if let Some(val) = get_const(pseudos, src) { + Simplification::FoldToConst(!val) + } else { + Simplification::None + } } // ============================================================================ @@ -428,6 +777,10 @@ mod tests { func } + // ======================================================================== + // Algebraic Simplification Tests + // ======================================================================== + #[test] fn test_add_zero_right() { // x + 0 -> x @@ -482,33 +835,6 @@ mod tests { assert_eq!(result_insn.src, vec![PseudoId(1)]); } - #[test] - fn test_mul_zero() { - // x * 0 -> 0 (copy from zero constant) - let types = TypeTable::new(); - let insn = Instruction::binop( - Opcode::Mul, - PseudoId(2), - PseudoId(0), - PseudoId(1), // 0 - types.int_id, - 32, - ); - let pseudos = vec![ - Pseudo::reg(PseudoId(0), 0), - Pseudo::val(PseudoId(1), 0), - Pseudo::reg(PseudoId(2), 2), - ]; - let mut func = make_test_func_with_insn(insn, pseudos); - - let changed = run(&mut func); - assert!(changed); - - let result_insn = &func.blocks[0].insns[1]; - assert_eq!(result_insn.op, Opcode::Copy); - assert_eq!(result_insn.src, vec![PseudoId(1)]); // Copy from zero constant - } - #[test] fn test_mul_one() { // x * 1 -> x @@ -610,22 +936,46 @@ mod tests { } #[test] - fn test_no_change_for_non_const() { - // x + y (neither const) -> no change + fn test_xor_self() { + // x ^ x -> 0 let types = TypeTable::new(); let insn = Instruction::binop( - Opcode::Add, - PseudoId(2), - PseudoId(0), + Opcode::Xor, PseudoId(1), + PseudoId(0), + PseudoId(0), types.int_id, 32, ); - let pseudos = vec![ - Pseudo::reg(PseudoId(0), 0), - Pseudo::reg(PseudoId(1), 1), - Pseudo::reg(PseudoId(2), 2), - ]; + let pseudos = vec![Pseudo::reg(PseudoId(0), 0), Pseudo::reg(PseudoId(1), 1)]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + // The new constant (0) was created and copied from + assert!(func.pseudos.len() > 2); + } + + #[test] + fn test_no_change_for_non_const() { + // x + y (neither const) -> no change + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Add, + PseudoId(2), + PseudoId(0), + PseudoId(1), + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::reg(PseudoId(0), 0), + Pseudo::reg(PseudoId(1), 1), + Pseudo::reg(PseudoId(2), 2), + ]; let mut func = make_test_func_with_insn(insn, pseudos); let changed = run(&mut func); @@ -635,9 +985,13 @@ mod tests { assert_eq!(result_insn.op, Opcode::Add); } + // ======================================================================== + // Constant Folding Tests + // ======================================================================== + #[test] - fn test_no_const_folding() { - // 2 + 3 -> no change (MVP doesn't create new constants) + fn test_const_fold_add() { + // 2 + 3 -> 5 let types = TypeTable::new(); let insn = Instruction::binop( Opcode::Add, @@ -655,9 +1009,397 @@ mod tests { let mut func = make_test_func_with_insn(insn, pseudos); let changed = run(&mut func); - assert!(!changed); // MVP doesn't fold constants + assert!(changed); let result_insn = &func.blocks[0].insns[1]; - assert_eq!(result_insn.op, Opcode::Add); + assert_eq!(result_insn.op, Opcode::Copy); + + // Find the new constant pseudo that was created + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(5)); + } + + #[test] + fn test_const_fold_sub() { + // 10 - 3 -> 7 + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Sub, + PseudoId(2), + PseudoId(0), + PseudoId(1), + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::val(PseudoId(0), 10), + Pseudo::val(PseudoId(1), 3), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(7)); + } + + #[test] + fn test_const_fold_mul() { + // 6 * 7 -> 42 + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Mul, + PseudoId(2), + PseudoId(0), + PseudoId(1), + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::val(PseudoId(0), 6), + Pseudo::val(PseudoId(1), 7), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(42)); + } + + #[test] + fn test_const_fold_mul_zero() { + // x * 0 -> 0 + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Mul, + PseudoId(2), + PseudoId(0), + PseudoId(1), // 0 + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::reg(PseudoId(0), 0), + Pseudo::val(PseudoId(1), 0), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(0)); + } + + #[test] + fn test_const_fold_div() { + // 10 / 2 -> 5 + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::DivS, + PseudoId(2), + PseudoId(0), + PseudoId(1), + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::val(PseudoId(0), 10), + Pseudo::val(PseudoId(1), 2), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(5)); + } + + #[test] + fn test_const_fold_mod() { + // 10 % 3 -> 1 + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::ModS, + PseudoId(2), + PseudoId(0), + PseudoId(1), + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::val(PseudoId(0), 10), + Pseudo::val(PseudoId(1), 3), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(1)); + } + + #[test] + fn test_const_fold_shift() { + // 1 << 4 -> 16 + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Shl, + PseudoId(2), + PseudoId(0), + PseudoId(1), + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::val(PseudoId(0), 1), + Pseudo::val(PseudoId(1), 4), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(16)); + } + + #[test] + fn test_const_fold_and() { + // 0xFF & 0x0F -> 0x0F + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::And, + PseudoId(2), + PseudoId(0), + PseudoId(1), + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::val(PseudoId(0), 0xFF), + Pseudo::val(PseudoId(1), 0x0F), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(0x0F)); + } + + // ======================================================================== + // Comparison Folding Tests + // ======================================================================== + + #[test] + fn test_const_fold_seteq_true() { + // 5 == 5 -> 1 + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::SetEq, + PseudoId(2), + PseudoId(0), + PseudoId(1), + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::val(PseudoId(0), 5), + Pseudo::val(PseudoId(1), 5), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(1)); + } + + #[test] + fn test_const_fold_seteq_false() { + // 5 == 3 -> 0 + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::SetEq, + PseudoId(2), + PseudoId(0), + PseudoId(1), + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::val(PseudoId(0), 5), + Pseudo::val(PseudoId(1), 3), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(0)); + } + + #[test] + fn test_const_fold_setlt() { + // 3 < 5 -> 1 + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::SetLt, + PseudoId(2), + PseudoId(0), + PseudoId(1), + types.int_id, + 32, + ); + let pseudos = vec![ + Pseudo::val(PseudoId(0), 3), + Pseudo::val(PseudoId(1), 5), + Pseudo::reg(PseudoId(2), 2), + ]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(1)); + } + + #[test] + fn test_identity_eq_self() { + // x == x -> 1 + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::SetEq, + PseudoId(1), + PseudoId(0), + PseudoId(0), + types.int_id, + 32, + ); + let pseudos = vec![Pseudo::reg(PseudoId(0), 0), Pseudo::reg(PseudoId(1), 1)]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(1)); + } + + #[test] + fn test_identity_lt_self() { + // x < x -> 0 + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::SetLt, + PseudoId(1), + PseudoId(0), + PseudoId(0), + types.int_id, + 32, + ); + let pseudos = vec![Pseudo::reg(PseudoId(0), 0), Pseudo::reg(PseudoId(1), 1)]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(0)); + } + + // ======================================================================== + // Unary Folding Tests + // ======================================================================== + + #[test] + fn test_const_fold_neg() { + // -42 -> -42 + let types = TypeTable::new(); + let insn = Instruction::unop(Opcode::Neg, PseudoId(1), PseudoId(0), types.int_id, 32); + let pseudos = vec![Pseudo::val(PseudoId(0), 42), Pseudo::reg(PseudoId(1), 1)]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(-42)); + } + + #[test] + fn test_const_fold_not() { + // ~0 -> -1 + let types = TypeTable::new(); + let insn = Instruction::unop(Opcode::Not, PseudoId(1), PseudoId(0), types.int_id, 32); + let pseudos = vec![Pseudo::val(PseudoId(0), 0), Pseudo::reg(PseudoId(1), 1)]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(-1)); } } diff --git a/cc/ir.rs b/cc/ir.rs index 8ab66aaf..ca72bf62 100644 --- a/cc/ir.rs +++ b/cc/ir.rs @@ -1012,6 +1012,28 @@ impl Function { self.locals.get(name) } + /// Compute the next available pseudo ID + /// This scans all existing pseudos to find the maximum ID, then returns max + 1 + pub fn next_pseudo_id(&self) -> PseudoId { + let max_id = self.pseudos.iter().map(|p| p.id.0).max().unwrap_or(0); + PseudoId(max_id + 1) + } + + /// Create a new constant integer pseudo and return its ID + /// The pseudo is added to self.pseudos + pub fn create_const_pseudo(&mut self, value: i64) -> PseudoId { + let id = self.next_pseudo_id(); + let pseudo = Pseudo::val(id, value); + self.add_pseudo(pseudo); + id + } + + /// Get a pseudo by its ID + #[cfg(test)] + pub fn get_pseudo(&self, id: PseudoId) -> Option<&Pseudo> { + self.pseudos.iter().find(|p| p.id == id) + } + /// Check if block a dominates block b pub fn dominates(&self, a: BasicBlockId, b: BasicBlockId) -> bool { if a == b { From 7c19c16bb32e87544eaad9eda476553512a76aac Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Sun, 7 Dec 2025 15:29:32 -0500 Subject: [PATCH 3/7] [cc] refactor linearize_expr --- cc/linearize.rs | 1671 +++++++++++++++++++++++------------------------ 1 file changed, 804 insertions(+), 867 deletions(-) diff --git a/cc/linearize.rs b/cc/linearize.rs index 3c06337b..465ff1ae 100644 --- a/cc/linearize.rs +++ b/cc/linearize.rs @@ -1684,552 +1684,867 @@ impl<'a> Linearizer<'a> { } } - fn linearize_expr(&mut self, expr: &Expr) -> PseudoId { - // Set current position for debug info - self.current_pos = Some(expr.pos); + /// 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); - match &expr.kind { - ExprKind::IntLit(val) => { - let typ = self.expr_type(expr); - self.emit_const(*val, typ) - } + // Emit conversion if needed + let src_is_float = self.types.is_float(src_type); + let dst_is_float = self.types.is_float(cast_type); - ExprKind::FloatLit(val) => { - let typ = self.expr_type(expr); - self.emit_fconst(*val, typ) + if src_is_float && !dst_is_float { + // Float to integer conversion + let result = self.alloc_pseudo(); + let pseudo = Pseudo::reg(result, result.0); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); } - - ExprKind::CharLit(c) => { - let typ = self.expr_type(expr); - self.emit_const(*c as i64, typ) + // FCvtS for signed int, FCvtU for unsigned + let opcode = if self.types.is_unsigned(cast_type) { + Opcode::FCvtU + } else { + Opcode::FCvtS + }; + let mut insn = Instruction::new(opcode) + .with_target(result) + .with_src(src) + .with_type(cast_type); + insn.src_size = self.types.size_bits(src_type); + self.emit(insn); + result + } else if !src_is_float && dst_is_float { + // Integer to float conversion + let result = self.alloc_pseudo(); + let pseudo = Pseudo::reg(result, result.0); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); } - - ExprKind::StringLit(s) => { - // Add string to module and get its label - let label = self.module.add_string(s.clone()); - - // Create a symbol pseudo for the string label - let sym_id = self.alloc_pseudo(); - let sym_pseudo = Pseudo::sym(sym_id, label.clone()); - if let Some(func) = &mut self.current_func { - func.add_pseudo(sym_pseudo); - } - - // Create result pseudo for the address + // SCvtF for signed int, UCvtF for unsigned + let opcode = if self.types.is_unsigned(src_type) { + Opcode::UCvtF + } else { + Opcode::SCvtF + }; + let mut insn = Instruction::new(opcode) + .with_target(result) + .with_src(src) + .with_type(cast_type); + insn.src_size = self.types.size_bits(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_pseudo(); - let typ = self.expr_type(expr); let pseudo = Pseudo::reg(result, result.0); if let Some(func) = &mut self.current_func { func.add_pseudo(pseudo); } + let mut insn = Instruction::new(Opcode::FCvtF) + .with_target(result) + .with_src(src) + .with_type(cast_type); + insn.src_size = src_size; + 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) + } + } - // Emit SymAddr to load the address of the string - self.emit(Instruction::sym_addr(result, sym_id, typ)); + /// Linearize a struct member access expression (e.g., s.member) + fn linearize_member(&mut self, expr: &Expr, inner_expr: &Expr, member: StringId) -> PseudoId { + // Get address of the struct base + let base = self.linearize_lvalue(inner_expr); + let struct_type = self.expr_type(inner_expr); + + // Look up member offset and 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 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 i64, 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 result = self.alloc_pseudo(); + let size = self.types.size_bits(member_info.typ); + self.emit(Instruction::load( + result, + base, + member_info.offset as i64, + member_info.typ, + size, + )); + result + } + } + + /// Linearize a pointer member access expression (e.g., p->member) + fn linearize_arrow(&mut self, expr: &Expr, inner_expr: &Expr, member: StringId) -> PseudoId { + // Pointer already contains the struct address + let ptr = self.linearize_expr(inner_expr); + let ptr_type = self.expr_type(inner_expr); + + // Dereference pointer to get struct type + let struct_type = self + .types + .base_type(ptr_type) + .unwrap_or_else(|| self.expr_type(expr)); + + // Look up member offset and 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 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 { + ptr + } else { + let result = self.alloc_pseudo(); + let offset_val = self.emit_const(member_info.offset as i64, self.types.long_id); + self.emit(Instruction::binop( + Opcode::Add, + result, + ptr, + 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( + ptr, + member_info.offset, + bit_offset, + bit_width, + storage_size, + member_info.typ, + ) + } else { + let result = self.alloc_pseudo(); + let size = self.types.size_bits(member_info.typ); + self.emit(Instruction::load( + result, + ptr, + 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 elem_size = self.types.size_bits(elem_type) / 8; + let elem_size_val = self.emit_const(elem_size as i64, 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, + )); + + // If element type is an array, just return the address (arrays decay to pointers) + if self.types.kind(elem_type) == TypeKind::Array { + addr + } else { + let result = self.alloc_pseudo(); + let size = self.types.size_bits(elem_type); + 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 { + // Get function name + let func_name = match &func_expr.kind { + ExprKind::Ident { name, .. } => self.str(*name).to_string(), + _ => "".to_string(), + }; + + let typ = self.expr_type(expr); // Use evaluated type (function return type) + + // Check if this is a variadic function call + // If the function expression has a type, check its variadic flag + let variadic_arg_start = if let Some(func_type) = func_expr.typ { + let ft = self.types.get(func_type); + if ft.variadic { + // Variadic args start after the fixed parameters + ft.params.as_ref().map(|p| p.len()) + } else { + None + } + } else { + None // No type info, assume non-variadic + }; + + // Check if function returns a large struct + // If so, allocate space and pass address as hidden first argument + let typ_kind = self.types.kind(typ); + let returns_large_struct = (typ_kind == TypeKind::Struct || typ_kind == TypeKind::Union) + && self.types.size_bits(typ) > self.target.max_aggregate_register_bits; + + 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 + func.add_local( + format!("__sret_{}", sret_sym.0), + sret_sym, + typ, + false, + self.current_bb, + ); + } + + // Get address of the allocated space + let sret_addr = self.alloc_pseudo(); + let addr_pseudo = Pseudo::reg(sret_addr, sret_addr.0); + if let Some(func) = &mut self.current_func { + func.add_pseudo(addr_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 { + let result = self.alloc_pseudo(); + (result, Vec::new(), Vec::new()) + }; + + // Linearize regular arguments + // For large structs, pass by reference (address) instead of by value + for a in args.iter() { + let 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) > self.target.max_aggregate_register_bits + { + // Large struct: pass address instead of value + // The argument type becomes a pointer + arg_types_vec.push(self.types.pointer_to(arg_type)); + self.linearize_lvalue(a) + } else { + arg_types_vec.push(arg_type); + self.linearize_expr(a) + }; + arg_vals.push(arg_val); + } + + 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_pseudo(); + let result_pseudo = Pseudo::reg(result, result.0); + if let Some(func) = &mut self.current_func { + func.add_pseudo(result_pseudo); + } + let ptr_typ = self.types.pointer_to(typ); + let mut call_insn = 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_sret_call = true; + 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 = Instruction::call( + Some(result_sym), + &func_name, + arg_vals, + arg_types_vec, + typ, + ret_size, + ); + call_insn.variadic_arg_start = variadic_arg_start; + 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 { name, .. } = &operand.kind { + self.locals.contains_key(self.str(*name)) + } else { + false + }; + + let old_val = if is_local { + // Copy the old value to a temp + let temp = self.alloc_pseudo(); + let pseudo = Pseudo::reg(temp, temp.0); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); + } + self.emit( + Instruction::new(Opcode::Copy) + .with_target(temp) + .with_src(val) + .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 i64, self.types.long_id) + } else if is_float { + self.emit_fconst(1.0, typ) + } else { + self.emit_const(1, typ) + }; + let result = self.alloc_pseudo(); + let pseudo = Pseudo::reg(result, result.0); + if let Some(func) = &mut self.current_func { + func.add_pseudo(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 { name, .. } => { let name_str = self.str(*name).to_string(); - // First check if it's an enum constant - if let Some(value) = self.symbols.get_enum_value(*name) { - self.emit_const(value, self.types.int_id) - } - // Check if it's a local variable - else if let Some(local) = self.locals.get(&name_str).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; - // Arrays decay to pointers - get address, not value - if self.types.kind(typ) == 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 { - let result = self.alloc_pseudo(); - let size = self.types.size_bits(typ); - self.emit(Instruction::load(result, sym_id, 0, typ, size)); - return result; - } - } - } - let result = self.alloc_pseudo(); - let pseudo = Pseudo::reg(result, result.0); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - // Arrays decay to pointers - get address, not value - if self.types.kind(local.typ) == 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 { - let size = self.types.size_bits(local.typ); - 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 { + if let Some(local) = self.locals.get(&name_str).cloned() { + 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); } - let typ = self.expr_type(expr); - // Arrays decay to pointers - get address, not value - if self.types.kind(typ) == 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 - } else { - let result = self.alloc_pseudo(); - let size = self.types.size_bits(typ); - self.emit(Instruction::load(result, sym_id, 0, typ, size)); - result - } + 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::Unary { op, operand } => { - // 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 { - 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; - - // 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 i64, self.types.long_id) - } else if is_float { - self.emit_fconst(1.0, typ) - } else { - self.emit_const(1, typ) - }; - let result = self.alloc_pseudo(); - let pseudo = Pseudo::reg(result, result.0); - if let Some(func) = &mut self.current_func { - func.add_pseudo(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 - }; + old_val // Return old value + } - // Store back to the variable - if let ExprKind::Ident { name, .. } = &operand.kind { - let name_str = self.str(*name).to_string(); - if let Some(local) = self.locals.get(&name_str).cloned() { - let store_size = self.types.size_bits(typ); - 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); - } - let store_size = self.types.size_bits(typ); - self.emit(Instruction::store(final_result, sym_id, 0, typ, store_size)); - } - } + /// 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); + } - return final_result; - } + 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, + )); - 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) - } + // 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; - ExprKind::Binary { op, left, right } => { - // 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); - } + // Divide by element size + let scale = self.emit_const(elem_size as i64, 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) + }; - 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 + 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; - // 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; + // Scale the integer by element size + let scale = self.emit_const(elem_size as i64, self.types.long_id); + let scaled_offset = self.alloc_pseudo(); + // Extend int_val to 64-bit for proper address arithmetic + let int_val_extended = + self.emit_convert(int_val, self.types.int_id, self.types.long_id); + self.emit(Instruction::binop( + Opcode::Mul, + scaled_offset, + int_val_extended, + scale, + self.types.long_id, + 64, + )); - // Divide by element size - let scale = self.emit_const(elem_size as i64, 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) - }; + // 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 { + // 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 + }; - // 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; + // Linearize operands + let left_val = self.linearize_expr(left); + let right_val = self.linearize_expr(right); - // Scale the integer by element size - let scale = self.emit_const(elem_size as i64, self.types.long_id); - let scaled_offset = self.alloc_pseudo(); - // Extend int_val to 64-bit for proper address arithmetic - let int_val_extended = - self.emit_convert(int_val, self.types.int_id, self.types.long_id); - self.emit(Instruction::binop( - Opcode::Mul, - scaled_offset, - int_val_extended, - scale, - self.types.long_id, - 64, - )); + // 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); - // 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 { - // 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 - }; + self.emit_binary(op, left_val, right_val, result_typ, operand_typ) + } + } - // Linearize operands - let left_val = self.linearize_expr(left); - let right_val = self.linearize_expr(right); + /// 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); + } - // 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); + // Handle PreInc/PreDec specially - they need store-back + if op == UnaryOp::PreInc || op == UnaryOp::PreDec { + 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; - self.emit_binary(*op, left_val, right_val, result_typ, operand_typ) - } + // 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 i64, self.types.long_id) + } else if is_float { + self.emit_fconst(1.0, typ) + } else { + self.emit_const(1, typ) + }; + let result = self.alloc_pseudo(); + let pseudo = Pseudo::reg(result, result.0); + if let Some(func) = &mut self.current_func { + func.add_pseudo(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, + )); - ExprKind::Assign { op, target, value } => self.emit_assign(*op, target, value), - - ExprKind::PostInc(operand) => { - 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 _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 + }; - // 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 { name, .. } = &operand.kind { - self.locals.contains_key(self.str(*name)) + // Store back to the variable + if let ExprKind::Ident { name, .. } = &operand.kind { + let name_str = self.str(*name).to_string(); + if let Some(local) = self.locals.get(&name_str).cloned() { + let store_size = self.types.size_bits(typ); + 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 { - false - }; - - let old_val = if is_local { - // Copy the old value to a temp - let temp = self.alloc_pseudo(); - let pseudo = Pseudo::reg(temp, temp.0); + // 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::new(Opcode::Copy) - .with_target(temp) - .with_src(val) - .with_size(self.types.size_bits(typ)), - ); - temp - } else { - val - }; - - // For pointers, increment by element size; for others, increment by 1 - 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 i64, self.types.long_id) - } else if is_float { - self.emit_fconst(1.0, typ) - } else { - self.emit_const(1, typ) - }; - let result = self.alloc_pseudo(); - let pseudo = Pseudo::reg(result, result.0); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); + let store_size = self.types.size_bits(typ); + self.emit(Instruction::store(final_result, sym_id, 0, typ, store_size)); } - let opcode = if is_float { Opcode::FAdd } else { Opcode::Add }; - 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, increment, 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 - }; + return final_result; + } - // Store to local, update parameter mapping, or store through pointer - let store_size = self.types.size_bits(typ); - match &operand.kind { - ExprKind::Ident { name, .. } => { - let name_str = self.str(*name).to_string(); - if let Some(local) = self.locals.get(&name_str).cloned() { - 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)); - } + 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) + fn linearize_ident(&mut self, expr: &Expr, name: StringId) -> PseudoId { + let name_str = self.str(name).to_string(); + // First check if it's an enum constant + if let Some(value) = self.symbols.get_enum_value(name) { + self.emit_const(value, self.types.int_id) + } + // Check if it's a local variable + else if let Some(local) = self.locals.get(&name_str).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); } - ExprKind::Unary { - op: UnaryOp::Deref, - operand: ptr_expr, - } => { - // (*p)++ - store back through the pointer - let addr = self.linearize_expr(ptr_expr); - self.emit(Instruction::store(final_result, addr, 0, typ, store_size)); + let typ = static_info.typ; + // Arrays decay to pointers - get address, not value + if self.types.kind(typ) == 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 { + let result = self.alloc_pseudo(); + let size = self.types.size_bits(typ); + self.emit(Instruction::load(result, sym_id, 0, typ, size)); + return result; } - _ => {} } + } + let result = self.alloc_pseudo(); + let pseudo = Pseudo::reg(result, result.0); + if let Some(func) = &mut self.current_func { + func.add_pseudo(pseudo); + } + // Arrays decay to pointers - get address, not value + if self.types.kind(local.typ) == 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 { + let size = self.types.size_bits(local.typ); + 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 { + 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); + // Arrays decay to pointers - get address, not value + if self.types.kind(typ) == 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 + } else { + let result = self.alloc_pseudo(); + let size = self.types.size_bits(typ); + self.emit(Instruction::load(result, sym_id, 0, typ, size)); + result + } + } + } - old_val // Return old value + 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, 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 i64, typ) } - ExprKind::PostDec(operand) => { - 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 { name, .. } = &operand.kind { - self.locals.contains_key(self.str(*name)) - } else { - false - }; + ExprKind::StringLit(s) => { + // Add string to module and get its label + let label = self.module.add_string(s.clone()); - let old_val = if is_local { - // Copy the old value to a temp - let temp = self.alloc_pseudo(); - let pseudo = Pseudo::reg(temp, temp.0); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - self.emit( - Instruction::new(Opcode::Copy) - .with_target(temp) - .with_src(val) - .with_size(self.types.size_bits(typ)), - ); - temp - } else { - val - }; + // Create a symbol pseudo for the string label + let sym_id = self.alloc_pseudo(); + let sym_pseudo = Pseudo::sym(sym_id, label.clone()); + if let Some(func) = &mut self.current_func { + func.add_pseudo(sym_pseudo); + } - // For pointers, decrement by element size; for others, decrement by 1 - 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 i64, self.types.long_id) - } else if is_float { - self.emit_fconst(1.0, typ) - } else { - self.emit_const(1, typ) - }; + // Create result pseudo for the address let result = self.alloc_pseudo(); + let typ = self.expr_type(expr); let pseudo = Pseudo::reg(result, result.0); if let Some(func) = &mut self.current_func { func.add_pseudo(pseudo); } - let opcode = if is_float { Opcode::FSub } 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, decrement, 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 - }; + // Emit SymAddr to load the address of the string + self.emit(Instruction::sym_addr(result, sym_id, typ)); + result + } - // Store to local, update parameter mapping, or store through pointer - let store_size = self.types.size_bits(typ); - match &operand.kind { - ExprKind::Ident { name, .. } => { - let name_str = self.str(*name).to_string(); - if let Some(local) = self.locals.get(&name_str).cloned() { - 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)-- - store back through the pointer - let addr = self.linearize_expr(ptr_expr); - self.emit(Instruction::store(final_result, addr, 0, typ, store_size)); - } - _ => {} - } + ExprKind::Ident { name, .. } => self.linearize_ident(expr, *name), - old_val // Return old value - } + 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, @@ -2250,402 +2565,24 @@ impl<'a> Linearizer<'a> { result } - ExprKind::Call { func, args } => { - // Get function name - let func_name = match &func.kind { - ExprKind::Ident { name, .. } => self.str(*name).to_string(), - _ => "".to_string(), - }; - - let typ = self.expr_type(expr); // Use evaluated type (function return type) - - // Check if this is a variadic function call - // If the function expression has a type, check its variadic flag - let variadic_arg_start = if let Some(func_type) = func.typ { - let ft = self.types.get(func_type); - if ft.variadic { - // Variadic args start after the fixed parameters - ft.params.as_ref().map(|p| p.len()) - } else { - None - } - } else { - None // No type info, assume non-variadic - }; - - // Check if function returns a large struct - // If so, allocate space and pass address as hidden first argument - let typ_kind = self.types.kind(typ); - let returns_large_struct = (typ_kind == TypeKind::Struct - || typ_kind == TypeKind::Union) - && self.types.size_bits(typ) > self.target.max_aggregate_register_bits; - - 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 - func.add_local( - format!("__sret_{}", sret_sym.0), - sret_sym, - typ, - false, - self.current_bb, - ); - } - - // Get address of the allocated space - let sret_addr = self.alloc_pseudo(); - let addr_pseudo = Pseudo::reg(sret_addr, sret_addr.0); - if let Some(func) = &mut self.current_func { - func.add_pseudo(addr_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 { - let result = self.alloc_pseudo(); - (result, Vec::new(), Vec::new()) - }; - - // Linearize regular arguments - // For large structs, pass by reference (address) instead of by value - for a in args.iter() { - let 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) > self.target.max_aggregate_register_bits - { - // Large struct: pass address instead of value - // The argument type becomes a pointer - arg_types_vec.push(self.types.pointer_to(arg_type)); - self.linearize_lvalue(a) - } else { - arg_types_vec.push(arg_type); - self.linearize_expr(a) - }; - arg_vals.push(arg_val); - } - - 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_pseudo(); - let result_pseudo = Pseudo::reg(result, result.0); - if let Some(func) = &mut self.current_func { - func.add_pseudo(result_pseudo); - } - let ptr_typ = self.types.pointer_to(typ); - let mut call_insn = 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_sret_call = true; - 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 = Instruction::call( - Some(result_sym), - &func_name, - arg_vals, - arg_types_vec, - typ, - ret_size, - ); - call_insn.variadic_arg_start = variadic_arg_start; - self.emit(call_insn); - result_sym - } - } + ExprKind::Call { func, args } => self.linearize_call(expr, func, args), ExprKind::Member { expr: inner_expr, member, - } => { - // Get address of the struct base - let base = self.linearize_lvalue(inner_expr); - let struct_type = self.expr_type(inner_expr); - - // Look up member offset and 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 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 i64, 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 result = self.alloc_pseudo(); - let size = self.types.size_bits(member_info.typ); - self.emit(Instruction::load( - result, - base, - member_info.offset as i64, - member_info.typ, - size, - )); - result - } - } + } => self.linearize_member(expr, inner_expr, *member), ExprKind::Arrow { expr: inner_expr, member, - } => { - // Pointer already contains the struct address - let ptr = self.linearize_expr(inner_expr); - let ptr_type = self.expr_type(inner_expr); - - // Dereference pointer to get struct type - let struct_type = self - .types - .base_type(ptr_type) - .unwrap_or_else(|| self.expr_type(expr)); - - // Look up member offset and 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 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 { - ptr - } else { - let result = self.alloc_pseudo(); - let offset_val = - self.emit_const(member_info.offset as i64, self.types.long_id); - self.emit(Instruction::binop( - Opcode::Add, - result, - ptr, - 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( - ptr, - member_info.offset, - bit_offset, - bit_width, - storage_size, - member_info.typ, - ) - } else { - let result = self.alloc_pseudo(); - let size = self.types.size_bits(member_info.typ); - self.emit(Instruction::load( - result, - ptr, - member_info.offset as i64, - member_info.typ, - size, - )); - result - } - } - - ExprKind::Index { array, index } => { - // 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 elem_size = self.types.size_bits(elem_type) / 8; - let elem_size_val = self.emit_const(elem_size as i64, 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.linearize_arrow(expr, inner_expr, *member), - // If element type is an array, just return the address (arrays decay to pointers) - if self.types.kind(elem_type) == TypeKind::Array { - addr - } else { - let result = self.alloc_pseudo(); - let size = self.types.size_bits(elem_type); - self.emit(Instruction::load(result, addr, 0, elem_type, size)); - result - } - } + ExprKind::Index { array, index } => self.linearize_index(expr, array, index), ExprKind::Cast { cast_type, expr: inner_expr, - } => { - let src = self.linearize_expr(inner_expr); - let src_type = self.expr_type(inner_expr); - let cast_type = *cast_type; // Copy the TypeId - - // 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_pseudo(); - let pseudo = Pseudo::reg(result, result.0); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - // FCvtS for signed int, FCvtU for unsigned - let opcode = if self.types.is_unsigned(cast_type) { - Opcode::FCvtU - } else { - Opcode::FCvtS - }; - let mut insn = Instruction::new(opcode) - .with_target(result) - .with_src(src) - .with_type(cast_type); - insn.src_size = self.types.size_bits(src_type); - self.emit(insn); - result - } else if !src_is_float && dst_is_float { - // Integer to float conversion - let result = self.alloc_pseudo(); - let pseudo = Pseudo::reg(result, result.0); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - // SCvtF for signed int, UCvtF for unsigned - let opcode = if self.types.is_unsigned(src_type) { - Opcode::UCvtF - } else { - Opcode::SCvtF - }; - let mut insn = Instruction::new(opcode) - .with_target(result) - .with_src(src) - .with_type(cast_type); - insn.src_size = self.types.size_bits(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_pseudo(); - let pseudo = Pseudo::reg(result, result.0); - if let Some(func) = &mut self.current_func { - func.add_pseudo(pseudo); - } - let mut insn = Instruction::new(Opcode::FCvtF) - .with_target(result) - .with_src(src) - .with_type(cast_type); - insn.src_size = src_size; - 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) - } - } + } => self.linearize_cast(inner_expr, *cast_type), ExprKind::SizeofType(typ) => { let size = self.types.size_bits(*typ) / 8; From 5917ea6d1aeb8ec4cc57970ea4d10784254bd673 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Sun, 7 Dec 2025 16:31:32 -0500 Subject: [PATCH 4/7] [cc] target: remove invalid dead-code markers --- cc/target.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cc/target.rs b/cc/target.rs index ae29cc48..5a14cd35 100644 --- a/cc/target.rs +++ b/cc/target.rs @@ -16,7 +16,6 @@ use std::fmt; /// Target CPU architecture #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[allow(dead_code)] // Variants used via cfg-based detection and tests pub enum Arch { X86_64, Aarch64, @@ -33,7 +32,6 @@ impl fmt::Display for Arch { /// Target operating system #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[allow(dead_code)] // Variants used via cfg-based detection and tests pub enum Os { Linux, MacOS, From c1c6a8b5e4972de3d28409210814a0acf2ef969a Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Sun, 7 Dec 2025 16:47:01 -0500 Subject: [PATCH 5/7] [cc] remove dead arch code --- cc/arch/aarch64/lir.rs | 131 +++-------------------------------------- cc/arch/x86_64/lir.rs | 75 ++--------------------- 2 files changed, 11 insertions(+), 195 deletions(-) diff --git a/cc/arch/aarch64/lir.rs b/cc/arch/aarch64/lir.rs index b0578099..a696522f 100644 --- a/cc/arch/aarch64/lir.rs +++ b/cc/arch/aarch64/lir.rs @@ -13,9 +13,6 @@ // enabling peephole optimizations before final assembly emission. // -// Allow unused variants/methods - LIR defines complete instruction set for future use -#![allow(dead_code)] - use super::codegen::{Reg, VReg}; use crate::arch::lir::{Directive, EmitAsm, FpSize, Label, OperandSize, Symbol}; use crate::target::{Os, Target}; @@ -26,6 +23,7 @@ use std::fmt::{self, Write}; // ============================================================================ /// AArch64 memory addressing mode +#[allow(dead_code)] // Documents full instruction set #[derive(Debug, Clone, PartialEq)] pub enum MemAddr { /// [base] - Register indirect @@ -96,19 +94,6 @@ impl GpOperand { GpOperand::Imm(v) => format!("#{}", v), } } - - /// Check if this is a register operand - pub fn is_reg(&self) -> bool { - matches!(self, GpOperand::Reg(_)) - } - - /// Get register if this is a register operand - pub fn as_reg(&self) -> Option { - match self { - GpOperand::Reg(r) => Some(*r), - _ => None, - } - } } // ============================================================================ @@ -116,29 +101,19 @@ impl GpOperand { // ============================================================================ /// AArch64 FP/SIMD operand (register) +#[allow(dead_code)] // Documents full instruction set #[derive(Debug, Clone, PartialEq)] pub enum FpOperand { /// FP register operand Reg(VReg), } -impl FpOperand { - /// Format operand in AArch64 syntax - pub fn format(&self, size: FpSize) -> String { - match self { - FpOperand::Reg(r) => match size { - FpSize::Single => r.name_s().to_string(), - FpSize::Double => r.name_d().to_string(), - }, - } - } -} - // ============================================================================ // Condition Codes // ============================================================================ /// AArch64 condition codes for comparisons and conditional operations +#[allow(dead_code)] // Documents full instruction set #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Cond { /// Equal (Z=1) @@ -197,28 +172,6 @@ impl Cond { Cond::Nv => "nv", } } - - /// Get the inverse condition - pub fn inverse(&self) -> Cond { - match self { - Cond::Eq => Cond::Ne, - Cond::Ne => Cond::Eq, - Cond::Cs => Cond::Cc, - Cond::Cc => Cond::Cs, - Cond::Mi => Cond::Pl, - Cond::Pl => Cond::Mi, - Cond::Vs => Cond::Vc, - Cond::Vc => Cond::Vs, - Cond::Hi => Cond::Ls, - Cond::Ls => Cond::Hi, - Cond::Ge => Cond::Lt, - Cond::Lt => Cond::Ge, - Cond::Gt => Cond::Le, - Cond::Le => Cond::Gt, - Cond::Al => Cond::Nv, - Cond::Nv => Cond::Al, - } - } } impl fmt::Display for Cond { @@ -232,6 +185,7 @@ impl fmt::Display for Cond { // ============================================================================ /// Shift type for shifted register operands +#[allow(dead_code)] // Documents full instruction set #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ShiftType { /// Logical shift left @@ -244,22 +198,12 @@ pub enum ShiftType { Ror, } -impl ShiftType { - pub fn as_str(&self) -> &'static str { - match self { - ShiftType::Lsl => "lsl", - ShiftType::Lsr => "lsr", - ShiftType::Asr => "asr", - ShiftType::Ror => "ror", - } - } -} - // ============================================================================ // Extend Type // ============================================================================ /// Extend type for extended register operands +#[allow(dead_code)] // Documents full instruction set #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ExtendType { /// Unsigned extend byte @@ -280,26 +224,12 @@ pub enum ExtendType { Sxtx, } -impl ExtendType { - pub fn as_str(&self) -> &'static str { - match self { - ExtendType::Uxtb => "uxtb", - ExtendType::Uxth => "uxth", - ExtendType::Uxtw => "uxtw", - ExtendType::Uxtx => "uxtx", - ExtendType::Sxtb => "sxtb", - ExtendType::Sxth => "sxth", - ExtendType::Sxtw => "sxtw", - ExtendType::Sxtx => "sxtx", - } - } -} - // ============================================================================ // Call Target // ============================================================================ /// Target for call instructions +#[allow(dead_code)] // Documents full instruction set #[derive(Debug, Clone, PartialEq)] pub enum CallTarget { /// Direct call to symbol @@ -313,6 +243,7 @@ pub enum CallTarget { // ============================================================================ /// AArch64 Low-level IR instruction +#[allow(dead_code)] // Documents full instruction set #[derive(Debug, Clone)] pub enum Aarch64Inst { // ======================================================================== @@ -1553,19 +1484,6 @@ fn size_bits(size: FpSize) -> u32 { } } -// ============================================================================ -// Utility Functions -// ============================================================================ - -/// Emit a sequence of LIR instructions to assembly text -pub fn emit_instrs(instrs: &[Aarch64Inst], target: &Target) -> String { - let mut out = String::new(); - for inst in instrs { - inst.emit(target, &mut out); - } - out -} - // ============================================================================ // Tests // ============================================================================ @@ -1768,39 +1686,4 @@ mod tests { inst.emit(&macos, &mut out); assert!(out.contains("bl _printf")); } - - #[test] - fn test_condition_inverse() { - assert_eq!(Cond::Eq.inverse(), Cond::Ne); - assert_eq!(Cond::Lt.inverse(), Cond::Ge); - assert_eq!(Cond::Hi.inverse(), Cond::Ls); - } - - #[test] - fn test_emit_instrs() { - let target = linux_target(); - - let instrs = vec![ - Aarch64Inst::Stp { - size: OperandSize::B64, - src1: Reg::X29, - src2: Reg::X30, - addr: MemAddr::PreIndex { - base: Reg::X29, - offset: -16, - }, - }, - Aarch64Inst::Mov { - size: OperandSize::B64, - src: GpOperand::Imm(42), - dst: Reg::X0, - }, - Aarch64Inst::Ret, - ]; - - let out = emit_instrs(&instrs, &target); - assert!(out.contains("stp x29, x30")); - assert!(out.contains("mov x0, #42")); - assert!(out.contains("ret")); - } } diff --git a/cc/arch/x86_64/lir.rs b/cc/arch/x86_64/lir.rs index 4d6413d7..98afd458 100644 --- a/cc/arch/x86_64/lir.rs +++ b/cc/arch/x86_64/lir.rs @@ -13,9 +13,6 @@ // enabling peephole optimizations before final assembly emission. // -// Allow dead code - LIR infrastructure for future phases -#![allow(dead_code)] - use super::codegen::{Reg, XmmReg}; use crate::arch::lir::{Directive, EmitAsm, FpSize, Label, OperandSize, Symbol}; use crate::target::{Os, Target}; @@ -26,6 +23,7 @@ use std::fmt::{self, Write}; // ============================================================================ /// x86-64 memory addressing mode +#[allow(dead_code)] // Documents full instruction set #[derive(Debug, Clone, PartialEq)] pub enum MemAddr { /// [base + offset] - Register indirect with displacement @@ -103,19 +101,6 @@ impl GpOperand { GpOperand::Imm(v) => format!("${}", v), } } - - /// Check if this is a register operand - pub fn is_reg(&self) -> bool { - matches!(self, GpOperand::Reg(_)) - } - - /// Get register if this is a register operand - pub fn as_reg(&self) -> Option { - match self { - GpOperand::Reg(r) => Some(*r), - _ => None, - } - } } // ============================================================================ @@ -146,6 +131,7 @@ impl XmmOperand { // ============================================================================ /// Integer condition codes for comparisons and conditional jumps/sets +#[allow(dead_code)] // Documents full instruction set #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum IntCC { /// Equal (ZF=1) @@ -198,26 +184,6 @@ impl IntCC { IntCC::Np => "np", } } - - /// Get the inverse condition - pub fn inverse(&self) -> IntCC { - match self { - IntCC::E => IntCC::Ne, - IntCC::Ne => IntCC::E, - IntCC::L => IntCC::Ge, - IntCC::Le => IntCC::G, - IntCC::G => IntCC::Le, - IntCC::Ge => IntCC::L, - IntCC::B => IntCC::Ae, - IntCC::Be => IntCC::A, - IntCC::A => IntCC::Be, - IntCC::Ae => IntCC::B, - IntCC::S => IntCC::Ns, - IntCC::Ns => IntCC::S, - IntCC::P => IntCC::Np, - IntCC::Np => IntCC::P, - } - } } impl fmt::Display for IntCC { @@ -244,6 +210,7 @@ pub enum ShiftCount { // ============================================================================ /// Target for call instructions +#[allow(dead_code)] // Documents full instruction set #[derive(Debug, Clone, PartialEq)] pub enum CallTarget { /// Direct call to symbol @@ -259,6 +226,7 @@ pub enum CallTarget { // ============================================================================ /// x86-64 Low-level IR instruction +#[allow(dead_code)] // Documents full instruction set #[derive(Debug, Clone)] pub enum X86Inst { // ======================================================================== @@ -1098,19 +1066,6 @@ impl EmitAsm for X86Inst { } } -// ============================================================================ -// Utility Functions -// ============================================================================ - -/// Emit a sequence of LIR instructions to assembly text -pub fn emit_instrs(instrs: &[X86Inst], target: &Target) -> String { - let mut out = String::new(); - for inst in instrs { - inst.emit(target, &mut out); - } - out -} - // ============================================================================ // Tests // ============================================================================ @@ -1255,26 +1210,4 @@ mod tests { inst.emit(&target, &mut out); assert!(out.contains("addss")); } - - #[test] - fn test_emit_instrs() { - let target = linux_target(); - - let instrs = vec![ - X86Inst::Push { - src: GpOperand::Reg(Reg::Rbp), - }, - X86Inst::Mov { - size: OperandSize::B64, - src: GpOperand::Reg(Reg::Rsp), - dst: GpOperand::Reg(Reg::Rbp), - }, - X86Inst::Ret, - ]; - - let out = emit_instrs(&instrs, &target); - assert!(out.contains("pushq %rbp")); - assert!(out.contains("movq %rsp, %rbp")); - assert!(out.contains("ret")); - } } From ba1f18067642d0c9fa13d6c62241a035bdfc3a4a Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Sun, 7 Dec 2025 17:47:13 -0500 Subject: [PATCH 6/7] [cc] InstCombine improvements, based on review --- cc/instcombine.rs | 34 ++++++++++++++++++++++++++++--- cc/tests/features/optimization.rs | 4 ---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/cc/instcombine.rs b/cc/instcombine.rs index 3ea987bb..0250fcea 100644 --- a/cc/instcombine.rs +++ b/cc/instcombine.rs @@ -190,6 +190,12 @@ fn simplify_sub(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { let src1 = insn.src[0]; let src2 = insn.src[1]; + + // Identity: x - x -> 0 + if src1 == src2 { + return Simplification::FoldToConst(0); + } + let val1 = get_const(pseudos, src1); let val2 = get_const(pseudos, src2); @@ -202,9 +208,6 @@ fn simplify_sub(insn: &Instruction, pseudos: &[Pseudo]) -> Simplification { _ => Simplification::None, } - - // Identity: x - x -> 0 is handled by constant folding if both are constants, - // but for same-pseudo case we need a separate check } // ============================================================================ @@ -1048,6 +1051,31 @@ mod tests { assert_eq!(new_const.kind, PseudoKind::Val(7)); } + #[test] + fn test_sub_self() { + // x - x -> 0 + let types = TypeTable::new(); + let insn = Instruction::binop( + Opcode::Sub, + PseudoId(1), + PseudoId(0), + PseudoId(0), + types.int_id, + 32, + ); + let pseudos = vec![Pseudo::reg(PseudoId(0), 0), Pseudo::reg(PseudoId(1), 1)]; + let mut func = make_test_func_with_insn(insn, pseudos); + + let changed = run(&mut func); + assert!(changed); + + let result_insn = &func.blocks[0].insns[1]; + assert_eq!(result_insn.op, Opcode::Copy); + + let new_const = func.get_pseudo(result_insn.src[0]).unwrap(); + assert_eq!(new_const.kind, PseudoKind::Val(0)); + } + #[test] fn test_const_fold_mul() { // 6 * 7 -> 42 diff --git a/cc/tests/features/optimization.rs b/cc/tests/features/optimization.rs index 60671a3c..47d32e09 100644 --- a/cc/tests/features/optimization.rs +++ b/cc/tests/features/optimization.rs @@ -11,10 +11,6 @@ // These tests verify that optimizations don't break program correctness. // Tests are aggregated to reduce compile/link cycles. // -// Note: MVP InstCombine only does algebraic simplifications (x + 0 -> x, etc.) -// and does NOT do constant folding (2 + 3 -> 5) because that would require -// creating new constant pseudos. -// use crate::common::compile_and_run_optimized; From a1ae17b7b7968d144c836c0946fbae23894b1505 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Sun, 7 Dec 2025 18:34:40 -0500 Subject: [PATCH 7/7] [cc] cleanup: move files to cc/ir/ --- cc/{ => ir}/dce.rs | 2 +- cc/{ => ir}/dominate.rs | 2 +- cc/{ => ir}/instcombine.rs | 2 +- cc/{ => ir}/linearize.rs | 6 +++--- cc/{ => ir}/lower.rs | 2 +- cc/{ir.rs => ir/mod.rs} | 7 +++++++ cc/{ => ir}/ssa.rs | 6 ++---- cc/main.rs | 10 ++-------- cc/opt.rs | 4 ++-- 9 files changed, 20 insertions(+), 21 deletions(-) rename cc/{ => ir}/dce.rs (99%) rename cc/{ => ir}/dominate.rs (99%) rename cc/{ => ir}/instcombine.rs (99%) rename cc/{ => ir}/linearize.rs (99%) rename cc/{ => ir}/lower.rs (99%) rename cc/{ir.rs => ir/mod.rs} (99%) rename cc/{ => ir}/ssa.rs (99%) diff --git a/cc/dce.rs b/cc/ir/dce.rs similarity index 99% rename from cc/dce.rs rename to cc/ir/dce.rs index 6d2b504e..521e42a7 100644 --- a/cc/dce.rs +++ b/cc/ir/dce.rs @@ -15,7 +15,7 @@ // 3. Delete all unmarked instructions // -use crate::ir::{BasicBlockId, Function, Instruction, Opcode, PseudoId}; +use super::{BasicBlockId, Function, Instruction, Opcode, PseudoId}; use std::collections::{HashSet, VecDeque}; // ============================================================================ diff --git a/cc/dominate.rs b/cc/ir/dominate.rs similarity index 99% rename from cc/dominate.rs rename to cc/ir/dominate.rs index 93d5e3c8..63c2f554 100644 --- a/cc/dominate.rs +++ b/cc/ir/dominate.rs @@ -13,7 +13,7 @@ // - IDF computation: "A Linear Time Algorithm for Placing phi-nodes" by Sreedhar and Gao // -use crate::ir::{BasicBlockId, Function}; +use super::{BasicBlockId, Function}; use std::collections::{HashMap, HashSet}; // ============================================================================ diff --git a/cc/instcombine.rs b/cc/ir/instcombine.rs similarity index 99% rename from cc/instcombine.rs rename to cc/ir/instcombine.rs index 0250fcea..9c96d809 100644 --- a/cc/instcombine.rs +++ b/cc/ir/instcombine.rs @@ -14,7 +14,7 @@ // - Identity patterns: x - x -> 0, x ^ x -> 0, etc. // -use crate::ir::{Function, Instruction, Opcode, Pseudo, PseudoId, PseudoKind}; +use super::{Function, Instruction, Opcode, Pseudo, PseudoId, PseudoKind}; use crate::types::TypeId; // ============================================================================ diff --git a/cc/linearize.rs b/cc/ir/linearize.rs similarity index 99% rename from cc/linearize.rs rename to cc/ir/linearize.rs index 465ff1ae..b9fae620 100644 --- a/cc/linearize.rs +++ b/cc/ir/linearize.rs @@ -10,15 +10,15 @@ // Converts AST to SSA-style IR following sparse's design // -use crate::diag::Position; -use crate::ir::{ +use super::ssa::ssa_convert; +use super::{ BasicBlock, BasicBlockId, Function, Initializer, Instruction, Module, Opcode, Pseudo, PseudoId, }; +use crate::diag::Position; use crate::parse::ast::{ AssignOp, BinaryOp, BlockItem, Declaration, Designator, Expr, ExprKind, ExternalDecl, ForInit, FunctionDef, InitElement, Stmt, TranslationUnit, UnaryOp, }; -use crate::ssa::ssa_convert; use crate::strings::{StringId, StringTable}; use crate::symbol::SymbolTable; use crate::target::Target; diff --git a/cc/lower.rs b/cc/ir/lower.rs similarity index 99% rename from cc/lower.rs rename to cc/ir/lower.rs index bb1fd0e7..b1b206ae 100644 --- a/cc/lower.rs +++ b/cc/ir/lower.rs @@ -15,7 +15,7 @@ // - Phi elimination: Converts SSA phi nodes to copy instructions // -use crate::ir::{BasicBlockId, Function, Instruction, Module, Opcode}; +use super::{BasicBlockId, Function, Instruction, Module, Opcode}; use std::collections::HashMap; // ============================================================================ diff --git a/cc/ir.rs b/cc/ir/mod.rs similarity index 99% rename from cc/ir.rs rename to cc/ir/mod.rs index ca72bf62..34c05d54 100644 --- a/cc/ir.rs +++ b/cc/ir/mod.rs @@ -14,6 +14,13 @@ // optimization passes. // +pub mod dce; +pub mod dominate; +pub mod instcombine; +pub mod linearize; +pub mod lower; +pub mod ssa; + use crate::diag::Position; use crate::types::TypeId; use std::collections::HashMap; diff --git a/cc/ssa.rs b/cc/ir/ssa.rs similarity index 99% rename from cc/ssa.rs rename to cc/ir/ssa.rs index 870813cd..2511a75d 100644 --- a/cc/ssa.rs +++ b/cc/ir/ssa.rs @@ -14,10 +14,8 @@ // - Renames variables to complete SSA construction // -use crate::dominate::{compute_dominance_frontiers, domtree_build, idf_compute}; -use crate::ir::{ - BasicBlockId, Function, InsnRef, Instruction, Opcode, Pseudo, PseudoId, PseudoKind, -}; +use super::dominate::{compute_dominance_frontiers, domtree_build, idf_compute}; +use super::{BasicBlockId, Function, InsnRef, Instruction, Opcode, Pseudo, PseudoId, PseudoKind}; use crate::types::{TypeId, TypeTable}; use std::collections::{HashMap, HashSet}; diff --git a/cc/main.rs b/cc/main.rs index ff34660d..33847097 100644 --- a/cc/main.rs +++ b/cc/main.rs @@ -10,17 +10,11 @@ // mod arch; -mod dce; mod diag; -mod dominate; -mod instcombine; mod ir; -mod linearize; -mod lower; mod opt; mod os; mod parse; -mod ssa; mod strings; mod symbol; mod target; @@ -218,7 +212,7 @@ fn process_file( } // Linearize to IR - let mut module = linearize::linearize_with_debug( + let mut module = ir::linearize::linearize_with_debug( &ast, &symbols, &types, @@ -239,7 +233,7 @@ fn process_file( } // Lower IR (phi elimination, etc.) - lower::lower_module(&mut module); + ir::lower::lower_module(&mut module); // Generate assembly let emit_unwind_tables = !args.no_unwind_tables; diff --git a/cc/opt.rs b/cc/opt.rs index 35840aa6..7685c930 100644 --- a/cc/opt.rs +++ b/cc/opt.rs @@ -12,8 +12,8 @@ // used by optimization passes (InstCombine, DCE, etc.). // -use crate::dce; -use crate::instcombine; +use crate::ir::dce; +use crate::ir::instcombine; use crate::ir::{Function, Module}; #[cfg(test)]