feat(ir/backend): add support for floating-point types and arithmetic
- Introduces F32 and F64 types and Float operand variant to the IR. - Implements floating-point binary operations (FAdd, FSub, FMul, FDiv, FRem, FCmp) and FNeg unary op. - Updates IR printer, validator, and builder to handle the new floating-point functionality. - Extends the constant folding pass to evaluate floating-point expressions at compile time. - Enhances x86_64 backend with XMM register support and floating-point codegen. - Implements a fixed-point iteration pass for register type resolution to correctly allocate GPR vs XMM registers. - Updates the linear scan allocator to manage multiple register classes (GPR, XMM). - Adds System V ABI compliant handling for floating-point function arguments and return values. - Includes comprehensive tests for IR validation, constant folding, and assembly generation.
This commit is contained in:
@@ -112,6 +112,57 @@ impl Codegen {
|
|||||||
self.instructions.push(Instruction::Setcc(cc, dest));
|
self.instructions.push(Instruction::Setcc(cc, dest));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emits a floating-point `mov` instruction (`movss` or `movsd`).
|
||||||
|
pub fn emit_fmov(&mut self, width: OperandWidth, src: Operand, dest: Operand) {
|
||||||
|
if matches!(dest, Operand::Imm(_)) {
|
||||||
|
panic!("x86-64 error: Cannot use an immediate as a floating-point destination.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(src, Operand::Mem { .. }) && matches!(dest, Operand::Mem { .. }) {
|
||||||
|
panic!("x86-64 error: Floating-point memory-to-memory moves are not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
match width {
|
||||||
|
OperandWidth::DWord => self.instructions.push(Instruction::Movss(src, dest)),
|
||||||
|
OperandWidth::QWord => self.instructions.push(Instruction::Movsd(src, dest)),
|
||||||
|
_ => panic!(
|
||||||
|
"x86-64 error: Invalid width for floating-point move: {:?}",
|
||||||
|
width
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits a floating-point arithmetic instruction.
|
||||||
|
pub fn emit_fbinary(
|
||||||
|
&mut self,
|
||||||
|
op_f32: fn(Operand, Operand) -> Instruction,
|
||||||
|
op_f64: fn(Operand, Operand) -> Instruction,
|
||||||
|
width: OperandWidth,
|
||||||
|
src: Operand,
|
||||||
|
dest: Operand,
|
||||||
|
) {
|
||||||
|
if matches!(dest, Operand::Imm(_)) {
|
||||||
|
panic!(
|
||||||
|
"x86-64 error: Destination of a floating-point operation cannot be an immediate."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(src, Operand::Mem { .. }) && matches!(dest, Operand::Mem { .. }) {
|
||||||
|
panic!(
|
||||||
|
"x86-64 error: Floating-point binary operations cannot act on two memory addresses."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
match width {
|
||||||
|
OperandWidth::DWord => self.instructions.push(op_f32(src, dest)),
|
||||||
|
OperandWidth::QWord => self.instructions.push(op_f64(src, dest)),
|
||||||
|
_ => panic!(
|
||||||
|
"x86-64 error: Invalid width for floating-point binary: {:?}",
|
||||||
|
width
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper to grab the finalized list of instructions
|
/// Helper to grab the finalized list of instructions
|
||||||
pub fn finish(self) -> Vec<Instruction> {
|
pub fn finish(self) -> Vec<Instruction> {
|
||||||
self.instructions
|
self.instructions
|
||||||
|
|||||||
+387
-54
@@ -12,6 +12,7 @@ use crate::ir;
|
|||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
enum Storage {
|
enum Storage {
|
||||||
Hardware(Gpr),
|
Hardware(Gpr),
|
||||||
|
Xmm(Xmm),
|
||||||
Stack(i32),
|
Stack(i32),
|
||||||
Alloc(i32),
|
Alloc(i32),
|
||||||
}
|
}
|
||||||
@@ -21,6 +22,7 @@ pub struct X86Backend<'a> {
|
|||||||
assembly: String,
|
assembly: String,
|
||||||
live_intervals: HashMap<ir::Register, (usize, usize)>,
|
live_intervals: HashMap<ir::Register, (usize, usize)>,
|
||||||
allocations: HashMap<ir::Register, Storage>,
|
allocations: HashMap<ir::Register, Storage>,
|
||||||
|
reg_types: HashMap<ir::Register, ir::Type>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> X86Backend<'a> {
|
impl<'a> X86Backend<'a> {
|
||||||
@@ -30,6 +32,7 @@ impl<'a> X86Backend<'a> {
|
|||||||
module,
|
module,
|
||||||
live_intervals: HashMap::new(),
|
live_intervals: HashMap::new(),
|
||||||
allocations: HashMap::new(),
|
allocations: HashMap::new(),
|
||||||
|
reg_types: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,8 +40,25 @@ impl<'a> X86Backend<'a> {
|
|||||||
match op {
|
match op {
|
||||||
ir::Operand::Integer(v) => Operand::Imm(*v as i64),
|
ir::Operand::Integer(v) => Operand::Imm(*v as i64),
|
||||||
ir::Operand::Boolean(b) => Operand::Imm(if *b { 1 } else { 0 }),
|
ir::Operand::Boolean(b) => Operand::Imm(if *b { 1 } else { 0 }),
|
||||||
|
ir::Operand::Float(f) => {
|
||||||
|
// For simplicity, load into a scratch XMM register if it's a constant
|
||||||
|
// Real compilers would use a constant pool.
|
||||||
|
let bits = f.to_bits();
|
||||||
|
cg.emit_mov(
|
||||||
|
OperandWidth::QWord,
|
||||||
|
Operand::Imm(bits as i64),
|
||||||
|
Operand::Reg(scratch_base),
|
||||||
|
);
|
||||||
|
cg.instructions.push(Instruction::Mov(
|
||||||
|
OperandWidth::QWord,
|
||||||
|
Operand::Reg(scratch_base),
|
||||||
|
Operand::Xmm(Xmm::Xmm15),
|
||||||
|
));
|
||||||
|
Operand::Xmm(Xmm::Xmm15)
|
||||||
|
}
|
||||||
ir::Operand::Register(r) => match self.allocations.get(r).unwrap() {
|
ir::Operand::Register(r) => match self.allocations.get(r).unwrap() {
|
||||||
Storage::Hardware(hw_gpr) => Operand::Reg(*hw_gpr),
|
Storage::Hardware(hw_gpr) => Operand::Reg(*hw_gpr),
|
||||||
|
Storage::Xmm(hw_xmm) => Operand::Xmm(*hw_xmm),
|
||||||
&Storage::Stack(offset) => Operand::Mem {
|
&Storage::Stack(offset) => Operand::Mem {
|
||||||
base: Gpr::Rbp,
|
base: Gpr::Rbp,
|
||||||
offset,
|
offset,
|
||||||
@@ -59,6 +79,7 @@ impl<'a> X86Backend<'a> {
|
|||||||
fn resolve_dest(&self, reg: ir::Register) -> Operand {
|
fn resolve_dest(&self, reg: ir::Register) -> Operand {
|
||||||
match self.allocations.get(®).unwrap() {
|
match self.allocations.get(®).unwrap() {
|
||||||
&Storage::Hardware(hw_gpr) => Operand::Reg(hw_gpr),
|
&Storage::Hardware(hw_gpr) => Operand::Reg(hw_gpr),
|
||||||
|
&Storage::Xmm(hw_xmm) => Operand::Xmm(hw_xmm),
|
||||||
&Storage::Stack(offset) | &Storage::Alloc(offset) => Operand::Mem {
|
&Storage::Stack(offset) | &Storage::Alloc(offset) => Operand::Mem {
|
||||||
base: Gpr::Rbp,
|
base: Gpr::Rbp,
|
||||||
offset,
|
offset,
|
||||||
@@ -88,16 +109,63 @@ impl<'a> X86Backend<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn compile_function(&mut self, func: &ir::Function) {
|
fn compile_function(&mut self, func: &ir::Function) {
|
||||||
// 0. Pre-Pass: Handle Allocations
|
self.reg_types.clear();
|
||||||
|
self.allocations.clear();
|
||||||
|
self.live_intervals.clear();
|
||||||
|
|
||||||
|
// 0. Pre-Pass: Handle Allocations and collect register types
|
||||||
let mut next_stack_offset = 0;
|
let mut next_stack_offset = 0;
|
||||||
|
|
||||||
|
for (ty, reg) in &func.params {
|
||||||
|
self.reg_types.insert(*reg, *ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut changed = true;
|
||||||
|
while changed {
|
||||||
|
changed = false;
|
||||||
for block in &func.blocks {
|
for block in &func.blocks {
|
||||||
for inst in &block.instructions {
|
for inst in &block.instructions {
|
||||||
if let ir::Instruction::Alloc { dest, .. } = inst {
|
let (dest, ty) = match inst {
|
||||||
|
ir::Instruction::Alloc { dest, .. } => {
|
||||||
|
if !self.allocations.contains_key(dest) {
|
||||||
next_stack_offset -= 8;
|
next_stack_offset -= 8;
|
||||||
self.allocations
|
self.allocations
|
||||||
.insert(*dest, Storage::Alloc(next_stack_offset));
|
.insert(*dest, Storage::Alloc(next_stack_offset));
|
||||||
}
|
}
|
||||||
|
(Some(*dest), Some(ir::Type::Ptr))
|
||||||
|
}
|
||||||
|
ir::Instruction::Binary {
|
||||||
|
dest, result_ty, ..
|
||||||
|
} => (Some(*dest), Some(*result_ty)),
|
||||||
|
ir::Instruction::Unary {
|
||||||
|
dest, result_ty, ..
|
||||||
|
} => (Some(*dest), Some(*result_ty)),
|
||||||
|
ir::Instruction::Load { dest, ty, .. } => (Some(*dest), Some(*ty)),
|
||||||
|
ir::Instruction::Call {
|
||||||
|
dest, result_ty, ..
|
||||||
|
} => (Some(*dest), Some(*result_ty)),
|
||||||
|
ir::Instruction::Phi {
|
||||||
|
dest, result_ty, ..
|
||||||
|
} => (Some(*dest), Some(*result_ty)),
|
||||||
|
ir::Instruction::Copy { dest, src } => {
|
||||||
|
let ty = match src {
|
||||||
|
ir::Operand::Integer(_) => Some(ir::Type::I64),
|
||||||
|
ir::Operand::Boolean(_) => Some(ir::Type::Bool),
|
||||||
|
ir::Operand::Float(_) => Some(ir::Type::F64),
|
||||||
|
ir::Operand::Register(r) => self.reg_types.get(r).copied(),
|
||||||
|
};
|
||||||
|
(Some(*dest), ty)
|
||||||
|
}
|
||||||
|
ir::Instruction::Store { .. } => (None, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let (Some(d), Some(t)) = (dest, ty) {
|
||||||
|
if !self.reg_types.contains_key(&d) {
|
||||||
|
self.reg_types.insert(d, t);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,9 +186,10 @@ impl<'a> X86Backend<'a> {
|
|||||||
ir::Instruction::Call { dest, args, .. } => {
|
ir::Instruction::Call { dest, args, .. } => {
|
||||||
self.mark_use(*dest, inst_idx);
|
self.mark_use(*dest, inst_idx);
|
||||||
let arg_regs = [Gpr::Rdi, Gpr::Rsi, Gpr::Rdx, Gpr::Rcx, Gpr::R8, Gpr::R9];
|
let arg_regs = [Gpr::Rdi, Gpr::Rsi, Gpr::Rdx, Gpr::Rcx, Gpr::R8, Gpr::R9];
|
||||||
for (i, (_, arg_op)) in args.iter().enumerate() {
|
for (i, (ty, arg_op)) in args.iter().enumerate() {
|
||||||
self.mark_op(arg_op, inst_idx);
|
self.mark_op(arg_op, inst_idx);
|
||||||
if i < 6
|
if i < 6
|
||||||
|
&& !matches!(ty, ir::Type::F32 | ir::Type::F64)
|
||||||
&& let ir::Operand::Register(r) = arg_op
|
&& let ir::Operand::Register(r) = arg_op
|
||||||
{
|
{
|
||||||
hints.insert(*r, arg_regs[i]);
|
hints.insert(*r, arg_regs[i]);
|
||||||
@@ -176,8 +245,26 @@ impl<'a> X86Backend<'a> {
|
|||||||
// 2. ABI-Aware Linear Scan Allocation
|
// 2. ABI-Aware Linear Scan Allocation
|
||||||
let mut free_callee_saved = vec![Gpr::Rbx, Gpr::R12, Gpr::R13, Gpr::R14, Gpr::R15];
|
let mut free_callee_saved = vec![Gpr::Rbx, Gpr::R12, Gpr::R13, Gpr::R14, Gpr::R15];
|
||||||
let mut free_caller_saved = vec![Gpr::Rdi, Gpr::Rsi, Gpr::Rdx, Gpr::Rcx, Gpr::R8, Gpr::R9];
|
let mut free_caller_saved = vec![Gpr::Rdi, Gpr::Rsi, Gpr::Rdx, Gpr::Rcx, Gpr::R8, Gpr::R9];
|
||||||
|
let mut free_xmm = vec![
|
||||||
|
Xmm::Xmm0,
|
||||||
|
Xmm::Xmm1,
|
||||||
|
Xmm::Xmm2,
|
||||||
|
Xmm::Xmm3,
|
||||||
|
Xmm::Xmm4,
|
||||||
|
Xmm::Xmm5,
|
||||||
|
Xmm::Xmm6,
|
||||||
|
Xmm::Xmm7,
|
||||||
|
Xmm::Xmm8,
|
||||||
|
Xmm::Xmm9,
|
||||||
|
Xmm::Xmm10,
|
||||||
|
Xmm::Xmm11,
|
||||||
|
Xmm::Xmm12,
|
||||||
|
Xmm::Xmm13,
|
||||||
|
Xmm::Xmm14,
|
||||||
|
];
|
||||||
|
|
||||||
let mut active: Vec<(ir::Register, usize, Gpr, bool)> = Vec::new();
|
let mut active_gpr: Vec<(ir::Register, usize, Gpr, bool)> = Vec::new();
|
||||||
|
let mut active_xmm: Vec<(ir::Register, usize, Xmm)> = Vec::new();
|
||||||
let mut used_callee_saved = HashSet::new();
|
let mut used_callee_saved = HashSet::new();
|
||||||
|
|
||||||
let live_intervals = take(&mut self.live_intervals);
|
let live_intervals = take(&mut self.live_intervals);
|
||||||
@@ -185,7 +272,7 @@ impl<'a> X86Backend<'a> {
|
|||||||
intervals_sorted.sort_by_key(|(_, (start, _))| *start);
|
intervals_sorted.sort_by_key(|(_, (start, _))| *start);
|
||||||
|
|
||||||
for (reg, (start, end)) in intervals_sorted {
|
for (reg, (start, end)) in intervals_sorted {
|
||||||
active.retain(|(_, active_end, hw_reg, is_caller)| {
|
active_gpr.retain(|(_, active_end, hw_reg, is_caller)| {
|
||||||
if *active_end < start {
|
if *active_end < start {
|
||||||
if *is_caller {
|
if *is_caller {
|
||||||
free_caller_saved.push(*hw_reg);
|
free_caller_saved.push(*hw_reg);
|
||||||
@@ -197,11 +284,32 @@ impl<'a> X86Backend<'a> {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
active_xmm.retain(|(_, active_end, hw_reg)| {
|
||||||
|
if *active_end < start {
|
||||||
|
free_xmm.push(*hw_reg);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let crosses_call = call_indices
|
let crosses_call = call_indices
|
||||||
.iter()
|
.iter()
|
||||||
.any(|&c_idx| c_idx > start && c_idx < end);
|
.any(|&c_idx| c_idx > start && c_idx < end);
|
||||||
|
|
||||||
|
let reg_ty = self.reg_types.get(®).unwrap();
|
||||||
|
let is_float = matches!(reg_ty, ir::Type::F32 | ir::Type::F64);
|
||||||
|
|
||||||
|
if is_float {
|
||||||
|
if let Some(hw_xmm) = free_xmm.pop() {
|
||||||
|
self.allocations.insert(reg, Storage::Xmm(hw_xmm));
|
||||||
|
active_xmm.push((reg, end, hw_xmm));
|
||||||
|
} else {
|
||||||
|
next_stack_offset += 8;
|
||||||
|
self.allocations
|
||||||
|
.insert(reg, Storage::Stack(next_stack_offset));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
let mut selected_hw = None;
|
let mut selected_hw = None;
|
||||||
let mut is_caller = false;
|
let mut is_caller = false;
|
||||||
|
|
||||||
@@ -224,20 +332,20 @@ impl<'a> X86Backend<'a> {
|
|||||||
&& let Some(r) = free_callee_saved.pop()
|
&& let Some(r) = free_callee_saved.pop()
|
||||||
{
|
{
|
||||||
selected_hw = Some(r);
|
selected_hw = Some(r);
|
||||||
|
|
||||||
used_callee_saved.insert(r);
|
used_callee_saved.insert(r);
|
||||||
is_caller = false;
|
is_caller = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(hw_reg) = selected_hw {
|
if let Some(hw_reg) = selected_hw {
|
||||||
self.allocations.insert(reg, Storage::Hardware(hw_reg));
|
self.allocations.insert(reg, Storage::Hardware(hw_reg));
|
||||||
active.push((reg, end, hw_reg, is_caller));
|
active_gpr.push((reg, end, hw_reg, is_caller));
|
||||||
} else {
|
} else {
|
||||||
next_stack_offset += 8;
|
next_stack_offset += 8;
|
||||||
self.allocations
|
self.allocations
|
||||||
.insert(reg, Storage::Stack(next_stack_offset));
|
.insert(reg, Storage::Stack(next_stack_offset));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let used_callee_saved: Vec<Gpr> = used_callee_saved.into_iter().collect();
|
let used_callee_saved: Vec<Gpr> = used_callee_saved.into_iter().collect();
|
||||||
|
|
||||||
@@ -291,26 +399,56 @@ impl<'a> X86Backend<'a> {
|
|||||||
|
|
||||||
// 4. Map ABI Arguments
|
// 4. Map ABI Arguments
|
||||||
let arg_regs = [Gpr::Rdi, Gpr::Rsi, Gpr::Rdx, Gpr::Rcx, Gpr::R8, Gpr::R9];
|
let arg_regs = [Gpr::Rdi, Gpr::Rsi, Gpr::Rdx, Gpr::Rcx, Gpr::R8, Gpr::R9];
|
||||||
for (i, (ty, reg)) in func.params.iter().enumerate() {
|
let xmm_regs = [
|
||||||
|
Xmm::Xmm0,
|
||||||
|
Xmm::Xmm1,
|
||||||
|
Xmm::Xmm2,
|
||||||
|
Xmm::Xmm3,
|
||||||
|
Xmm::Xmm4,
|
||||||
|
Xmm::Xmm5,
|
||||||
|
Xmm::Xmm6,
|
||||||
|
Xmm::Xmm7,
|
||||||
|
];
|
||||||
|
let mut gpr_idx = 0;
|
||||||
|
let mut xmm_idx = 0;
|
||||||
|
let mut stack_idx = 0;
|
||||||
|
|
||||||
|
for (ty, reg) in &func.params {
|
||||||
let width = OperandWidth::from_type(ty);
|
let width = OperandWidth::from_type(ty);
|
||||||
let dest_op = self.resolve_dest(*reg);
|
let dest_op = self.resolve_dest(*reg);
|
||||||
|
let is_float = matches!(ty, ir::Type::F32 | ir::Type::F64);
|
||||||
|
|
||||||
if i < 6 {
|
if is_float {
|
||||||
pro_cg.emit_mov(width, Operand::Reg(arg_regs[i]), dest_op);
|
if xmm_idx < 8 {
|
||||||
|
pro_cg.emit_fmov(width, Operand::Xmm(xmm_regs[xmm_idx]), dest_op);
|
||||||
|
xmm_idx += 1;
|
||||||
} else {
|
} else {
|
||||||
let caller_offset = 16 + ((i - 6) * 8);
|
let caller_offset = 16 + (stack_idx * 8);
|
||||||
|
let caller_mem = Operand::Mem {
|
||||||
|
base: Gpr::Rbp,
|
||||||
|
offset: caller_offset as i32,
|
||||||
|
};
|
||||||
|
pro_cg.emit_fmov(width, caller_mem, dest_op);
|
||||||
|
stack_idx += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if gpr_idx < 6 {
|
||||||
|
pro_cg.emit_mov(width, Operand::Reg(arg_regs[gpr_idx]), dest_op);
|
||||||
|
gpr_idx += 1;
|
||||||
|
} else {
|
||||||
|
let caller_offset = 16 + (stack_idx * 8);
|
||||||
let caller_mem = Operand::Mem {
|
let caller_mem = Operand::Mem {
|
||||||
base: Gpr::Rbp,
|
base: Gpr::Rbp,
|
||||||
offset: caller_offset as i32,
|
offset: caller_offset as i32,
|
||||||
};
|
};
|
||||||
|
|
||||||
// x86-64 constraint: Memory-to-memory moves must route through a register
|
|
||||||
if matches!(dest_op, Operand::Mem { .. }) {
|
if matches!(dest_op, Operand::Mem { .. }) {
|
||||||
pro_cg.emit_mov(width, caller_mem, Operand::Reg(Gpr::Rax));
|
pro_cg.emit_mov(width, caller_mem, Operand::Reg(Gpr::Rax));
|
||||||
pro_cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op);
|
pro_cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op);
|
||||||
} else {
|
} else {
|
||||||
pro_cg.emit_mov(width, caller_mem, dest_op);
|
pro_cg.emit_mov(width, caller_mem, dest_op);
|
||||||
}
|
}
|
||||||
|
stack_idx += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,26 +469,31 @@ impl<'a> X86Backend<'a> {
|
|||||||
|
|
||||||
writeln!(&mut self.assembly, ".L{}_block_{}:", func.name, block.id.0).unwrap();
|
writeln!(&mut self.assembly, ".L{}_block_{}:", func.name, block.id.0).unwrap();
|
||||||
|
|
||||||
// Peephole Optimization: Fuse an immediately preceding ICmp into the Branch
|
// Peephole Optimization: Fuse an immediately preceding Comparison into the Branch
|
||||||
let mut fused_cmp = None;
|
let mut fused_icmp = None;
|
||||||
|
let mut fused_fcmp = None;
|
||||||
if let ir::Terminator::Branch {
|
if let ir::Terminator::Branch {
|
||||||
cond: ir::Operand::Register(cond_reg),
|
cond: ir::Operand::Register(cond_reg),
|
||||||
..
|
..
|
||||||
} = block.terminator
|
} = block.terminator
|
||||||
&& let Some(ir::Instruction::Binary {
|
&& let Some(ir::Instruction::Binary {
|
||||||
dest,
|
dest,
|
||||||
op: ir::BinaryOp::ICmp(cmp_op),
|
op,
|
||||||
src1,
|
src1,
|
||||||
src2,
|
src2,
|
||||||
..
|
..
|
||||||
}) = block.instructions.last()
|
}) = block.instructions.last()
|
||||||
&& cond_reg == *dest
|
&& cond_reg == *dest
|
||||||
{
|
{
|
||||||
fused_cmp = Some((*cmp_op, *src1, *src2));
|
match op {
|
||||||
|
ir::BinaryOp::ICmp(cmp_op) => fused_icmp = Some((*cmp_op, *src1, *src2)),
|
||||||
|
ir::BinaryOp::FCmp(cmp_op) => fused_fcmp = Some((*cmp_op, *src1, *src2)),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we fused the comparison, we omit the last instruction from standard compilation
|
// If we fused the comparison, we omit the last instruction from standard compilation
|
||||||
let inst_limit = if fused_cmp.is_some() {
|
let inst_limit = if fused_icmp.is_some() || fused_fcmp.is_some() {
|
||||||
block.instructions.len() - 1
|
block.instructions.len() - 1
|
||||||
} else {
|
} else {
|
||||||
block.instructions.len()
|
block.instructions.len()
|
||||||
@@ -366,7 +509,8 @@ impl<'a> X86Backend<'a> {
|
|||||||
&block.terminator,
|
&block.terminator,
|
||||||
&func.name,
|
&func.name,
|
||||||
next_block_id,
|
next_block_id,
|
||||||
fused_cmp,
|
fused_icmp,
|
||||||
|
fused_fcmp,
|
||||||
&mut block_instructions,
|
&mut block_instructions,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -424,22 +568,42 @@ impl<'a> X86Backend<'a> {
|
|||||||
ir::Instruction::Alloc { .. } => {} // Handled in prologue
|
ir::Instruction::Alloc { .. } => {} // Handled in prologue
|
||||||
|
|
||||||
ir::Instruction::Copy { dest, src } => {
|
ir::Instruction::Copy { dest, src } => {
|
||||||
// Assuming everything is 64-bit for a raw copy unless we track sizes perfectly
|
|
||||||
let width = OperandWidth::QWord;
|
|
||||||
let dest_op = self.resolve_dest(*dest);
|
let dest_op = self.resolve_dest(*dest);
|
||||||
let src_op = self.resolve_op(src, Gpr::Rax, &mut cg);
|
let src_op = self.resolve_op(src, Gpr::Rax, &mut cg);
|
||||||
|
let is_float =
|
||||||
|
matches!(src_op, Operand::Xmm(_)) || matches!(dest_op, Operand::Xmm(_));
|
||||||
|
let width = if is_float {
|
||||||
|
let ty = self.reg_types.get(dest).unwrap();
|
||||||
|
OperandWidth::from_type(ty)
|
||||||
|
} else {
|
||||||
|
OperandWidth::QWord
|
||||||
|
};
|
||||||
|
|
||||||
if matches!(dest_op, Operand::Mem { .. }) && matches!(src_op, Operand::Mem { .. }) {
|
if is_float {
|
||||||
|
if matches!(dest_op, Operand::Mem { .. })
|
||||||
|
&& matches!(src_op, Operand::Mem { .. })
|
||||||
|
{
|
||||||
|
cg.emit_fmov(width, src_op, Operand::Xmm(Xmm::Xmm15));
|
||||||
|
cg.emit_fmov(width, Operand::Xmm(Xmm::Xmm15), dest_op);
|
||||||
|
} else {
|
||||||
|
cg.emit_fmov(width, src_op, dest_op);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if matches!(dest_op, Operand::Mem { .. })
|
||||||
|
&& matches!(src_op, Operand::Mem { .. })
|
||||||
|
{
|
||||||
cg.emit_mov(width, src_op, Operand::Reg(Gpr::Rax));
|
cg.emit_mov(width, src_op, Operand::Reg(Gpr::Rax));
|
||||||
cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op);
|
cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op);
|
||||||
} else {
|
} else {
|
||||||
cg.emit_mov(width, src_op, dest_op);
|
cg.emit_mov(width, src_op, dest_op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ir::Instruction::Load { ty, dest, src } => {
|
ir::Instruction::Load { ty, dest, src } => {
|
||||||
let width = OperandWidth::from_type(ty);
|
let width = OperandWidth::from_type(ty);
|
||||||
let dest_op = self.resolve_dest(*dest);
|
let dest_op = self.resolve_dest(*dest);
|
||||||
|
let is_float = matches!(ty, ir::Type::F32 | ir::Type::F64);
|
||||||
|
|
||||||
// If loading directly from an Alloc, we can bypass pointer materialization
|
// If loading directly from an Alloc, we can bypass pointer materialization
|
||||||
if let ir::Operand::Register(r) = src
|
if let ir::Operand::Register(r) = src
|
||||||
@@ -449,7 +613,11 @@ impl<'a> X86Backend<'a> {
|
|||||||
base: Gpr::Rbp,
|
base: Gpr::Rbp,
|
||||||
offset,
|
offset,
|
||||||
};
|
};
|
||||||
|
if is_float {
|
||||||
|
cg.emit_fmov(width, mem, dest_op);
|
||||||
|
} else {
|
||||||
cg.emit_mov(width, mem, dest_op);
|
cg.emit_mov(width, mem, dest_op);
|
||||||
|
}
|
||||||
block_instructions.extend(cg.finish());
|
block_instructions.extend(cg.finish());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -472,6 +640,14 @@ impl<'a> X86Backend<'a> {
|
|||||||
offset: 0,
|
offset: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if is_float {
|
||||||
|
if matches!(dest_op, Operand::Mem { .. }) {
|
||||||
|
cg.emit_fmov(width, deref, Operand::Xmm(Xmm::Xmm15));
|
||||||
|
cg.emit_fmov(width, Operand::Xmm(Xmm::Xmm15), dest_op);
|
||||||
|
} else {
|
||||||
|
cg.emit_fmov(width, deref, dest_op);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if matches!(dest_op, Operand::Mem { .. }) {
|
if matches!(dest_op, Operand::Mem { .. }) {
|
||||||
cg.emit_mov(width, deref, Operand::Reg(Gpr::R11));
|
cg.emit_mov(width, deref, Operand::Reg(Gpr::R11));
|
||||||
cg.emit_mov(width, Operand::Reg(Gpr::R11), dest_op);
|
cg.emit_mov(width, Operand::Reg(Gpr::R11), dest_op);
|
||||||
@@ -479,10 +655,12 @@ impl<'a> X86Backend<'a> {
|
|||||||
cg.emit_mov(width, deref, dest_op);
|
cg.emit_mov(width, deref, dest_op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ir::Instruction::Store { ty, dest, src } => {
|
ir::Instruction::Store { ty, dest, src } => {
|
||||||
let width = OperandWidth::from_type(ty);
|
let width = OperandWidth::from_type(ty);
|
||||||
let src_op = self.resolve_op(src, Gpr::Rax, &mut cg);
|
let src_op = self.resolve_op(src, Gpr::Rax, &mut cg);
|
||||||
|
let is_float = matches!(ty, ir::Type::F32 | ir::Type::F64);
|
||||||
|
|
||||||
// If storing directly to an Alloc, bypass pointer materialization
|
// If storing directly to an Alloc, bypass pointer materialization
|
||||||
if let ir::Operand::Register(r) = dest
|
if let ir::Operand::Register(r) = dest
|
||||||
@@ -492,12 +670,23 @@ impl<'a> X86Backend<'a> {
|
|||||||
base: Gpr::Rbp,
|
base: Gpr::Rbp,
|
||||||
offset,
|
offset,
|
||||||
};
|
};
|
||||||
if matches!(src_op, Operand::Mem { .. }) || matches!(src_op, Operand::Imm(_)) {
|
if is_float {
|
||||||
|
if matches!(src_op, Operand::Mem { .. }) {
|
||||||
|
cg.emit_fmov(width, src_op, Operand::Xmm(Xmm::Xmm15));
|
||||||
|
cg.emit_fmov(width, Operand::Xmm(Xmm::Xmm15), mem);
|
||||||
|
} else {
|
||||||
|
cg.emit_fmov(width, src_op, mem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if matches!(src_op, Operand::Mem { .. })
|
||||||
|
|| matches!(src_op, Operand::Imm(_))
|
||||||
|
{
|
||||||
cg.emit_mov(width, src_op, Operand::Reg(Gpr::Rax));
|
cg.emit_mov(width, src_op, Operand::Reg(Gpr::Rax));
|
||||||
cg.emit_mov(width, Operand::Reg(Gpr::Rax), mem);
|
cg.emit_mov(width, Operand::Reg(Gpr::Rax), mem);
|
||||||
} else {
|
} else {
|
||||||
cg.emit_mov(width, src_op, mem);
|
cg.emit_mov(width, src_op, mem);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
block_instructions.extend(cg.finish());
|
block_instructions.extend(cg.finish());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -517,6 +706,14 @@ impl<'a> X86Backend<'a> {
|
|||||||
offset: 0,
|
offset: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if is_float {
|
||||||
|
if matches!(src_op, Operand::Mem { .. }) {
|
||||||
|
cg.emit_fmov(width, src_op, Operand::Xmm(Xmm::Xmm15));
|
||||||
|
cg.emit_fmov(width, Operand::Xmm(Xmm::Xmm15), deref);
|
||||||
|
} else {
|
||||||
|
cg.emit_fmov(width, src_op, deref);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if matches!(src_op, Operand::Mem { .. }) || matches!(src_op, Operand::Imm(_)) {
|
if matches!(src_op, Operand::Mem { .. }) || matches!(src_op, Operand::Imm(_)) {
|
||||||
cg.emit_mov(width, src_op, Operand::Reg(Gpr::Rax));
|
cg.emit_mov(width, src_op, Operand::Reg(Gpr::Rax));
|
||||||
cg.emit_mov(width, Operand::Reg(Gpr::Rax), deref);
|
cg.emit_mov(width, Operand::Reg(Gpr::Rax), deref);
|
||||||
@@ -524,6 +721,7 @@ impl<'a> X86Backend<'a> {
|
|||||||
cg.emit_mov(width, src_op, deref);
|
cg.emit_mov(width, src_op, deref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ir::Instruction::Binary {
|
ir::Instruction::Binary {
|
||||||
dest,
|
dest,
|
||||||
@@ -534,6 +732,48 @@ impl<'a> X86Backend<'a> {
|
|||||||
} => {
|
} => {
|
||||||
let width = OperandWidth::from_type(result_ty);
|
let width = OperandWidth::from_type(result_ty);
|
||||||
let dest_op = self.resolve_dest(*dest);
|
let dest_op = self.resolve_dest(*dest);
|
||||||
|
let is_float = matches!(result_ty, ir::Type::F32 | ir::Type::F64);
|
||||||
|
|
||||||
|
if is_float {
|
||||||
|
let src1_op = self.resolve_op(src1, Gpr::R10, &mut cg);
|
||||||
|
let src2_op = self.resolve_op(src2, Gpr::R11, &mut cg);
|
||||||
|
|
||||||
|
if src1_op != dest_op {
|
||||||
|
cg.emit_fmov(width, src1_op, dest_op);
|
||||||
|
}
|
||||||
|
|
||||||
|
match op {
|
||||||
|
ir::BinaryOp::FAdd => cg.emit_fbinary(
|
||||||
|
Instruction::Addss,
|
||||||
|
Instruction::Addsd,
|
||||||
|
width,
|
||||||
|
src2_op,
|
||||||
|
dest_op,
|
||||||
|
),
|
||||||
|
ir::BinaryOp::FSub => cg.emit_fbinary(
|
||||||
|
Instruction::Subss,
|
||||||
|
Instruction::Subsd,
|
||||||
|
width,
|
||||||
|
src2_op,
|
||||||
|
dest_op,
|
||||||
|
),
|
||||||
|
ir::BinaryOp::FMul => cg.emit_fbinary(
|
||||||
|
Instruction::Mulss,
|
||||||
|
Instruction::Mulsd,
|
||||||
|
width,
|
||||||
|
src2_op,
|
||||||
|
dest_op,
|
||||||
|
),
|
||||||
|
ir::BinaryOp::FDiv => cg.emit_fbinary(
|
||||||
|
Instruction::Divss,
|
||||||
|
Instruction::Divsd,
|
||||||
|
width,
|
||||||
|
src2_op,
|
||||||
|
dest_op,
|
||||||
|
),
|
||||||
|
_ => todo!("Implement other float binary ops: {:?}", op),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
let src1_op = self.resolve_op(src1, Gpr::R10, &mut cg);
|
let src1_op = self.resolve_op(src1, Gpr::R10, &mut cg);
|
||||||
let src2_op = self.resolve_op(src2, Gpr::R11, &mut cg);
|
let src2_op = self.resolve_op(src2, Gpr::R11, &mut cg);
|
||||||
|
|
||||||
@@ -546,7 +786,6 @@ impl<'a> X86Backend<'a> {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure dest = src1 before applying destructive x86 math
|
|
||||||
if src1_op != dest_op {
|
if src1_op != dest_op {
|
||||||
if matches!(src1_op, Operand::Mem { .. })
|
if matches!(src1_op, Operand::Mem { .. })
|
||||||
&& matches!(dest_op, Operand::Mem { .. })
|
&& matches!(dest_op, Operand::Mem { .. })
|
||||||
@@ -577,8 +816,8 @@ impl<'a> X86Backend<'a> {
|
|||||||
}
|
}
|
||||||
cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op);
|
cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op);
|
||||||
}
|
}
|
||||||
// Implement Div/Rem/Cmp similarly using cg.emit_* methods...
|
_ => todo!("Implement other binary ops: {:?}", op),
|
||||||
_ => unimplemented!("Implement other binary ops"),
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,11 +829,46 @@ impl<'a> X86Backend<'a> {
|
|||||||
} => {
|
} => {
|
||||||
let width = OperandWidth::from_type(result_ty);
|
let width = OperandWidth::from_type(result_ty);
|
||||||
let dest_op = self.resolve_dest(*dest);
|
let dest_op = self.resolve_dest(*dest);
|
||||||
let src_op = self.resolve_op(src, Gpr::Rax, &mut cg);
|
let is_float = matches!(result_ty, ir::Type::F32 | ir::Type::F64);
|
||||||
|
|
||||||
|
if is_float {
|
||||||
|
let src_op = self.resolve_op(src, Gpr::Rax, &mut cg);
|
||||||
|
match op {
|
||||||
|
ir::UnaryOp::FNeg => {
|
||||||
|
// Negation: xor with -0.0
|
||||||
|
let mask = if *result_ty == ir::Type::F32 {
|
||||||
|
0x80000000u64
|
||||||
|
} else {
|
||||||
|
0x8000000000000000u64
|
||||||
|
};
|
||||||
|
cg.emit_mov(
|
||||||
|
OperandWidth::QWord,
|
||||||
|
Operand::Imm(mask as i64),
|
||||||
|
Operand::Reg(Gpr::Rax),
|
||||||
|
);
|
||||||
|
cg.instructions.push(Instruction::Mov(
|
||||||
|
OperandWidth::QWord,
|
||||||
|
Operand::Reg(Gpr::Rax),
|
||||||
|
Operand::Xmm(Xmm::Xmm15),
|
||||||
|
));
|
||||||
|
cg.emit_fmov(width, src_op, dest_op);
|
||||||
|
if *result_ty == ir::Type::F32 {
|
||||||
|
cg.instructions
|
||||||
|
.push(Instruction::Xorps(Operand::Xmm(Xmm::Xmm15), dest_op));
|
||||||
|
} else {
|
||||||
|
cg.instructions
|
||||||
|
.push(Instruction::Xorpd(Operand::Xmm(Xmm::Xmm15), dest_op));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => todo!("Implement other float unary ops"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let src_op = self.resolve_op(src, Gpr::Rax, &mut cg);
|
||||||
cg.emit_mov(width, src_op, dest_op);
|
cg.emit_mov(width, src_op, dest_op);
|
||||||
match op {
|
match op {
|
||||||
ir::UnaryOp::INeg => cg.instructions.push(Instruction::Neg(width, dest_op)),
|
ir::UnaryOp::INeg => cg.instructions.push(Instruction::Neg(width, dest_op)),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,18 +880,57 @@ impl<'a> X86Backend<'a> {
|
|||||||
} => {
|
} => {
|
||||||
let width = OperandWidth::from_type(result_ty);
|
let width = OperandWidth::from_type(result_ty);
|
||||||
let arg_regs = [Gpr::Rdi, Gpr::Rsi, Gpr::Rdx, Gpr::Rcx, Gpr::R8, Gpr::R9];
|
let arg_regs = [Gpr::Rdi, Gpr::Rsi, Gpr::Rdx, Gpr::Rcx, Gpr::R8, Gpr::R9];
|
||||||
|
let xmm_regs = [
|
||||||
|
Xmm::Xmm0,
|
||||||
|
Xmm::Xmm1,
|
||||||
|
Xmm::Xmm2,
|
||||||
|
Xmm::Xmm3,
|
||||||
|
Xmm::Xmm4,
|
||||||
|
Xmm::Xmm5,
|
||||||
|
Xmm::Xmm6,
|
||||||
|
Xmm::Xmm7,
|
||||||
|
];
|
||||||
|
let mut gpr_idx = 0;
|
||||||
|
let mut xmm_idx = 0;
|
||||||
|
let mut stack_args = Vec::new();
|
||||||
|
|
||||||
for (i, (ty, arg_op)) in args.iter().enumerate() {
|
for (ty, arg_op) in args {
|
||||||
let arg_width = OperandWidth::from_type(ty);
|
let arg_width = OperandWidth::from_type(ty);
|
||||||
let src = self.resolve_op(arg_op, Gpr::R10, &mut cg);
|
let src = self.resolve_op(arg_op, Gpr::R10, &mut cg);
|
||||||
if i < 6 {
|
let is_float = matches!(ty, ir::Type::F32 | ir::Type::F64);
|
||||||
cg.emit_mov(arg_width, src, Operand::Reg(arg_regs[i]));
|
|
||||||
|
if is_float {
|
||||||
|
if xmm_idx < 8 {
|
||||||
|
cg.emit_fmov(arg_width, src, Operand::Xmm(xmm_regs[xmm_idx]));
|
||||||
|
xmm_idx += 1;
|
||||||
} else {
|
} else {
|
||||||
if matches!(src, Operand::Mem { .. }) {
|
stack_args.push((arg_width, src, true));
|
||||||
cg.emit_mov(OperandWidth::QWord, src, Operand::Reg(Gpr::Rax));
|
}
|
||||||
|
} else {
|
||||||
|
if gpr_idx < 6 {
|
||||||
|
cg.emit_mov(arg_width, src, Operand::Reg(arg_regs[gpr_idx]));
|
||||||
|
gpr_idx += 1;
|
||||||
|
} else {
|
||||||
|
stack_args.push((arg_width, src, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_, src, is_float) in stack_args.iter().rev() {
|
||||||
|
if *is_float {
|
||||||
|
// Push float by moving to GPR first then pushing
|
||||||
|
cg.instructions.push(Instruction::Mov(
|
||||||
|
OperandWidth::QWord,
|
||||||
|
*src,
|
||||||
|
Operand::Reg(Gpr::Rax),
|
||||||
|
));
|
||||||
cg.emit_push(Operand::Reg(Gpr::Rax));
|
cg.emit_push(Operand::Reg(Gpr::Rax));
|
||||||
} else {
|
} else {
|
||||||
cg.emit_push(src);
|
if matches!(src, Operand::Mem { .. }) {
|
||||||
|
cg.emit_mov(OperandWidth::QWord, *src, Operand::Reg(Gpr::Rax));
|
||||||
|
cg.emit_push(Operand::Reg(Gpr::Rax));
|
||||||
|
} else {
|
||||||
|
cg.emit_push(*src);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -628,29 +941,31 @@ impl<'a> X86Backend<'a> {
|
|||||||
.iter()
|
.iter()
|
||||||
.find_map(|f| (f.id == *func).then(|| f.name.clone()))
|
.find_map(|f| (f.id == *func).then(|| f.name.clone()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
cg.instructions.push(Instruction::Call(func_name));
|
cg.instructions.push(Instruction::Call(func_name));
|
||||||
|
|
||||||
if args.len() > 6 {
|
let stack_cleanup = stack_args.len() * 8;
|
||||||
let cleanup = ((args.len() - 6) * 8) as i64;
|
if stack_cleanup > 0 {
|
||||||
cg.emit_binary(
|
cg.emit_binary(
|
||||||
Instruction::Add,
|
Instruction::Add,
|
||||||
OperandWidth::QWord,
|
OperandWidth::QWord,
|
||||||
Operand::Imm(cleanup),
|
Operand::Imm(stack_cleanup as i64),
|
||||||
Operand::Reg(Gpr::Rsp),
|
Operand::Reg(Gpr::Rsp),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if *result_ty != ir::Type::Void {
|
if *result_ty != ir::Type::Void {
|
||||||
let dest_op = self.resolve_dest(*dest);
|
let dest_op = self.resolve_dest(*dest);
|
||||||
|
if matches!(result_ty, ir::Type::F32 | ir::Type::F64) {
|
||||||
|
cg.emit_fmov(width, Operand::Xmm(Xmm::Xmm0), dest_op);
|
||||||
|
} else {
|
||||||
cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op);
|
cg.emit_mov(width, Operand::Reg(Gpr::Rax), dest_op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ir::Instruction::Phi { .. } => unreachable!("Run SSA Destruction pass before codegen!"),
|
ir::Instruction::Phi { .. } => unreachable!("Run SSA Destruction pass before codegen!"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit generated instructions to the block
|
|
||||||
block_instructions.extend(cg.finish());
|
block_instructions.extend(cg.finish());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,7 +974,8 @@ impl<'a> X86Backend<'a> {
|
|||||||
term: &ir::Terminator,
|
term: &ir::Terminator,
|
||||||
func_name: &str,
|
func_name: &str,
|
||||||
next_block_id: Option<ir::BlockId>,
|
next_block_id: Option<ir::BlockId>,
|
||||||
fused_cmp: Option<(ir::ICmpOp, ir::Operand, ir::Operand)>,
|
fused_icmp: Option<(ir::ICmpOp, ir::Operand, ir::Operand)>,
|
||||||
|
fused_fcmp: Option<(ir::FCmpOp, ir::Operand, ir::Operand)>,
|
||||||
block_instructions: &mut Vec<Instruction>,
|
block_instructions: &mut Vec<Instruction>,
|
||||||
) {
|
) {
|
||||||
let mut cg = Codegen::new();
|
let mut cg = Codegen::new();
|
||||||
@@ -669,10 +985,12 @@ impl<'a> X86Backend<'a> {
|
|||||||
if let Some(val) = value {
|
if let Some(val) = value {
|
||||||
let width = OperandWidth::from_type(return_ty);
|
let width = OperandWidth::from_type(return_ty);
|
||||||
let src = self.resolve_op(val, Gpr::R10, &mut cg);
|
let src = self.resolve_op(val, Gpr::R10, &mut cg);
|
||||||
|
if matches!(return_ty, ir::Type::F32 | ir::Type::F64) {
|
||||||
|
cg.emit_fmov(width, src, Operand::Xmm(Xmm::Xmm0));
|
||||||
|
} else {
|
||||||
cg.emit_mov(width, src, Operand::Reg(Gpr::Rax));
|
cg.emit_mov(width, src, Operand::Reg(Gpr::Rax));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// If there is no next block, we can fallthrough. Otherwise, emit a jump.
|
|
||||||
if next_block_id.is_some() {
|
if next_block_id.is_some() {
|
||||||
cg.instructions
|
cg.instructions
|
||||||
.push(Instruction::Jmp(format!(".L{}_epilogue", func_name)));
|
.push(Instruction::Jmp(format!(".L{}_epilogue", func_name)));
|
||||||
@@ -693,17 +1011,11 @@ impl<'a> X86Backend<'a> {
|
|||||||
then_block,
|
then_block,
|
||||||
else_block,
|
else_block,
|
||||||
} => {
|
} => {
|
||||||
let (cc_true, cc_false) = if let Some((cmp_op, src1, src2)) = fused_cmp {
|
let (cc_true, cc_false) = if let Some((cmp_op, src1, src2)) = fused_icmp {
|
||||||
let src1_op = self.resolve_op(&src1, Gpr::R10, &mut cg);
|
let src1_op = self.resolve_op(&src1, Gpr::R10, &mut cg);
|
||||||
let src2_op = self.resolve_op(&src2, Gpr::R11, &mut cg);
|
let src2_op = self.resolve_op(&src2, Gpr::R11, &mut cg);
|
||||||
|
|
||||||
// Note: IR Binary ops only track the result type (Bool for ICmp).
|
|
||||||
// To be perfectly accurate in the future, we may want to track operand types.
|
|
||||||
// For now, we assume pointers/64-bit integers (QWord) for the comparison.
|
|
||||||
let width = OperandWidth::QWord;
|
let width = OperandWidth::QWord;
|
||||||
|
|
||||||
// x86-64 constraints: destination (src1 in AT&T) cannot be an immediate.
|
|
||||||
// Memory-to-memory comparisons are also invalid.
|
|
||||||
if matches!(src1_op, Operand::Mem { .. })
|
if matches!(src1_op, Operand::Mem { .. })
|
||||||
&& matches!(src2_op, Operand::Mem { .. })
|
&& matches!(src2_op, Operand::Mem { .. })
|
||||||
|| matches!(src1_op, Operand::Imm(_))
|
|| matches!(src1_op, Operand::Imm(_))
|
||||||
@@ -731,11 +1043,35 @@ impl<'a> X86Backend<'a> {
|
|||||||
ir::ICmpOp::Ugt => (ConditionCode::A, ConditionCode::Be),
|
ir::ICmpOp::Ugt => (ConditionCode::A, ConditionCode::Be),
|
||||||
ir::ICmpOp::Uge => (ConditionCode::Ae, ConditionCode::B),
|
ir::ICmpOp::Uge => (ConditionCode::Ae, ConditionCode::B),
|
||||||
}
|
}
|
||||||
|
} else if let Some((cmp_op, src1, src2)) = fused_fcmp {
|
||||||
|
let src1_op = self.resolve_op(&src1, Gpr::R10, &mut cg);
|
||||||
|
let src2_op = self.resolve_op(&src2, Gpr::R11, &mut cg);
|
||||||
|
// Get type of src1 to determine width
|
||||||
|
let ty = if let ir::Operand::Register(r) = src1 {
|
||||||
|
*self.reg_types.get(&r).unwrap()
|
||||||
} else {
|
} else {
|
||||||
// Fallback: evaluate an isolated boolean
|
ir::Type::F64
|
||||||
let cond_op = self.resolve_op(cond, Gpr::R10, &mut cg);
|
};
|
||||||
let width = OperandWidth::Byte; // Booleans are 8-bit
|
let width = OperandWidth::from_type(&ty);
|
||||||
|
|
||||||
|
if width == OperandWidth::DWord {
|
||||||
|
cg.instructions.push(Instruction::Ucomiss(src2_op, src1_op));
|
||||||
|
} else {
|
||||||
|
cg.instructions.push(Instruction::Ucomisd(src2_op, src1_op));
|
||||||
|
}
|
||||||
|
|
||||||
|
match cmp_op {
|
||||||
|
ir::FCmpOp::Oeq => (ConditionCode::E, ConditionCode::Ne),
|
||||||
|
ir::FCmpOp::One => (ConditionCode::Ne, ConditionCode::E),
|
||||||
|
ir::FCmpOp::Olt => (ConditionCode::B, ConditionCode::Ae),
|
||||||
|
ir::FCmpOp::Ole => (ConditionCode::Be, ConditionCode::A),
|
||||||
|
ir::FCmpOp::Ogt => (ConditionCode::A, ConditionCode::Be),
|
||||||
|
ir::FCmpOp::Oge => (ConditionCode::Ae, ConditionCode::B),
|
||||||
|
_ => todo!("Implement other fcmp ops in branch: {:?}", cmp_op),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let cond_op = self.resolve_op(cond, Gpr::R10, &mut cg);
|
||||||
|
let width = OperandWidth::Byte;
|
||||||
if matches!(cond_op, Operand::Imm(_)) || matches!(cond_op, Operand::Mem { .. })
|
if matches!(cond_op, Operand::Imm(_)) || matches!(cond_op, Operand::Mem { .. })
|
||||||
{
|
{
|
||||||
cg.emit_mov(width, cond_op, Operand::Reg(Gpr::Rax));
|
cg.emit_mov(width, cond_op, Operand::Reg(Gpr::Rax));
|
||||||
@@ -748,14 +1084,12 @@ impl<'a> X86Backend<'a> {
|
|||||||
cg.instructions
|
cg.instructions
|
||||||
.push(Instruction::Test(width, cond_op, cond_op));
|
.push(Instruction::Test(width, cond_op, cond_op));
|
||||||
}
|
}
|
||||||
|
|
||||||
(ConditionCode::Nz, ConditionCode::Z)
|
(ConditionCode::Nz, ConditionCode::Z)
|
||||||
};
|
};
|
||||||
|
|
||||||
let then_label = format!(".L{}_block_{}", func_name, then_block.0);
|
let then_label = format!(".L{}_block_{}", func_name, then_block.0);
|
||||||
let else_label = format!(".L{}_block_{}", func_name, else_block.0);
|
let else_label = format!(".L{}_block_{}", func_name, else_block.0);
|
||||||
|
|
||||||
// Fallthrough logic cleanly applied to dynamically resolved jump conditions
|
|
||||||
if Some(*else_block) == next_block_id {
|
if Some(*else_block) == next_block_id {
|
||||||
cg.instructions.push(Instruction::Jcc(cc_true, then_label));
|
cg.instructions.push(Instruction::Jcc(cc_true, then_label));
|
||||||
} else if Some(*then_block) == next_block_id {
|
} else if Some(*then_block) == next_block_id {
|
||||||
@@ -768,7 +1102,6 @@ impl<'a> X86Backend<'a> {
|
|||||||
ir::Terminator::Unknown => panic!("Cannot compile Unknown terminator"),
|
ir::Terminator::Unknown => panic!("Cannot compile Unknown terminator"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit generated instructions to the block
|
|
||||||
block_instructions.extend(cg.finish());
|
block_instructions.extend(cg.finish());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+179
-3
@@ -26,8 +26,8 @@ impl OperandWidth {
|
|||||||
match ty {
|
match ty {
|
||||||
Type::Bool | Type::I8 => OperandWidth::Byte,
|
Type::Bool | Type::I8 => OperandWidth::Byte,
|
||||||
Type::I16 => OperandWidth::Word,
|
Type::I16 => OperandWidth::Word,
|
||||||
Type::I32 => OperandWidth::DWord,
|
Type::I32 | Type::F32 => OperandWidth::DWord,
|
||||||
Type::I64 | Type::Ptr => OperandWidth::QWord,
|
Type::I64 | Type::F64 | Type::Ptr => OperandWidth::QWord,
|
||||||
Type::Void => panic!("x86-64 error: Cannot compute width of Void type"),
|
Type::Void => panic!("x86-64 error: Cannot compute width of Void type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,6 +132,49 @@ impl Gpr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Xmm {
|
||||||
|
Xmm0,
|
||||||
|
Xmm1,
|
||||||
|
Xmm2,
|
||||||
|
Xmm3,
|
||||||
|
Xmm4,
|
||||||
|
Xmm5,
|
||||||
|
Xmm6,
|
||||||
|
Xmm7,
|
||||||
|
Xmm8,
|
||||||
|
Xmm9,
|
||||||
|
Xmm10,
|
||||||
|
Xmm11,
|
||||||
|
Xmm12,
|
||||||
|
Xmm13,
|
||||||
|
Xmm14,
|
||||||
|
Xmm15,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Xmm {
|
||||||
|
pub fn format(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Xmm::Xmm0 => "%xmm0",
|
||||||
|
Xmm::Xmm1 => "%xmm1",
|
||||||
|
Xmm::Xmm2 => "%xmm2",
|
||||||
|
Xmm::Xmm3 => "%xmm3",
|
||||||
|
Xmm::Xmm4 => "%xmm4",
|
||||||
|
Xmm::Xmm5 => "%xmm5",
|
||||||
|
Xmm::Xmm6 => "%xmm6",
|
||||||
|
Xmm::Xmm7 => "%xmm7",
|
||||||
|
Xmm::Xmm8 => "%xmm8",
|
||||||
|
Xmm::Xmm9 => "%xmm9",
|
||||||
|
Xmm::Xmm10 => "%xmm10",
|
||||||
|
Xmm::Xmm11 => "%xmm11",
|
||||||
|
Xmm::Xmm12 => "%xmm12",
|
||||||
|
Xmm::Xmm13 => "%xmm13",
|
||||||
|
Xmm::Xmm14 => "%xmm14",
|
||||||
|
Xmm::Xmm15 => "%xmm15",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum ConditionCode {
|
pub enum ConditionCode {
|
||||||
E,
|
E,
|
||||||
@@ -172,8 +215,10 @@ impl fmt::Display for ConditionCode {
|
|||||||
pub enum Operand {
|
pub enum Operand {
|
||||||
/// An immediate constant, e.g. `$5`
|
/// An immediate constant, e.g. `$5`
|
||||||
Imm(i64),
|
Imm(i64),
|
||||||
/// A hardware register
|
/// A hardware GPR register
|
||||||
Reg(Gpr),
|
Reg(Gpr),
|
||||||
|
/// A hardware XMM register
|
||||||
|
Xmm(Xmm),
|
||||||
/// A memory operand: `offset(base_reg)`
|
/// A memory operand: `offset(base_reg)`
|
||||||
Mem { base: Gpr, offset: i32 },
|
Mem { base: Gpr, offset: i32 },
|
||||||
}
|
}
|
||||||
@@ -189,6 +234,7 @@ impl Operand {
|
|||||||
match self {
|
match self {
|
||||||
Operand::Imm(val) => format!("${}", val),
|
Operand::Imm(val) => format!("${}", val),
|
||||||
Operand::Reg(gpr) => gpr.format_with_width(width).to_string(),
|
Operand::Reg(gpr) => gpr.format_with_width(width).to_string(),
|
||||||
|
Operand::Xmm(xmm) => xmm.format().to_string(),
|
||||||
// Memory addresses inherently use 64-bit pointers (QWord) for the base register,
|
// Memory addresses inherently use 64-bit pointers (QWord) for the base register,
|
||||||
// even if the value being read/written is smaller.
|
// even if the value being read/written is smaller.
|
||||||
Operand::Mem { base, offset } => {
|
Operand::Mem { base, offset } => {
|
||||||
@@ -210,6 +256,10 @@ pub enum Instruction {
|
|||||||
Movzx(OperandWidth, OperandWidth, Operand, Operand), // src_width, dest_width, src, dest
|
Movzx(OperandWidth, OperandWidth, Operand, Operand), // src_width, dest_width, src, dest
|
||||||
Lea(OperandWidth, Operand, Operand), // width, src (mem), dest (reg)
|
Lea(OperandWidth, Operand, Operand), // width, src (mem), dest (reg)
|
||||||
|
|
||||||
|
// Floating point movement
|
||||||
|
Movss(Operand, Operand), // 32-bit
|
||||||
|
Movsd(Operand, Operand), // 64-bit
|
||||||
|
|
||||||
// Stack manipulation (typically 64-bit in x86-64)
|
// Stack manipulation (typically 64-bit in x86-64)
|
||||||
Push(Operand),
|
Push(Operand),
|
||||||
Pop(Operand),
|
Pop(Operand),
|
||||||
@@ -224,9 +274,23 @@ pub enum Instruction {
|
|||||||
Div(OperandWidth, Operand),
|
Div(OperandWidth, Operand),
|
||||||
Cqto, // Sign-extend RAX into RDX:RAX
|
Cqto, // Sign-extend RAX into RDX:RAX
|
||||||
|
|
||||||
|
// Floating point arithmetic
|
||||||
|
Addss(Operand, Operand),
|
||||||
|
Addsd(Operand, Operand),
|
||||||
|
Subss(Operand, Operand),
|
||||||
|
Subsd(Operand, Operand),
|
||||||
|
Mulss(Operand, Operand),
|
||||||
|
Mulsd(Operand, Operand),
|
||||||
|
Divss(Operand, Operand),
|
||||||
|
Divsd(Operand, Operand),
|
||||||
|
Xorps(Operand, Operand), // Used for negation/clearing
|
||||||
|
Xorpd(Operand, Operand),
|
||||||
|
|
||||||
// Logic & Comparison
|
// Logic & Comparison
|
||||||
Cmp(OperandWidth, Operand, Operand), // width, src, dest
|
Cmp(OperandWidth, Operand, Operand), // width, src, dest
|
||||||
Test(OperandWidth, Operand, Operand),
|
Test(OperandWidth, Operand, Operand),
|
||||||
|
Ucomiss(Operand, Operand),
|
||||||
|
Ucomisd(Operand, Operand),
|
||||||
Setcc(ConditionCode, Operand), // e.g., sete %al
|
Setcc(ConditionCode, Operand), // e.g., sete %al
|
||||||
|
|
||||||
// Control Flow
|
// Control Flow
|
||||||
@@ -268,6 +332,22 @@ impl fmt::Display for Instruction {
|
|||||||
dest.format_with_width(*w)
|
dest.format_with_width(*w)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Instruction::Movss(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"movss {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::DWord),
|
||||||
|
dest.format_with_width(OperandWidth::DWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Movsd(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"movsd {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::QWord),
|
||||||
|
dest.format_with_width(OperandWidth::QWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
Instruction::Push(op) => {
|
Instruction::Push(op) => {
|
||||||
write!(f, "pushq {}", op.format_with_width(OperandWidth::QWord))
|
write!(f, "pushq {}", op.format_with_width(OperandWidth::QWord))
|
||||||
}
|
}
|
||||||
@@ -316,6 +396,86 @@ impl fmt::Display for Instruction {
|
|||||||
Instruction::Cqto => {
|
Instruction::Cqto => {
|
||||||
write!(f, "cqto")
|
write!(f, "cqto")
|
||||||
}
|
}
|
||||||
|
Instruction::Addss(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"addss {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::DWord),
|
||||||
|
dest.format_with_width(OperandWidth::DWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Addsd(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"addsd {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::QWord),
|
||||||
|
dest.format_with_width(OperandWidth::QWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Subss(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"subss {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::DWord),
|
||||||
|
dest.format_with_width(OperandWidth::DWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Subsd(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"subsd {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::QWord),
|
||||||
|
dest.format_with_width(OperandWidth::QWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Mulss(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"mulss {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::DWord),
|
||||||
|
dest.format_with_width(OperandWidth::DWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Mulsd(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"mulsd {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::QWord),
|
||||||
|
dest.format_with_width(OperandWidth::QWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Divss(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"divss {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::DWord),
|
||||||
|
dest.format_with_width(OperandWidth::DWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Divsd(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"divsd {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::QWord),
|
||||||
|
dest.format_with_width(OperandWidth::QWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Xorps(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"xorps {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::DWord),
|
||||||
|
dest.format_with_width(OperandWidth::DWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Xorpd(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"xorpd {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::QWord),
|
||||||
|
dest.format_with_width(OperandWidth::QWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
Instruction::Cmp(w, src, dest) => {
|
Instruction::Cmp(w, src, dest) => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
@@ -334,6 +494,22 @@ impl fmt::Display for Instruction {
|
|||||||
dest.format_with_width(*w)
|
dest.format_with_width(*w)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Instruction::Ucomiss(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"ucomiss {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::DWord),
|
||||||
|
dest.format_with_width(OperandWidth::DWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Instruction::Ucomisd(src, dest) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"ucomisd {}, {}",
|
||||||
|
src.format_with_width(OperandWidth::QWord),
|
||||||
|
dest.format_with_width(OperandWidth::QWord)
|
||||||
|
)
|
||||||
|
}
|
||||||
Instruction::Setcc(cc, dest) => {
|
Instruction::Setcc(cc, dest) => {
|
||||||
// setcc strictly operates on 8-bit (byte) registers
|
// setcc strictly operates on 8-bit (byte) registers
|
||||||
write!(
|
write!(
|
||||||
|
|||||||
@@ -282,6 +282,48 @@ impl IrFunctionBuilder {
|
|||||||
self.build_binary(Type::Bool, BinaryOp::ICmp(cmp_op), lhs, rhs)
|
self.build_binary(Type::Bool, BinaryOp::ICmp(cmp_op), lhs, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds an `fadd` instruction.
|
||||||
|
pub fn build_fadd(&mut self, result_ty: Type, lhs: Operand, rhs: Operand) -> Operand {
|
||||||
|
self.build_binary(result_ty, BinaryOp::FAdd, lhs, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds an `fsub` instruction.
|
||||||
|
pub fn build_fsub(&mut self, result_ty: Type, lhs: Operand, rhs: Operand) -> Operand {
|
||||||
|
self.build_binary(result_ty, BinaryOp::FSub, lhs, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds an `fmul` instruction.
|
||||||
|
pub fn build_fmul(&mut self, result_ty: Type, lhs: Operand, rhs: Operand) -> Operand {
|
||||||
|
self.build_binary(result_ty, BinaryOp::FMul, lhs, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds an `fdiv` instruction.
|
||||||
|
pub fn build_fdiv(&mut self, result_ty: Type, lhs: Operand, rhs: Operand) -> Operand {
|
||||||
|
self.build_binary(result_ty, BinaryOp::FDiv, lhs, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds an `frem` instruction.
|
||||||
|
pub fn build_frem(&mut self, result_ty: Type, lhs: Operand, rhs: Operand) -> Operand {
|
||||||
|
self.build_binary(result_ty, BinaryOp::FRem, lhs, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds an `fcmp` instruction.
|
||||||
|
pub fn build_fcmp(&mut self, cmp_op: FCmpOp, lhs: Operand, rhs: Operand) -> Operand {
|
||||||
|
self.build_binary(Type::Bool, BinaryOp::FCmp(cmp_op), lhs, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds an `fneg` instruction.
|
||||||
|
pub fn build_fneg(&mut self, result_ty: Type, src: Operand) -> Operand {
|
||||||
|
let dest = self.allocate_register();
|
||||||
|
self.insert_instruction(Instruction::Unary {
|
||||||
|
dest,
|
||||||
|
result_ty,
|
||||||
|
op: UnaryOp::FNeg,
|
||||||
|
src,
|
||||||
|
});
|
||||||
|
Operand::Register(dest)
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds a `call` instruction.
|
/// Builds a `call` instruction.
|
||||||
pub fn build_call<'a>(
|
pub fn build_call<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ pub enum Type {
|
|||||||
I16,
|
I16,
|
||||||
I32,
|
I32,
|
||||||
I64,
|
I64,
|
||||||
|
F32,
|
||||||
|
F64,
|
||||||
Ptr,
|
Ptr,
|
||||||
Void,
|
Void,
|
||||||
}
|
}
|
||||||
@@ -12,13 +14,39 @@ pub enum Type {
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct Register(pub usize);
|
pub struct Register(pub usize);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum Operand {
|
pub enum Operand {
|
||||||
Integer(u64),
|
Integer(u64),
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
|
Float(f64),
|
||||||
Register(Register),
|
Register(Register),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Eq for Operand {}
|
||||||
|
|
||||||
|
impl std::hash::Hash for Operand {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
match self {
|
||||||
|
Operand::Integer(i) => {
|
||||||
|
state.write_u8(0);
|
||||||
|
i.hash(state);
|
||||||
|
}
|
||||||
|
Operand::Boolean(b) => {
|
||||||
|
state.write_u8(1);
|
||||||
|
b.hash(state);
|
||||||
|
}
|
||||||
|
Operand::Float(f) => {
|
||||||
|
state.write_u8(2);
|
||||||
|
state.write_u64(f.to_bits());
|
||||||
|
}
|
||||||
|
Operand::Register(r) => {
|
||||||
|
state.write_u8(3);
|
||||||
|
r.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum ICmpOp {
|
pub enum ICmpOp {
|
||||||
Slt,
|
Slt,
|
||||||
@@ -33,6 +61,24 @@ pub enum ICmpOp {
|
|||||||
Ne,
|
Ne,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum FCmpOp {
|
||||||
|
Oeq,
|
||||||
|
Ogt,
|
||||||
|
Oge,
|
||||||
|
Olt,
|
||||||
|
Ole,
|
||||||
|
One,
|
||||||
|
Ord,
|
||||||
|
Uno,
|
||||||
|
Ueq,
|
||||||
|
Ugt,
|
||||||
|
Uge,
|
||||||
|
Ult,
|
||||||
|
Ule,
|
||||||
|
Une,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum BinaryOp {
|
pub enum BinaryOp {
|
||||||
Add,
|
Add,
|
||||||
@@ -44,11 +90,18 @@ pub enum BinaryOp {
|
|||||||
SRem,
|
SRem,
|
||||||
URem,
|
URem,
|
||||||
ICmp(ICmpOp),
|
ICmp(ICmpOp),
|
||||||
|
FAdd,
|
||||||
|
FSub,
|
||||||
|
FMul,
|
||||||
|
FDiv,
|
||||||
|
FRem,
|
||||||
|
FCmp(FCmpOp),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum UnaryOp {
|
pub enum UnaryOp {
|
||||||
INeg,
|
INeg,
|
||||||
|
FNeg,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
|||||||
+30
-14
@@ -6,6 +6,7 @@ fn build_test_module() -> Module {
|
|||||||
let mut module_builder = IrModuleBuilder::new();
|
let mut module_builder = IrModuleBuilder::new();
|
||||||
|
|
||||||
let i32_ty = Type::I32;
|
let i32_ty = Type::I32;
|
||||||
|
let f64_ty = Type::F64;
|
||||||
|
|
||||||
// 1. Define the Factorial Function
|
// 1. Define the Factorial Function
|
||||||
// factorial(n: i32) -> i32
|
// factorial(n: i32) -> i32
|
||||||
@@ -64,31 +65,44 @@ fn build_test_module() -> Module {
|
|||||||
}
|
}
|
||||||
module_builder.complete_function();
|
module_builder.complete_function();
|
||||||
|
|
||||||
// 2. Define the Main Function
|
// 2. Define a Floating Point Function: hypotenuse_sq(a: f64, b: f64) -> f64
|
||||||
|
let hypot_id = module_builder.new_function_id();
|
||||||
|
{
|
||||||
|
let f_builder =
|
||||||
|
module_builder.new_function(hypot_id, "hypotenuse_sq", vec![&f64_ty, &f64_ty], f64_ty);
|
||||||
|
|
||||||
|
let a = f_builder.get_param(0).unwrap();
|
||||||
|
let b = f_builder.get_param(1).unwrap();
|
||||||
|
|
||||||
|
let a_sq = f_builder.build_fmul(f64_ty, a, a);
|
||||||
|
let b_sq = f_builder.build_fmul(f64_ty, b, b);
|
||||||
|
let res = f_builder.build_fadd(f64_ty, a_sq, b_sq);
|
||||||
|
|
||||||
|
f_builder.build_return(f64_ty, res);
|
||||||
|
}
|
||||||
|
module_builder.complete_function();
|
||||||
|
|
||||||
|
// 3. Define the Main Function
|
||||||
let main_id = module_builder.new_function_id();
|
let main_id = module_builder.new_function_id();
|
||||||
{
|
{
|
||||||
let f_builder = module_builder.new_function(
|
let f_builder = module_builder.new_function(main_id, "main", vec![], i32_ty);
|
||||||
main_id,
|
|
||||||
"main",
|
|
||||||
vec![], // no params
|
|
||||||
i32_ty,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 1. Allocate space for an integer on the stack
|
// 1. Integer math: call factorial(5)
|
||||||
let ptr = f_builder.build_alloc(i32_ty);
|
let ptr = f_builder.build_alloc(i32_ty);
|
||||||
|
|
||||||
// 2. Store the value '5' into that pointer
|
|
||||||
let input_val = Operand::Integer(5);
|
let input_val = Operand::Integer(5);
|
||||||
f_builder.build_store(i32_ty, ptr, input_val);
|
f_builder.build_store(i32_ty, ptr, input_val);
|
||||||
|
|
||||||
// 3. Load the value back from the pointer
|
|
||||||
let loaded_val = f_builder.build_load(i32_ty, ptr);
|
let loaded_val = f_builder.build_load(i32_ty, ptr);
|
||||||
|
|
||||||
// 4. Call factorial(loaded_val)
|
|
||||||
let args = [(i32_ty, loaded_val)];
|
let args = [(i32_ty, loaded_val)];
|
||||||
let final_result = f_builder.build_call(i32_ty, fact_id, args.iter());
|
let final_result = f_builder.build_call(i32_ty, fact_id, args.iter());
|
||||||
|
|
||||||
// 5. Return the result of the factorial call
|
// 2. Floating point math: call hypotenuse_sq(3.0, 4.0)
|
||||||
|
let arg1 = Operand::Float(3.0);
|
||||||
|
let arg2 = Operand::Float(4.0);
|
||||||
|
let hypot_args = [(f64_ty, arg1), (f64_ty, arg2)];
|
||||||
|
let _hypot_res = f_builder.build_call(f64_ty, hypot_id, hypot_args.iter());
|
||||||
|
|
||||||
|
// 3. Return the result of the factorial call
|
||||||
f_builder.build_return(i32_ty, final_result);
|
f_builder.build_return(i32_ty, final_result);
|
||||||
}
|
}
|
||||||
module_builder.complete_function();
|
module_builder.complete_function();
|
||||||
@@ -103,6 +117,8 @@ fn main() {
|
|||||||
validate_module(&module).expect("failed to validate module");
|
validate_module(&module).expect("failed to validate module");
|
||||||
passes::optimize(&mut module);
|
passes::optimize(&mut module);
|
||||||
|
|
||||||
|
println!("{}", module);
|
||||||
|
|
||||||
let assembly = X86Backend::new(&module).compile_module();
|
let assembly = X86Backend::new(&module).compile_module();
|
||||||
println!("{}", assembly);
|
println!("{}", assembly);
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-3
@@ -75,10 +75,11 @@ fn fold_function_constants(func: &mut Function) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Helper Functions ---
|
|
||||||
|
|
||||||
fn is_constant(op: &Operand) -> bool {
|
fn is_constant(op: &Operand) -> bool {
|
||||||
matches!(op, Operand::Integer(_) | Operand::Boolean(_))
|
matches!(
|
||||||
|
op,
|
||||||
|
Operand::Integer(_) | Operand::Boolean(_) | Operand::Float(_)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn substitute_constants_in_inst(inst: &mut Instruction, constants: &HashMap<Register, Operand>) {
|
fn substitute_constants_in_inst(inst: &mut Instruction, constants: &HashMap<Register, Operand>) {
|
||||||
@@ -191,6 +192,7 @@ fn evaluate_binary(op: BinaryOp, src1: &Operand, src2: &Operand) -> Option<Opera
|
|||||||
};
|
};
|
||||||
Some(Operand::Boolean(res))
|
Some(Operand::Boolean(res))
|
||||||
}
|
}
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Operand::Boolean(a), Operand::Boolean(b)) => match op {
|
(Operand::Boolean(a), Operand::Boolean(b)) => match op {
|
||||||
@@ -198,6 +200,38 @@ fn evaluate_binary(op: BinaryOp, src1: &Operand, src2: &Operand) -> Option<Opera
|
|||||||
BinaryOp::ICmp(ICmpOp::Ne) => Some(Operand::Boolean(a != b)),
|
BinaryOp::ICmp(ICmpOp::Ne) => Some(Operand::Boolean(a != b)),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
|
(Operand::Float(a), Operand::Float(b)) => {
|
||||||
|
let a = *a;
|
||||||
|
let b = *b;
|
||||||
|
|
||||||
|
match op {
|
||||||
|
BinaryOp::FAdd => Some(Operand::Float(a + b)),
|
||||||
|
BinaryOp::FSub => Some(Operand::Float(a - b)),
|
||||||
|
BinaryOp::FMul => Some(Operand::Float(a * b)),
|
||||||
|
BinaryOp::FDiv => Some(Operand::Float(a / b)),
|
||||||
|
BinaryOp::FRem => Some(Operand::Float(a % b)),
|
||||||
|
BinaryOp::FCmp(cmp) => {
|
||||||
|
let res = match cmp {
|
||||||
|
FCmpOp::Oeq => a == b,
|
||||||
|
FCmpOp::Ogt => a > b,
|
||||||
|
FCmpOp::Oge => a >= b,
|
||||||
|
FCmpOp::Olt => a < b,
|
||||||
|
FCmpOp::Ole => a <= b,
|
||||||
|
FCmpOp::One => a != b && !a.is_nan() && !b.is_nan(),
|
||||||
|
FCmpOp::Ord => !a.is_nan() && !b.is_nan(),
|
||||||
|
FCmpOp::Uno => a.is_nan() || b.is_nan(),
|
||||||
|
FCmpOp::Ueq => a == b || a.is_nan() || b.is_nan(),
|
||||||
|
FCmpOp::Ugt => a > b || a.is_nan() || b.is_nan(),
|
||||||
|
FCmpOp::Uge => a >= b || a.is_nan() || b.is_nan(),
|
||||||
|
FCmpOp::Ult => a < b || a.is_nan() || b.is_nan(),
|
||||||
|
FCmpOp::Ule => a <= b || a.is_nan() || b.is_nan(),
|
||||||
|
FCmpOp::Une => a != b || a.is_nan() || b.is_nan(),
|
||||||
|
};
|
||||||
|
Some(Operand::Boolean(res))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,6 +239,7 @@ fn evaluate_binary(op: BinaryOp, src1: &Operand, src2: &Operand) -> Option<Opera
|
|||||||
fn evaluate_unary(op: UnaryOp, src: &Operand) -> Option<Operand> {
|
fn evaluate_unary(op: UnaryOp, src: &Operand) -> Option<Operand> {
|
||||||
match (op, src) {
|
match (op, src) {
|
||||||
(UnaryOp::INeg, Operand::Integer(a)) => Some(Operand::Integer(a.wrapping_neg())),
|
(UnaryOp::INeg, Operand::Integer(a)) => Some(Operand::Integer(a.wrapping_neg())),
|
||||||
|
(UnaryOp::FNeg, Operand::Float(a)) => Some(Operand::Float(-a)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-2
@@ -1,8 +1,8 @@
|
|||||||
use std::fmt::{self, Display, Write};
|
use std::fmt::{self, Display, Write};
|
||||||
|
|
||||||
use crate::ir::{
|
use crate::ir::{
|
||||||
BasicBlock, BinaryOp, BlockId, Function, ICmpOp, Instruction, Module, Operand, Register,
|
BasicBlock, BinaryOp, BlockId, FCmpOp, Function, ICmpOp, Instruction, Module, Operand,
|
||||||
Terminator, Type, UnaryOp,
|
Register, Terminator, Type, UnaryOp,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Display for Register {
|
impl Display for Register {
|
||||||
@@ -16,6 +16,7 @@ impl Display for Operand {
|
|||||||
match self {
|
match self {
|
||||||
Operand::Integer(value) => write!(f, "${}", value),
|
Operand::Integer(value) => write!(f, "${}", value),
|
||||||
Operand::Boolean(value) => write!(f, "${}", value),
|
Operand::Boolean(value) => write!(f, "${}", value),
|
||||||
|
Operand::Float(value) => write!(f, "${}", value),
|
||||||
Operand::Register(register) => register.fmt(f),
|
Operand::Register(register) => register.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,6 +30,8 @@ impl Display for Type {
|
|||||||
Type::I16 => write!(f, "i16"),
|
Type::I16 => write!(f, "i16"),
|
||||||
Type::I32 => write!(f, "i32"),
|
Type::I32 => write!(f, "i32"),
|
||||||
Type::I64 => write!(f, "i64"),
|
Type::I64 => write!(f, "i64"),
|
||||||
|
Type::F32 => write!(f, "f32"),
|
||||||
|
Type::F64 => write!(f, "f64"),
|
||||||
Type::Ptr => write!(f, "ptr"),
|
Type::Ptr => write!(f, "ptr"),
|
||||||
Type::Void => write!(f, "void"),
|
Type::Void => write!(f, "void"),
|
||||||
}
|
}
|
||||||
@@ -45,6 +48,7 @@ impl Display for UnaryOp {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
UnaryOp::INeg => write!(f, "ineg"),
|
UnaryOp::INeg => write!(f, "ineg"),
|
||||||
|
UnaryOp::FNeg => write!(f, "fneg"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +65,12 @@ impl Display for BinaryOp {
|
|||||||
BinaryOp::SRem => write!(f, "srem"),
|
BinaryOp::SRem => write!(f, "srem"),
|
||||||
BinaryOp::URem => write!(f, "urem"),
|
BinaryOp::URem => write!(f, "urem"),
|
||||||
BinaryOp::ICmp(icmp_op) => write!(f, "icmp {}", icmp_op),
|
BinaryOp::ICmp(icmp_op) => write!(f, "icmp {}", icmp_op),
|
||||||
|
BinaryOp::FAdd => write!(f, "fadd"),
|
||||||
|
BinaryOp::FSub => write!(f, "fsub"),
|
||||||
|
BinaryOp::FDiv => write!(f, "fdiv"),
|
||||||
|
BinaryOp::FMul => write!(f, "fmul"),
|
||||||
|
BinaryOp::FRem => write!(f, "frem"),
|
||||||
|
BinaryOp::FCmp(fcmp_op) => write!(f, "fcmp {}", fcmp_op),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,6 +92,27 @@ impl Display for ICmpOp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for FCmpOp {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
FCmpOp::Oeq => write!(f, "oeq"),
|
||||||
|
FCmpOp::Ogt => write!(f, "ogt"),
|
||||||
|
FCmpOp::Oge => write!(f, "oge"),
|
||||||
|
FCmpOp::Olt => write!(f, "olt"),
|
||||||
|
FCmpOp::Ole => write!(f, "ole"),
|
||||||
|
FCmpOp::One => write!(f, "one"),
|
||||||
|
FCmpOp::Ord => write!(f, "ord"),
|
||||||
|
FCmpOp::Uno => write!(f, "uno"),
|
||||||
|
FCmpOp::Ueq => write!(f, "ueq"),
|
||||||
|
FCmpOp::Ugt => write!(f, "ugt"),
|
||||||
|
FCmpOp::Uge => write!(f, "uge"),
|
||||||
|
FCmpOp::Ult => write!(f, "ult"),
|
||||||
|
FCmpOp::Ule => write!(f, "ule"),
|
||||||
|
FCmpOp::Une => write!(f, "une"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Module {
|
impl Display for Module {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str(IrPrinter::print(self)?.as_str())
|
f.write_str(IrPrinter::print(self)?.as_str())
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ fn validate_function(
|
|||||||
Operand::Register(r) => reg_types.get(r).copied(),
|
Operand::Register(r) => reg_types.get(r).copied(),
|
||||||
Operand::Boolean(_) => Some(Type::Bool),
|
Operand::Boolean(_) => Some(Type::Bool),
|
||||||
Operand::Integer(_) => Some(Type::I64), // Default fallback for untyped literals
|
Operand::Integer(_) => Some(Type::I64), // Default fallback for untyped literals
|
||||||
|
Operand::Float(_) => Some(Type::F64), // Default fallback
|
||||||
};
|
};
|
||||||
(Some(*register), inferred_ty)
|
(Some(*register), inferred_ty)
|
||||||
}
|
}
|
||||||
@@ -103,6 +104,12 @@ fn validate_function(
|
|||||||
return Err(format!("Cannot use integer literal as {:?}", expected));
|
return Err(format!("Cannot use integer literal as {:?}", expected));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Operand::Float(_) => match expected {
|
||||||
|
Type::F32 | Type::F64 => {}
|
||||||
|
_ => {
|
||||||
|
return Err(format!("Cannot use float literal as {:?}", expected));
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -150,6 +157,27 @@ fn validate_function(
|
|||||||
Operand::Register(r) => *reg_types.get(r).unwrap_or(&Type::I64),
|
Operand::Register(r) => *reg_types.get(r).unwrap_or(&Type::I64),
|
||||||
_ => Type::I64,
|
_ => Type::I64,
|
||||||
},
|
},
|
||||||
|
Operand::Float(_) => match src2 {
|
||||||
|
Operand::Register(r) => *reg_types.get(r).unwrap_or(&Type::F64),
|
||||||
|
_ => Type::F64,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
check_operand(src1, op_type)?;
|
||||||
|
check_operand(src2, op_type)?;
|
||||||
|
} else if let BinaryOp::FCmp(_) = op {
|
||||||
|
if *result_ty != Type::Bool {
|
||||||
|
return Err("FCmp result type must be Bool".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let op_type = match src1 {
|
||||||
|
Operand::Register(r) => *reg_types
|
||||||
|
.get(r)
|
||||||
|
.ok_or_else(|| format!("Unknown reg {:?}", r))?,
|
||||||
|
Operand::Float(_) => match src2 {
|
||||||
|
Operand::Register(r) => *reg_types.get(r).unwrap_or(&Type::F64),
|
||||||
|
_ => Type::F64,
|
||||||
|
},
|
||||||
|
_ => return Err("FCmp operands must be floating point".to_string()),
|
||||||
};
|
};
|
||||||
check_operand(src1, op_type)?;
|
check_operand(src1, op_type)?;
|
||||||
check_operand(src2, op_type)?;
|
check_operand(src2, op_type)?;
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
use scarlett::{builder::IrModuleBuilder, ir::*, validate::validate_module};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_float_ir() {
|
||||||
|
let mut module_builder = IrModuleBuilder::new();
|
||||||
|
let f64_ty = Type::F64;
|
||||||
|
|
||||||
|
let func_id = module_builder.new_function_id();
|
||||||
|
{
|
||||||
|
let f_builder =
|
||||||
|
module_builder.new_function(func_id, "float_math", vec![&f64_ty, &f64_ty], f64_ty);
|
||||||
|
|
||||||
|
let a = f_builder.get_param(0).unwrap();
|
||||||
|
let b = f_builder.get_param(1).unwrap();
|
||||||
|
|
||||||
|
let sum = f_builder.build_fadd(f64_ty, a, b);
|
||||||
|
let diff = f_builder.build_fsub(f64_ty, sum, a);
|
||||||
|
let prod = f_builder.build_fmul(f64_ty, diff, b);
|
||||||
|
let quot = f_builder.build_fdiv(f64_ty, prod, a);
|
||||||
|
let neg = f_builder.build_fneg(f64_ty, quot);
|
||||||
|
|
||||||
|
let is_gt = f_builder.build_fcmp(FCmpOp::Ogt, neg, Operand::Float(0.0));
|
||||||
|
|
||||||
|
let then_block = f_builder.create_block();
|
||||||
|
let else_block = f_builder.create_block();
|
||||||
|
|
||||||
|
f_builder.build_branch(is_gt, then_block, else_block);
|
||||||
|
|
||||||
|
f_builder.switch_to_block(then_block);
|
||||||
|
f_builder.build_return(f64_ty, neg);
|
||||||
|
|
||||||
|
f_builder.switch_to_block(else_block);
|
||||||
|
f_builder.build_return(f64_ty, Operand::Float(42.0));
|
||||||
|
}
|
||||||
|
module_builder.complete_function();
|
||||||
|
|
||||||
|
let module = module_builder.finish();
|
||||||
|
|
||||||
|
validate_module(&module).expect("Module validation failed");
|
||||||
|
|
||||||
|
let printed = format!("{}", module);
|
||||||
|
println!("{}", printed);
|
||||||
|
|
||||||
|
assert!(printed.contains("f64"));
|
||||||
|
assert!(printed.contains("fadd"));
|
||||||
|
assert!(printed.contains("fsub"));
|
||||||
|
assert!(printed.contains("fmul"));
|
||||||
|
assert!(printed.contains("fdiv"));
|
||||||
|
assert!(printed.contains("fneg"));
|
||||||
|
assert!(printed.contains("fcmp ogt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_float_constant_folding() {
|
||||||
|
let mut module_builder = IrModuleBuilder::new();
|
||||||
|
let f64_ty = Type::F64;
|
||||||
|
|
||||||
|
let func_id = module_builder.new_function_id();
|
||||||
|
{
|
||||||
|
let f_builder = module_builder.new_function(func_id, "fold_me", vec![], f64_ty);
|
||||||
|
|
||||||
|
let a = Operand::Float(10.0);
|
||||||
|
let b = Operand::Float(2.0);
|
||||||
|
|
||||||
|
let sum = f_builder.build_fadd(f64_ty, a, b); // 12.0
|
||||||
|
let prod = f_builder.build_fmul(f64_ty, sum, b); // 24.0
|
||||||
|
|
||||||
|
f_builder.build_return(f64_ty, prod);
|
||||||
|
}
|
||||||
|
module_builder.complete_function();
|
||||||
|
|
||||||
|
let mut module = module_builder.finish();
|
||||||
|
|
||||||
|
validate_module(&module).expect("Module validation failed");
|
||||||
|
|
||||||
|
scarlett::passes::cfp::fold_constants(&mut module);
|
||||||
|
|
||||||
|
let printed = format!("{}", module);
|
||||||
|
println!("{}", printed);
|
||||||
|
|
||||||
|
assert!(printed.contains("$24"));
|
||||||
|
assert!(!printed.contains("fadd"));
|
||||||
|
assert!(!printed.contains("fmul"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_float_codegen() {
|
||||||
|
let mut module_builder = IrModuleBuilder::new();
|
||||||
|
let f64_ty = Type::F64;
|
||||||
|
|
||||||
|
let func_id = module_builder.new_function_id();
|
||||||
|
{
|
||||||
|
let f_builder =
|
||||||
|
module_builder.new_function(func_id, "add_floats", vec![&f64_ty, &f64_ty], f64_ty);
|
||||||
|
|
||||||
|
let a = f_builder.get_param(0).unwrap();
|
||||||
|
let b = f_builder.get_param(1).unwrap();
|
||||||
|
|
||||||
|
let res = f_builder.build_fadd(f64_ty, a, b);
|
||||||
|
f_builder.build_return(f64_ty, res);
|
||||||
|
}
|
||||||
|
module_builder.complete_function();
|
||||||
|
|
||||||
|
let module = module_builder.finish();
|
||||||
|
|
||||||
|
let assembly = scarlett::backend::x86_64::X86Backend::new(&module).compile_module();
|
||||||
|
println!("{}", assembly);
|
||||||
|
|
||||||
|
assert!(assembly.contains("addsd"));
|
||||||
|
assert!(assembly.contains("movsd"));
|
||||||
|
assert!(assembly.contains("%xmm0"));
|
||||||
|
assert!(assembly.contains("%xmm1"));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user