From a4e4b25629883e394667bc44606d69e606ceb37a Mon Sep 17 00:00:00 2001 From: Philipe Caetano Date: Wed, 24 Jun 2026 22:45:25 -0300 Subject: [PATCH 1/4] feat(ir): adiciona campo de tipo as instrucoes TAC (Issue #172) --- src/ir/tac.rs | 45 ++++++++++----------------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/src/ir/tac.rs b/src/ir/tac.rs index 6ae6852..dd19918 100644 --- a/src/ir/tac.rs +++ b/src/ir/tac.rs @@ -1,6 +1,8 @@ use std::fmt; use crate::common::ast::expr::{BinOp, UnOp}; +use crate::common::ast::Type; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TempId(pub u32); @@ -80,15 +82,18 @@ pub enum TacInstr { op: BinOp, lhs: Operand, rhs: Operand, + ty: Type, }, UnOp { dst: TempId, op: UnOp, src: Operand, + ty:Type, }, Copy { dst: Operand, src: Operand, + ty: Type, }, Jump { label: LabelId, @@ -105,6 +110,7 @@ pub enum TacInstr { }, Return { val: Option, + ty: Option }, Label(LabelId), } @@ -163,13 +169,13 @@ impl fmt::Display for Operand { impl fmt::Display for TacInstr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TacInstr::BinOp { dst, op, lhs, rhs } => { + TacInstr::BinOp { dst, op, lhs, rhs, ty: _ } => { write!(f, "{dst} = {lhs} {} {rhs}", bin_op_symbol(op)) } - TacInstr::UnOp { dst, op, src } => { + TacInstr::UnOp { dst, op, src, ty: _ } => { write!(f, "{dst} = {}{src}", un_op_symbol(op)) } - TacInstr::Copy { dst, src } => write!(f, "{dst} = {src}"), + TacInstr::Copy { dst, src, ty: _ } => write!(f, "{dst} = {src}"), TacInstr::Jump { label } => write!(f, "goto {label}"), TacInstr::CondJump { cond, @@ -190,7 +196,7 @@ impl fmt::Display for TacInstr { } write!(f, ")") } - TacInstr::Return { val } => { + TacInstr::Return { val, ty: _ } => { if let Some(val) = val { write!(f, "return {val}") } else { @@ -235,35 +241,4 @@ fn un_op_symbol(op: &UnOp) -> &'static str { } } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn tac_instr_display_binop() { - let instr = TacInstr::BinOp { - dst: TempId(0), - op: BinOp::Add, - lhs: Operand::Temp(TempId(1)), - rhs: Operand::Temp(TempId(2)), - }; - - assert_eq!(instr.to_string(), "t0 = t1 + t2"); - } - - #[test] - fn temp_gen_increments() { - let mut gen = TempGen::new(); - assert_eq!(gen.fresh(), TempId(0)); - assert_eq!(gen.fresh(), TempId(1)); - } - - #[test] - fn label_gen_unique() { - let mut gen = LabelGen::new(); - - assert_eq!(gen.fresh(), LabelId(0)); - assert_eq!(gen.fresh(), LabelId(1)); - } -} From 9c1c98425c22e801ba28088d09c76d30fbec8b57 Mon Sep 17 00:00:00 2001 From: Philipe Caetano Date: Wed, 24 Jun 2026 22:45:39 -0300 Subject: [PATCH 2/4] feat(codegen): implementa suporte a double e registradores XMM (Issue #172) --- src/codegen/last/x86_64.rs | 143 +++++++++++++++++++++++++++++-------- 1 file changed, 115 insertions(+), 28 deletions(-) diff --git a/src/codegen/last/x86_64.rs b/src/codegen/last/x86_64.rs index 497791c..42fbcaa 100644 --- a/src/codegen/last/x86_64.rs +++ b/src/codegen/last/x86_64.rs @@ -21,6 +21,7 @@ use crate::common::ast::expr::{BinOp, UnOp}; use crate::common::errors::types::CodegenError; use crate::ir::tac::{ConstValue, LabelId, Operand, TacFunction, TacInstr, TacProgram}; use std::collections::HashMap; +use crate::common::ast::ast::Type; type EmitResult = Result; @@ -40,6 +41,8 @@ struct Emitter { struct StringPool { entries: Vec<(String, String)>, labels: HashMap, + double_entries: Vec<(String, f64)>, + double_labels: HashMap, } impl StringPool { @@ -80,8 +83,16 @@ impl StringPool { } fn visit_operand(&mut self, op: &Operand) { - if let Operand::Const(ConstValue::String(value)) = op { - self.label_for(value); + match op { + Operand::Const(ConstValue::String(value)) => { + self.label_for(value); + } + + Operand::Const(ConstValue::Double(value)) => { + self.label_for_double(*value); + } + Operand::Deref(inner) => self.visit_operand(inner), + _=> {} } } @@ -95,6 +106,18 @@ impl StringPool { self.labels.insert(value.to_string(), label.clone()); label } + + fn label_for_double(&mut self, value: f64) -> String{ + let key = value.to_string(); + if let Some(label) = self.double_labels.get(&key){ + return label.clone(); + } + + let label = format!(".LC_DBL{}", self.double_entries.len()); + self.double_entries.push((label.clone(), value)); + self.double_labels.insert(key, label.clone()); + label + } } impl Emitter { @@ -139,12 +162,19 @@ impl Emitter { pub fn emit_program(prog: &TacProgram) -> EmitResult { let strings = StringPool::collect(prog); let mut em = Emitter::new(); - if !strings.entries.is_empty() { + + if !strings.entries.is_empty() || !strings.double_entries.is_empty() { em.raw(".section .rodata"); + for (label, value) in &strings.entries { em.raw(&format!("{label}:")); em.raw(&format!(" .asciz {}", escape_asm_string(value))); } + + for (label, value) in &strings.double_entries{ + em.raw(&format!("{label}:")); + em.raw(&format!(" .double {value}")); + } em.blank(); } em.raw(".text"); @@ -320,23 +350,29 @@ fn emit_instr( then_label, else_label, } => { - load_op(em, frame, cond, "rax", strings)?; + load_op(em, frame, cond, "rax", strings, &Type::Int)?; em.insn("testq %rax, %rax"); em.insn(&format!("jne {}", local_label(func_name, then_label))); em.insn(&format!("jmp {}", local_label(func_name, else_label))); Ok(()) } - TacInstr::Copy { dst, src } => { - load_op(em, frame, src, "rax", strings)?; - store_op(em, frame, dst, "rax", strings)?; + TacInstr::Copy { dst, src, ty } => { + let reg = if matches!(ty, Type::Double) { "xmm0" } else { "rax" }; + load_op(em, frame, src, reg, strings, ty)?; + store_op(em, frame, dst, reg, strings, ty)?; Ok(()) } - TacInstr::BinOp { dst, op, lhs, rhs } => emit_binop(em, op, lhs, rhs, *dst, frame, strings), - TacInstr::UnOp { dst, op, src } => emit_unop(em, op, src, *dst, frame, strings), + TacInstr::BinOp { dst, op, lhs, rhs, ty } => emit_binop(em, op, lhs, rhs, *dst, frame, strings, ty), + TacInstr::UnOp { dst, op, src, ty } => emit_unop(em, op, src, *dst, frame, strings, ty), TacInstr::Call { dst, fn_name, args } => emit_call(em, fn_name, args, *dst, frame, strings), - TacInstr::Return { val } => { + TacInstr::Return { val, ty } => { if let Some(val) = val { - load_op(em, frame, val, "rax", strings)?; + // 1. Resolve o tipo (se for None, assume Int) + let resolved_ty = ty.as_ref().unwrap_or(&Type::Int); + // 2. Escolhe o registrador de retorno correto + let reg = if matches!(resolved_ty, Type::Double) { "xmm0" } else { "rax" }; + // 3. Carrega o valor para o registrador + load_op(em, frame, val, reg, strings, resolved_ty)?; } em.insn(&format!("jmp {epilogue_label}")); Ok(()) @@ -352,16 +388,44 @@ fn emit_binop( dst: crate::ir::tac::TempId, frame: &Frame, strings: &StringPool, + ty: &Type, // <-- Faltava isto ) -> EmitResult<()> { - // Operacoes logicas short-circuit-like precisam normalizar cada operando - // para 0/1 individualmente. if matches!(op, BinOp::And | BinOp::Or) { emit_logical(em, matches!(op, BinOp::Or), lhs, rhs, dst, frame, strings)?; return Ok(()); } - load_op(em, frame, lhs, "rax", strings)?; - load_op(em, frame, rhs, "rcx", strings)?; + if matches!(ty, Type::Double) { + load_op(em, frame, lhs, "xmm0", strings, ty)?; + load_op(em, frame, rhs, "xmm1", strings, ty)?; + + match op { + BinOp::Add => em.insn("addsd %xmm1, %xmm0"), + BinOp::Sub => em.insn("subsd %xmm1, %xmm0"), + BinOp::Mul => em.insn("mulsd %xmm1, %xmm0"), + BinOp::Div => em.insn("divsd %xmm1, %xmm0"), + BinOp::Less => emit_comparison_double(em, "seta"), + BinOp::Greater => emit_comparison_double(em, "setb"), + BinOp::Leq => emit_comparison_double(em, "setae"), + BinOp::Geq => emit_comparison_double(em, "setbe"), + BinOp::Eq => emit_comparison_double(em, "sete"), + BinOp::Neq => emit_comparison_double(em, "setne"), + _ => return Err(codegen_error("Operacao double nao suportada", Some("binop"))), + } + + let is_relational = matches!(op, BinOp::Less | BinOp::Greater | BinOp::Leq | BinOp::Geq | BinOp::Eq | BinOp::Neq); + if is_relational { + store_op(em, frame, &Operand::Temp(dst), "rax", strings, &Type::Int)?; + } else { + store_op(em, frame, &Operand::Temp(dst), "xmm0", strings, ty)?; + } + + return Ok(()); + } + + + load_op(em, frame, lhs, "rax", strings, ty)?; + load_op(em, frame, rhs, "rcx", strings, ty)?; match op { BinOp::Add => em.insn("addq %rcx, %rax"), @@ -387,15 +451,10 @@ fn emit_binop( BinOp::Geq => emit_comparison(em, "setge"), BinOp::Eq => emit_comparison(em, "sete"), BinOp::Neq => emit_comparison(em, "setne"), - BinOp::And | BinOp::Or => { - return Err(codegen_error( - "operacao logica deveria ter sido tratada antes", - Some("binop"), - )) - } + BinOp::And | BinOp::Or => unreachable!(), } - store_op(em, frame, &Operand::Temp(dst), "rax", strings)?; + store_op(em, frame, &Operand::Temp(dst), "rax", strings, ty)?; Ok(()) } @@ -405,6 +464,12 @@ fn emit_comparison(em: &mut Emitter, setcc: &str) { em.insn("movzbq %al, %rax"); } +fn emit_comparison_double(em: &mut Emitter, setcc: &str) { + em.insn("ucomisd %xmm1, %xmm0"); + em.insn(&format!("{setcc} %al")); + em.insn("movzbq %al, %rax"); +} + fn emit_logical( em: &mut Emitter, is_or: bool, @@ -414,15 +479,17 @@ fn emit_logical( frame: &Frame, strings: &StringPool, ) -> EmitResult<()> { + + let ty = &Type::Int; // Normaliza lhs para 0/1 em %rdx. - load_op(em, frame, lhs, "rax", strings)?; + load_op(em, frame, lhs, "rax", strings, ty)?; em.insn("testq %rax, %rax"); em.insn("setne %al"); em.insn("movzbq %al, %rax"); em.insn("movq %rax, %rdx"); // Normaliza rhs para 0/1 em %rax. - load_op(em, frame, rhs, "rax", strings)?; + load_op(em, frame, rhs, "rax", strings, ty)?; em.insn("testq %rax, %rax"); em.insn("setne %al"); em.insn("movzbq %al, %rax"); @@ -444,6 +511,7 @@ fn emit_unop( dst: crate::ir::tac::TempId, frame: &Frame, strings: &StringPool, + ty: &Type, ) -> EmitResult<()> { // `&x` precisa do *endereco* do slot de `src`, nao do seu valor: nao // passa por `load_op` (que faria `movq slot(%rbp), %reg`, carregando o @@ -462,8 +530,14 @@ fn emit_unop( store_op(em, frame, &Operand::Temp(dst), "rax", strings)?; return Ok(()); } + if matches!(ty, Type::Double) { + return Err(codegen_error( + "Operacoes unarias ainda nao suportadas para double", + Some("unop"), + )); + } - load_op(em, frame, src, "rax", strings)?; + load_op(em, frame, src, "rax", strings, ty)?; match op { UnOp::Neg => em.insn("negq %rax"), UnOp::BitNot => em.insn("notq %rax"), @@ -507,7 +581,7 @@ fn emit_call( for (index, arg) in args.iter().take(abi::MAX_REG_ARGS).enumerate() { let reg = abi::arg_register(index).expect("index < MAX_REG_ARGS sempre tem registrador"); - load_op(em, frame, arg, "rax", strings)?; + load_op(em, frame, arg, "rax", strings, &Type::Int)?; em.insn(&format!("movq %rax, %{reg}")); } @@ -519,7 +593,7 @@ fn emit_call( } if let Some(dst) = dst { - store_op(em, frame, &Operand::Temp(dst), "rax", strings)?; + store_op(em, frame, &Operand::Temp(dst), "rax", strings, &Type::Int)?; } Ok(()) } @@ -531,7 +605,9 @@ fn load_op( op: &Operand, reg: &str, strings: &StringPool, + ty: &Type, ) -> EmitResult<()> { + let mov_insn = if matches!(ty, Type::Double) {"movsd"} else {"movq"}; match op { Operand::Const(ConstValue::String(value)) => { let label = strings @@ -541,6 +617,15 @@ fn load_op( em.insn(&format!("leaq {label}(%rip), %{reg}")); Ok(()) } + Operand::Const(ConstValue::Double(value)) =>{ + let label = strings + .double_labels + .get(&value.to_string()) + .expect("double literal deve ter sido coletado"); + + em.insn(&format!("movsd {label}(%rip), %{reg}")); + Ok(()) + } Operand::Const(value) => { em.insn(&format!("movq ${}, %{reg}", const_immediate(value)?)); Ok(()) @@ -577,9 +662,11 @@ fn store_op( op: &Operand, reg: &str, strings: &StringPool, + ty: &Type, ) -> EmitResult<()> { + let mov_insn = if matches!(ty, Type::Double) {"movsd"} else {"movq"}; if let Operand::Deref(inner) = op { - load_op(em, frame, inner, DEREF_SCRATCH_REG, strings)?; + load_op(em, frame, inner, DEREF_SCRATCH_REG, strings, &Type::Int)?; em.insn(&format!("movq %{reg}, (%{DEREF_SCRATCH_REG})")); return Ok(()); } From 381a4dcc7b70db98e86540c9d468eb4c097d8485 Mon Sep 17 00:00:00 2001 From: Philipe Caetano Date: Wed, 24 Jun 2026 22:45:51 -0300 Subject: [PATCH 3/4] test(ir): adiciona suite de testes para tipagem no TAC (Issue #172) --- src/tests/tac_test.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/tests/tac_test.rs diff --git a/src/tests/tac_test.rs b/src/tests/tac_test.rs new file mode 100644 index 0000000..0a7a5b1 --- /dev/null +++ b/src/tests/tac_test.rs @@ -0,0 +1,34 @@ + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tac_instr_display_binop() { + let instr = TacInstr::BinOp { + dst: TempId(0), + op: BinOp::Add, + lhs: Operand::Temp(TempId(1)), + rhs: Operand::Temp(TempId(2)), + ty: Type::Int, + }; + + assert_eq!(instr.to_string(), "t0 = t1 + t2"); + } + + #[test] + fn temp_gen_increments() { + let mut gen = TempGen::new(); + + assert_eq!(gen.fresh(), TempId(0)); + assert_eq!(gen.fresh(), TempId(1)); + } + + #[test] + fn label_gen_unique() { + let mut gen = LabelGen::new(); + + assert_eq!(gen.fresh(), LabelId(0)); + assert_eq!(gen.fresh(), LabelId(1)); + } +} \ No newline at end of file From 30b6b5eab2da66cc36930bf7bf3cb6a71376feac Mon Sep 17 00:00:00 2001 From: Bappoz Date: Wed, 24 Jun 2026 23:14:42 -0300 Subject: [PATCH 4/4] fix(codegen): corrige e completa suporte a double iniciado na branch do colaborador MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A branch original (a4e4b25..381a4dc) nao compilava: o campo 'ty' foi adicionado a TacInstr::{BinOp,UnOp,Copy,Return} mas varios call sites em lower.rs, optimizations.rs, cfg.rs e nos modulos de teste nao foram atualizados, e load_op/store_op em x86_64.rs ganharam o parametro 'ty' sem que todos os chamadores fossem ajustados. Correcoes e trabalho completado: - lower.rs: tipo de cada expressao agora e inferido (literais, variaveis locais, aritmetica, cast) para popular 'ty' corretamente em vez de um valor fixo; cobre VarDecl, Assign, CompoundAssign, Ternary, prefix/postfix e Return. - x86_64.rs: load_op/store_op passam a usar 'movsd' de fato para double (antes computavam 'mov_insn' mas ignoravam a variavel); corrige bug de comparacao double com mapeamento de setcc invertido (Less/Greater e Leq/Geq trocados — 'a < b' retornava falso e vice-versa); literal double usado em contexto nao-double (ex.: 'float', fora de escopo) agora produz erro de codegen claro em vez de assembly invalido. - optimizations.rs/cfg.rs: matches exaustivos e construcoes ajustados para o novo campo. - registra src/tests/tac_test.rs (existia mas nao estava no mod.rs). - novo tests/double_codegen_test.rs: cobre o criterio de aceite da issue (double local + aritmetica + return, verificado via gcc), comparacoes double->int via exit code, e checagem do assembly emitido. Escopo mantido conforme sugerido na issue: apenas 'double' (nao 'float'), literais/variaveis locais/aritmetica basica/comparacoes/return. Argumentos, parametros e retorno de double atraves de chamada feita pelo proprio codegen permanecem fora de escopo (exigem estender abi.rs para xmm0..xmm7). cargo build --all, cargo test --all (357 testes), cargo clippy -- -D warnings e cargo fmt --check passam limpos. Refs #172 --- src/codegen/inter/optimizations.rs | 98 +++++++-- src/codegen/last/x86_64.rs | 124 +++++++---- src/ir/cfg.rs | 18 +- src/ir/lower.rs | 94 ++++++-- src/ir/tac.rs | 24 ++- src/tests/mod.rs | 1 + src/tests/tac_test.rs | 54 +++-- tests/codegen_smoke.rs | 17 ++ tests/double_codegen_test.rs | 332 +++++++++++++++++++++++++++++ 9 files changed, 651 insertions(+), 111 deletions(-) create mode 100644 tests/double_codegen_test.rs diff --git a/src/codegen/inter/optimizations.rs b/src/codegen/inter/optimizations.rs index b594733..b82b998 100644 --- a/src/codegen/inter/optimizations.rs +++ b/src/codegen/inter/optimizations.rs @@ -235,7 +235,7 @@ fn instr_uses(instr: &TacInstr) -> Vec { push(&mut uses, arg); } } - TacInstr::Return { val: Some(v) } => push(&mut uses, v), + TacInstr::Return { val: Some(v), .. } => push(&mut uses, v), _ => {} } @@ -255,20 +255,28 @@ pub fn constant_fold(instrs: &mut [TacInstr]) -> bool { for instr in instrs.iter_mut() { match instr { - TacInstr::BinOp { dst, op, lhs, rhs } => { + TacInstr::BinOp { + dst, + op, + lhs, + rhs, + ty, + } => { if let Some(result) = fold_binop(op, lhs, rhs) { *instr = TacInstr::Copy { dst: Operand::Temp(*dst), src: Operand::Const(result), + ty: ty.clone(), }; changed = true; } } - TacInstr::UnOp { dst, op, src } => { + TacInstr::UnOp { dst, op, src, ty } => { if let Some(result) = fold_unop(op, src) { *instr = TacInstr::Copy { dst: Operand::Temp(*dst), src: Operand::Const(result), + ty: ty.clone(), }; changed = true; } @@ -382,6 +390,7 @@ pub fn constant_propagation(instrs: &mut [TacInstr]) -> bool { TacInstr::Copy { dst: Operand::Temp(t), src: Operand::Const(v), + .. } => { const_map.insert(*t, v.clone()); } @@ -390,6 +399,7 @@ pub fn constant_propagation(instrs: &mut [TacInstr]) -> bool { TacInstr::Copy { dst: Operand::Temp(t), src: _, + .. } => { const_map.remove(t); } @@ -434,7 +444,7 @@ fn propagate_uses( subst(arg, changed); } } - TacInstr::Return { val: Some(v) } => subst(v, changed), + TacInstr::Return { val: Some(v), .. } => subst(v, changed), _ => {} } } @@ -526,6 +536,7 @@ pub fn optimize_function(instrs: &mut Vec) { mod tests { use super::*; use crate::{ + common::ast::ast::Type, common::ast::expr::{BinOp, UnOp}, ir::tac::{ConstValue, LabelId, Operand, TacInstr, TempId}, }; @@ -551,6 +562,7 @@ mod tests { op: BinOp::Add, lhs: int(2), rhs: int(3), + ty: Type::Int, }]; assert!(constant_fold(&mut instrs)); assert_eq!( @@ -558,6 +570,7 @@ mod tests { TacInstr::Copy { dst: temp(0), src: int(5), + ty: Type::Int, } ); } @@ -571,12 +584,14 @@ mod tests { op: BinOp::Mul, lhs: int(3), rhs: int(4), + ty: Type::Int, }, TacInstr::BinOp { dst: TempId(1), op: BinOp::Add, lhs: int(2), rhs: temp(0), + ty: Type::Int, }, ]; // Após fold: t0 = 12, t1 = 2 + t0 (t0 ainda é temp, precisa de propagation) @@ -585,7 +600,8 @@ mod tests { instrs[0], TacInstr::Copy { dst: temp(0), - src: int(12) + src: int(12), + ty: Type::Int, } ); @@ -597,7 +613,8 @@ mod tests { instrs[1], TacInstr::Copy { dst: temp(1), - src: int(14) + src: int(14), + ty: Type::Int, } ); } @@ -609,13 +626,15 @@ mod tests { op: BinOp::Less, lhs: int(3), rhs: int(5), + ty: Type::Int, }]; assert!(constant_fold(&mut instrs)); assert_eq!( instrs[0], TacInstr::Copy { dst: temp(0), - src: int(1) + src: int(1), + ty: Type::Int, } ); } @@ -627,13 +646,15 @@ mod tests { op: BinOp::Eq, lhs: int(3), rhs: int(5), + ty: Type::Int, }]; assert!(constant_fold(&mut instrs)); assert_eq!( instrs[0], TacInstr::Copy { dst: temp(0), - src: int(0) + src: int(0), + ty: Type::Int, } ); } @@ -646,13 +667,15 @@ mod tests { op: BinOp::BitAnd, lhs: int(0b1010), rhs: int(0b1100), + ty: Type::Int, }]; assert!(constant_fold(&mut instrs)); assert_eq!( instrs[0], TacInstr::Copy { dst: temp(0), - src: int(8) + src: int(8), + ty: Type::Int, } ); } @@ -663,13 +686,15 @@ mod tests { dst: TempId(0), op: UnOp::Neg, src: int(7), + ty: Type::Int, }]; assert!(constant_fold(&mut instrs)); assert_eq!( instrs[0], TacInstr::Copy { dst: temp(0), - src: int(-7) + src: int(-7), + ty: Type::Int, } ); } @@ -682,11 +707,13 @@ mod tests { dst: TempId(0), op: UnOp::Not, src: int(0), + ty: Type::Int, }, TacInstr::UnOp { dst: TempId(1), op: UnOp::Not, src: int(5), + ty: Type::Int, }, ]; constant_fold(&mut instrs); @@ -694,14 +721,16 @@ mod tests { instrs[0], TacInstr::Copy { dst: temp(0), - src: int(1) + src: int(1), + ty: Type::Int, } ); assert_eq!( instrs[1], TacInstr::Copy { dst: temp(1), - src: int(0) + src: int(0), + ty: Type::Int, } ); } @@ -713,6 +742,7 @@ mod tests { op: BinOp::Div, lhs: int(10), rhs: int(0), + ty: Type::Int, }; let mut instrs = vec![original.clone()]; assert!(!constant_fold(&mut instrs)); @@ -726,6 +756,7 @@ mod tests { op: BinOp::Shl, lhs: int(1), rhs: int(-1), + ty: Type::Int, }; let mut instrs = vec![original.clone()]; assert!(!constant_fold(&mut instrs)); @@ -739,6 +770,7 @@ mod tests { op: BinOp::Shl, lhs: int(1), rhs: int(64), + ty: Type::Int, }; let mut instrs = vec![original.clone()]; assert!(!constant_fold(&mut instrs)); @@ -752,6 +784,7 @@ mod tests { op: BinOp::Add, lhs: Operand::Const(ConstValue::Double(1.0)), rhs: Operand::Const(ConstValue::Double(2.0)), + ty: Type::Int, }; let mut instrs = vec![original.clone()]; assert!(!constant_fold(&mut instrs)); @@ -767,12 +800,14 @@ mod tests { TacInstr::Copy { dst: temp(0), src: int(5), + ty: Type::Int, }, TacInstr::BinOp { dst: TempId(1), op: BinOp::Add, lhs: temp(0), rhs: int(3), + ty: Type::Int, }, ]; assert!(constant_propagation(&mut instrs)); @@ -783,6 +818,7 @@ mod tests { op: BinOp::Add, lhs: int(5), rhs: int(3), + ty: Type::Int, } ); // Após fold: t1 = 8 @@ -791,7 +827,8 @@ mod tests { instrs[1], TacInstr::Copy { dst: temp(1), - src: int(8) + src: int(8), + ty: Type::Int, } ); } @@ -803,6 +840,7 @@ mod tests { TacInstr::Copy { dst: temp(0), src: int(5), + ty: Type::Int, }, TacInstr::Call { dst: Some(TempId(0)), @@ -814,6 +852,7 @@ mod tests { op: BinOp::Add, lhs: temp(0), rhs: int(1), + ty: Type::Int, }, ]; // propagation não deve substituir t0 na última instrução @@ -837,6 +876,7 @@ mod tests { op: BinOp::Add, lhs: int(2), rhs: int(3), + ty: Type::Int, }]; let liveness = compute_liveness(&instrs); assert!(dead_code_eliminate(&mut instrs, &liveness)); @@ -862,6 +902,7 @@ mod tests { let mut instrs = vec![TacInstr::Copy { dst: var("x"), src: int(10), + ty: Type::Int, }]; let liveness = compute_liveness(&instrs); assert!(!dead_code_eliminate(&mut instrs, &liveness)); @@ -875,8 +916,12 @@ mod tests { TacInstr::Copy { dst: temp(0), src: int(5), + ty: Type::Int, + }, + TacInstr::Return { + val: Some(temp(0)), + ty: None, }, - TacInstr::Return { val: Some(temp(0)) }, ]; let liveness = compute_liveness(&instrs); assert!(!dead_code_eliminate(&mut instrs, &liveness)); @@ -903,15 +948,20 @@ mod tests { TacInstr::Copy { dst: temp(0), src: int(5), + ty: Type::Int, }, TacInstr::Jump { label: LabelId(3) }, TacInstr::Label(LabelId(2)), TacInstr::Copy { dst: temp(0), src: int(10), + ty: Type::Int, }, TacInstr::Label(LabelId(3)), - TacInstr::Return { val: Some(temp(0)) }, + TacInstr::Return { + val: Some(temp(0)), + ty: None, + }, ]; let liveness = compute_liveness(&instrs); @@ -933,16 +983,19 @@ mod tests { op: BinOp::Mul, lhs: int(3), rhs: int(4), + ty: Type::Int, }, TacInstr::BinOp { dst: TempId(1), op: BinOp::Add, lhs: int(2), rhs: temp(0), + ty: Type::Int, }, TacInstr::Copy { dst: var("x"), src: temp(1), + ty: Type::Int, }, ]; @@ -955,6 +1008,7 @@ mod tests { TacInstr::Copy { dst: var("x"), src: int(14), + ty: Type::Int, } ); } @@ -974,8 +1028,12 @@ mod tests { op: BinOp::Add, lhs: temp(0), rhs: int(0), + ty: Type::Int, + }, + TacInstr::Return { + val: Some(temp(1)), + ty: None, }, - TacInstr::Return { val: Some(temp(1)) }, ]; optimize_function(&mut instrs); @@ -988,7 +1046,13 @@ mod tests { #[test] fn optimize_function_side_effect_label_preserved() { - let mut instrs = vec![TacInstr::Label(LabelId(0)), TacInstr::Return { val: None }]; + let mut instrs = vec![ + TacInstr::Label(LabelId(0)), + TacInstr::Return { + val: None, + ty: None, + }, + ]; optimize_function(&mut instrs); assert_eq!(instrs.len(), 2); } diff --git a/src/codegen/last/x86_64.rs b/src/codegen/last/x86_64.rs index 42fbcaa..5edb4b7 100644 --- a/src/codegen/last/x86_64.rs +++ b/src/codegen/last/x86_64.rs @@ -17,11 +17,11 @@ use crate::codegen::last::abi; use crate::codegen::last::frame::{Frame, SlotKey}; use crate::codegen::last::peephole::PeepholePass; +use crate::common::ast::ast::Type; use crate::common::ast::expr::{BinOp, UnOp}; use crate::common::errors::types::CodegenError; use crate::ir::tac::{ConstValue, LabelId, Operand, TacFunction, TacInstr, TacProgram}; use std::collections::HashMap; -use crate::common::ast::ast::Type; type EmitResult = Result; @@ -63,7 +63,7 @@ impl StringPool { self.visit_operand(rhs); } TacInstr::UnOp { src, .. } => self.visit_operand(src), - TacInstr::Copy { dst, src } => { + TacInstr::Copy { dst, src, .. } => { self.visit_operand(dst); self.visit_operand(src); } @@ -73,7 +73,7 @@ impl StringPool { self.visit_operand(arg); } } - TacInstr::Return { val } => { + TacInstr::Return { val, .. } => { if let Some(val) = val { self.visit_operand(val); } @@ -92,7 +92,7 @@ impl StringPool { self.label_for_double(*value); } Operand::Deref(inner) => self.visit_operand(inner), - _=> {} + _ => {} } } @@ -107,9 +107,9 @@ impl StringPool { label } - fn label_for_double(&mut self, value: f64) -> String{ + fn label_for_double(&mut self, value: f64) -> String { let key = value.to_string(); - if let Some(label) = self.double_labels.get(&key){ + if let Some(label) = self.double_labels.get(&key) { return label.clone(); } @@ -171,7 +171,7 @@ pub fn emit_program(prog: &TacProgram) -> EmitResult { em.raw(&format!(" .asciz {}", escape_asm_string(value))); } - for (label, value) in &strings.double_entries{ + for (label, value) in &strings.double_entries { em.raw(&format!("{label}:")); em.raw(&format!(" .double {value}")); } @@ -304,7 +304,7 @@ fn slot_keys_of(instr: &TacInstr) -> Vec { keys.push(SlotKey::Temp(dst.0)); consider(&mut keys, src); } - TacInstr::Copy { dst, src } => { + TacInstr::Copy { dst, src, .. } => { consider(&mut keys, dst); consider(&mut keys, src); } @@ -317,7 +317,7 @@ fn slot_keys_of(instr: &TacInstr) -> Vec { consider(&mut keys, arg); } } - TacInstr::Return { val } => { + TacInstr::Return { val, .. } => { if let Some(val) = val { consider(&mut keys, val); } @@ -357,12 +357,22 @@ fn emit_instr( Ok(()) } TacInstr::Copy { dst, src, ty } => { - let reg = if matches!(ty, Type::Double) { "xmm0" } else { "rax" }; + let reg = if matches!(ty, Type::Double) { + "xmm0" + } else { + "rax" + }; load_op(em, frame, src, reg, strings, ty)?; store_op(em, frame, dst, reg, strings, ty)?; Ok(()) } - TacInstr::BinOp { dst, op, lhs, rhs, ty } => emit_binop(em, op, lhs, rhs, *dst, frame, strings, ty), + TacInstr::BinOp { + dst, + op, + lhs, + rhs, + ty, + } => emit_binop(em, op, lhs, rhs, *dst, frame, strings, ty), TacInstr::UnOp { dst, op, src, ty } => emit_unop(em, op, src, *dst, frame, strings, ty), TacInstr::Call { dst, fn_name, args } => emit_call(em, fn_name, args, *dst, frame, strings), TacInstr::Return { val, ty } => { @@ -370,7 +380,11 @@ fn emit_instr( // 1. Resolve o tipo (se for None, assume Int) let resolved_ty = ty.as_ref().unwrap_or(&Type::Int); // 2. Escolhe o registrador de retorno correto - let reg = if matches!(resolved_ty, Type::Double) { "xmm0" } else { "rax" }; + let reg = if matches!(resolved_ty, Type::Double) { + "xmm0" + } else { + "rax" + }; // 3. Carrega o valor para o registrador load_op(em, frame, val, reg, strings, resolved_ty)?; } @@ -380,6 +394,7 @@ fn emit_instr( } } +#[allow(clippy::too_many_arguments)] fn emit_binop( em: &mut Emitter, op: &BinOp, @@ -388,7 +403,7 @@ fn emit_binop( dst: crate::ir::tac::TempId, frame: &Frame, strings: &StringPool, - ty: &Type, // <-- Faltava isto + ty: &Type, ) -> EmitResult<()> { if matches!(op, BinOp::And | BinOp::Or) { emit_logical(em, matches!(op, BinOp::Or), lhs, rhs, dst, frame, strings)?; @@ -404,26 +419,33 @@ fn emit_binop( BinOp::Sub => em.insn("subsd %xmm1, %xmm0"), BinOp::Mul => em.insn("mulsd %xmm1, %xmm0"), BinOp::Div => em.insn("divsd %xmm1, %xmm0"), - BinOp::Less => emit_comparison_double(em, "seta"), - BinOp::Greater => emit_comparison_double(em, "setb"), - BinOp::Leq => emit_comparison_double(em, "setae"), - BinOp::Geq => emit_comparison_double(em, "setbe"), + BinOp::Less => emit_comparison_double(em, "setb"), + BinOp::Greater => emit_comparison_double(em, "seta"), + BinOp::Leq => emit_comparison_double(em, "setbe"), + BinOp::Geq => emit_comparison_double(em, "setae"), BinOp::Eq => emit_comparison_double(em, "sete"), BinOp::Neq => emit_comparison_double(em, "setne"), - _ => return Err(codegen_error("Operacao double nao suportada", Some("binop"))), + _ => { + return Err(codegen_error( + "Operacao double nao suportada", + Some("binop"), + )) + } } - - let is_relational = matches!(op, BinOp::Less | BinOp::Greater | BinOp::Leq | BinOp::Geq | BinOp::Eq | BinOp::Neq); + + let is_relational = matches!( + op, + BinOp::Less | BinOp::Greater | BinOp::Leq | BinOp::Geq | BinOp::Eq | BinOp::Neq + ); if is_relational { store_op(em, frame, &Operand::Temp(dst), "rax", strings, &Type::Int)?; } else { store_op(em, frame, &Operand::Temp(dst), "xmm0", strings, ty)?; } - + return Ok(()); } - load_op(em, frame, lhs, "rax", strings, ty)?; load_op(em, frame, rhs, "rcx", strings, ty)?; @@ -479,7 +501,6 @@ fn emit_logical( frame: &Frame, strings: &StringPool, ) -> EmitResult<()> { - let ty = &Type::Int; // Normaliza lhs para 0/1 em %rdx. load_op(em, frame, lhs, "rax", strings, ty)?; @@ -500,7 +521,7 @@ fn emit_logical( em.insn("andq %rdx, %rax"); } - store_op(em, frame, &Operand::Temp(dst), "rax", strings)?; + store_op(em, frame, &Operand::Temp(dst), "rax", strings, ty)?; Ok(()) } @@ -527,7 +548,7 @@ fn emit_unop( .offset_of(&key) .expect("operando de & deve ter slot alocado no frame"); em.insn(&format!("leaq {offset}(%rbp), %rax")); - store_op(em, frame, &Operand::Temp(dst), "rax", strings)?; + store_op(em, frame, &Operand::Temp(dst), "rax", strings, &Type::Int)?; return Ok(()); } if matches!(ty, Type::Double) { @@ -551,7 +572,7 @@ fn emit_unop( } UnOp::AddrOf => unreachable!("tratado antes do load_op acima"), } - store_op(em, frame, &Operand::Temp(dst), "rax", strings)?; + store_op(em, frame, &Operand::Temp(dst), "rax", strings, ty)?; Ok(()) } @@ -575,7 +596,7 @@ fn emit_call( } let stack_args = &args[args.len().min(abi::MAX_REG_ARGS)..]; for arg in stack_args.iter().rev() { - load_op(em, frame, arg, "rax", strings)?; + load_op(em, frame, arg, "rax", strings, &Type::Int)?; em.insn("pushq %rax"); } @@ -607,7 +628,11 @@ fn load_op( strings: &StringPool, ty: &Type, ) -> EmitResult<()> { - let mov_insn = if matches!(ty, Type::Double) {"movsd"} else {"movq"}; + let mov_insn = if matches!(ty, Type::Double) { + "movsd" + } else { + "movq" + }; match op { Operand::Const(ConstValue::String(value)) => { let label = strings @@ -617,14 +642,18 @@ fn load_op( em.insn(&format!("leaq {label}(%rip), %{reg}")); Ok(()) } - Operand::Const(ConstValue::Double(value)) =>{ + Operand::Const(ConstValue::Double(_)) if !matches!(ty, Type::Double) => Err(codegen_error( + "literal double usado em contexto nao-double (apenas double e' suportado neste backend; float ainda nao)", + Some("load"), + )), + Operand::Const(ConstValue::Double(value)) => { let label = strings .double_labels .get(&value.to_string()) .expect("double literal deve ter sido coletado"); - em.insn(&format!("movsd {label}(%rip), %{reg}")); - Ok(()) + em.insn(&format!("movsd {label}(%rip), %{reg}")); + Ok(()) } Operand::Const(value) => { em.insn(&format!("movq ${}, %{reg}", const_immediate(value)?)); @@ -634,22 +663,24 @@ fn load_op( let offset = frame .offset_of(&SlotKey::Temp(temp.0)) .expect("temp sem slot alocado"); - em.insn(&format!("movq {offset}(%rbp), %{reg}")); + em.insn(&format!("{mov_insn} {offset}(%rbp), %{reg}")); Ok(()) } Operand::Var(name) => { let offset = frame .offset_of(&SlotKey::Var(name.clone())) .expect("var sem slot alocado"); - em.insn(&format!("movq {offset}(%rbp), %{reg}")); + em.insn(&format!("{mov_insn} {offset}(%rbp), %{reg}")); Ok(()) } Operand::Deref(inner) => { // `%r11` e scratch/caller-saved e nao e usado como `reg` por // nenhum chamador de `load_op`/`store_op` neste backend, entao e // seguro usa-lo aqui para materializar o ponteiro antes do deref. - load_op(em, frame, inner, DEREF_SCRATCH_REG, strings)?; - em.insn(&format!("movq (%{DEREF_SCRATCH_REG}), %{reg}")); + // O proprio ponteiro e sempre um endereco de 8 bytes, independente + // do tipo do valor apontado. + load_op(em, frame, inner, DEREF_SCRATCH_REG, strings, &Type::Int)?; + em.insn(&format!("{mov_insn} (%{DEREF_SCRATCH_REG}), %{reg}")); Ok(()) } } @@ -664,10 +695,14 @@ fn store_op( strings: &StringPool, ty: &Type, ) -> EmitResult<()> { - let mov_insn = if matches!(ty, Type::Double) {"movsd"} else {"movq"}; + let mov_insn = if matches!(ty, Type::Double) { + "movsd" + } else { + "movq" + }; if let Operand::Deref(inner) = op { load_op(em, frame, inner, DEREF_SCRATCH_REG, strings, &Type::Int)?; - em.insn(&format!("movq %{reg}, (%{DEREF_SCRATCH_REG})")); + em.insn(&format!("{mov_insn} %{reg}, (%{DEREF_SCRATCH_REG})")); return Ok(()); } @@ -686,7 +721,7 @@ fn store_op( } Operand::Deref(_) => unreachable!("tratado antes do match acima"), }; - em.insn(&format!("movq %{reg}, {offset}(%rbp)")); + em.insn(&format!("{mov_insn} %{reg}, {offset}(%rbp)")); Ok(()) } @@ -751,6 +786,7 @@ mod tests { Vec::new(), vec![TacInstr::Return { val: Some(Operand::Const(ConstValue::Int(42))), + ty: None, }], ) } @@ -790,9 +826,11 @@ mod tests { op: BinOp::Add, lhs: Operand::Var("a".to_string()), rhs: Operand::Var("b".to_string()), + ty: Type::Int, }, TacInstr::Return { val: Some(Operand::Temp(TempId(0))), + ty: None, }, ], ); @@ -815,9 +853,11 @@ mod tests { op: BinOp::Div, lhs: Operand::Const(ConstValue::Int(10)), rhs: Operand::Const(ConstValue::Int(3)), + ty: Type::Int, }, TacInstr::Return { val: Some(Operand::Temp(TempId(0))), + ty: None, }, ], ); @@ -838,6 +878,7 @@ mod tests { op: BinOp::Mod, lhs: Operand::Const(ConstValue::Int(10)), rhs: Operand::Const(ConstValue::Int(3)), + ty: Type::Int, }], ); @@ -856,6 +897,7 @@ mod tests { op: BinOp::Less, lhs: Operand::Const(ConstValue::Int(1)), rhs: Operand::Const(ConstValue::Int(2)), + ty: Type::Int, }], ); @@ -882,6 +924,7 @@ mod tests { }, TacInstr::Return { val: Some(Operand::Temp(TempId(0))), + ty: None, }, ], ); @@ -909,6 +952,7 @@ mod tests { }, TacInstr::Return { val: Some(Operand::Temp(TempId(0))), + ty: None, }, ], ); @@ -979,10 +1023,12 @@ mod tests { TacInstr::Label(LabelId(0)), TacInstr::Return { val: Some(Operand::Const(ConstValue::Int(1))), + ty: None, }, TacInstr::Label(LabelId(1)), TacInstr::Return { val: Some(Operand::Const(ConstValue::Int(0))), + ty: None, }, ], ); @@ -1025,6 +1071,7 @@ mod tests { op: BinOp::And, lhs: Operand::Const(ConstValue::Int(1)), rhs: Operand::Const(ConstValue::Int(0)), + ty: Type::Int, }], ); @@ -1044,6 +1091,7 @@ mod tests { op: BinOp::Or, lhs: Operand::Const(ConstValue::Int(1)), rhs: Operand::Const(ConstValue::Int(0)), + ty: Type::Int, }], ); diff --git a/src/ir/cfg.rs b/src/ir/cfg.rs index 34a236e..245867f 100644 --- a/src/ir/cfg.rs +++ b/src/ir/cfg.rs @@ -168,6 +168,7 @@ pub fn build_cfg(func: &TacFunction) -> Cfg { #[cfg(test)] mod tests { use super::*; + use crate::common::ast::ast::Type; use crate::ir::tac::{ConstValue, Operand, TempId}; fn func(instrs: Vec) -> TacFunction { @@ -185,13 +186,16 @@ mod tests { TacInstr::Copy { dst: Operand::Temp(TempId(0)), src: Operand::Const(ConstValue::Int(1)), + ty: Type::Int, }, TacInstr::Copy { dst: Operand::Temp(TempId(1)), src: Operand::Const(ConstValue::Int(2)), + ty: Type::Int, }, TacInstr::Return { val: Some(Operand::Temp(TempId(1))), + ty: None, }, ]); @@ -217,16 +221,19 @@ mod tests { TacInstr::Copy { dst: Operand::Temp(TempId(0)), src: Operand::Const(ConstValue::Int(1)), + ty: Type::Int, }, TacInstr::Jump { label: merge_label }, TacInstr::Label(else_label), TacInstr::Copy { dst: Operand::Temp(TempId(0)), src: Operand::Const(ConstValue::Int(2)), + ty: Type::Int, }, TacInstr::Label(merge_label), TacInstr::Return { val: Some(Operand::Temp(TempId(0))), + ty: None, }, ]); @@ -253,10 +260,14 @@ mod tests { TacInstr::Copy { dst: Operand::Temp(TempId(0)), src: Operand::Const(ConstValue::Int(1)), + ty: Type::Int, }, TacInstr::Jump { label: cond_label }, TacInstr::Label(exit_label), - TacInstr::Return { val: None }, + TacInstr::Return { + val: None, + ty: None, + }, ]); let cfg = build_cfg(&f); @@ -279,7 +290,10 @@ mod tests { #[test] fn cfg_entry_has_no_predecessors() { - let f = func(vec![TacInstr::Return { val: None }]); + let f = func(vec![TacInstr::Return { + val: None, + ty: None, + }]); let cfg = build_cfg(&f); diff --git a/src/ir/lower.rs b/src/ir/lower.rs index 9179775..3df948b 100644 --- a/src/ir/lower.rs +++ b/src/ir/lower.rs @@ -96,6 +96,35 @@ impl Lowerer { sizes } + /// Infere se uma expressao produz um valor `double`, o suficiente para + /// escolher entre o caminho de codegen inteiro (`rax`/`rcx`) e o de + /// ponto flutuante (`xmm0`/`xmm1`) sem precisar reexecutar a analise + /// semantica completa. Qualquer expressao fora deste subconjunto e + /// tratada como inteira — escopo deliberadamente limitado a literais, + /// variaveis locais, aritmetica basica e cast, conforme a issue #172. + fn expr_type_for_codegen(&self, expr: &Expr) -> Type { + match expr { + Expr::Literal(Literal::Double(_), _) => Type::Double, + Expr::Ident(name, _) => self + .var_types + .get(name) + .map(|ty| resolve_alias(ty, &self.typedefs)) + .unwrap_or(Type::Int), + Expr::Binary(lhs, _, rhs, _) => { + if matches!(self.expr_type_for_codegen(lhs), Type::Double) + || matches!(self.expr_type_for_codegen(rhs), Type::Double) + { + Type::Double + } else { + Type::Int + } + } + Expr::Unary(_, inner, _) => self.expr_type_for_codegen(inner), + Expr::Cast(qty, _, _) => resolve_alias(&qty.ty, &self.typedefs), + _ => Type::Int, + } + } + /// Infere o tipo estatico (ja resolvido de aliases de `typedef`) de um /// subconjunto limitado de expressoes (identificadores, deref, indice /// via ponteiro, membro de struct e cast) — o suficiente para resolver @@ -227,6 +256,7 @@ impl Lowerer { op: BinOp::Mul, lhs: idx_op, rhs: Operand::Const(ConstValue::Int(elem_size)), + ty: Type::Int, }); Operand::Temp(scaled) }; @@ -237,6 +267,7 @@ impl Lowerer { op: BinOp::Add, lhs: base_ptr, rhs: offset, + ty: Type::Int, }); Ok(Operand::Temp(addr)) } @@ -276,6 +307,7 @@ impl Lowerer { op: BinOp::Add, lhs: base_addr, rhs: Operand::Const(ConstValue::Int(field_offset)), + ty: Type::Int, }); Ok(Operand::Temp(addr)) } @@ -291,6 +323,7 @@ impl Lowerer { dst: temp, op: UnOp::AddrOf, src: Operand::Var(name.clone()), + ty: Type::Int, }); Ok(Operand::Temp(temp)) } @@ -308,25 +341,29 @@ impl Lowerer { match expr { Expr::Literal(value, _) => Ok(Operand::Const(lower_literal(value))), Expr::Ident(name, _) => Ok(Operand::Var(name.clone())), - Expr::Binary(lhs, op, rhs, _) => { - let lhs = self.lower_expr(lhs)?; - let rhs = self.lower_expr(rhs)?; + Expr::Binary(lhs_expr, op, rhs_expr, _) => { + let ty = self.expr_type_for_codegen(expr); + let lhs = self.lower_expr(lhs_expr)?; + let rhs = self.lower_expr(rhs_expr)?; let dst = self.fresh_temp(); self.instrs.push(TacInstr::BinOp { dst, op: op.clone(), lhs, rhs, + ty, }); Ok(Operand::Temp(dst)) } - Expr::Unary(op, src, _) => { - let src = self.lower_expr(src)?; + Expr::Unary(op, src_expr, _) => { + let ty = self.expr_type_for_codegen(src_expr); + let src = self.lower_expr(src_expr)?; let dst = self.fresh_temp(); self.instrs.push(TacInstr::UnOp { dst, op: op.clone(), src, + ty, }); Ok(Operand::Temp(dst)) } @@ -354,14 +391,19 @@ impl Lowerer { }); Ok(Operand::Temp(dst)) } - Expr::Cast(_, inner, _) => self.lower_expr(inner), + Expr::Cast(qty, inner, _) => { + let _ = resolve_alias(&qty.ty, &self.typedefs); + self.lower_expr(inner) + } Expr::Assign(lhs, rhs, _) => { + let ty = self.expr_type_for_codegen(lhs); let src = self.lower_expr(rhs)?; let dst = self.lower_assignment_target(lhs)?; - self.emit_copy(dst.clone(), src)?; + self.emit_copy(dst.clone(), src, ty)?; Ok(dst) } Expr::CompoundAssign(op, lhs, rhs, _) => { + let ty = self.expr_type_for_codegen(lhs); let dst = self.lower_assignment_target(lhs)?; let rhs = self.lower_expr(rhs)?; let temp = self.fresh_temp(); @@ -370,8 +412,9 @@ impl Lowerer { op: op.clone(), lhs: dst.clone(), rhs, + ty: ty.clone(), }); - self.emit_copy(dst.clone(), Operand::Temp(temp))?; + self.emit_copy(dst.clone(), Operand::Temp(temp), ty)?; Ok(dst) } Expr::SizeofType(qty, _) => Ok(Operand::Const(ConstValue::Int(type_size(&qty.ty)?))), @@ -389,13 +432,15 @@ impl Lowerer { }); self.instrs.push(TacInstr::Label(then_label)); + let then_ty = self.expr_type_for_codegen(then_expr); let then_val = self.lower_expr(then_expr)?; - self.emit_copy(Operand::Temp(dst), then_val)?; + self.emit_copy(Operand::Temp(dst), then_val, then_ty)?; self.emit_jump_unless_terminated(end_label); self.instrs.push(TacInstr::Label(else_label)); + let else_ty = self.expr_type_for_codegen(else_expr); let else_val = self.lower_expr(else_expr)?; - self.emit_copy(Operand::Temp(dst), else_val)?; + self.emit_copy(Operand::Temp(dst), else_val, else_ty)?; self.instrs.push(TacInstr::Label(end_label)); Ok(Operand::Temp(dst)) @@ -577,18 +622,20 @@ impl Lowerer { Ok(()) } Stmt::Return(expr, _) => { + let ty = expr.as_ref().map(|expr| self.expr_type_for_codegen(expr)); let val = expr .as_ref() .map(|expr| self.lower_expr(expr)) .transpose()?; - self.instrs.push(TacInstr::Return { val }); + self.instrs.push(TacInstr::Return { val, ty }); Ok(()) } Stmt::VarDecl(qty, name, init, _) => { self.declare_var_type(name, &qty.ty); if let Some(init) = init { + let ty = resolve_alias(&qty.ty, &self.typedefs); let src = self.lower_expr(init)?; - self.emit_copy(Operand::Var(name.clone()), src)?; + self.emit_copy(Operand::Var(name.clone()), src, ty)?; } Ok(()) } @@ -615,6 +662,7 @@ impl Lowerer { op: BinOp::Eq, lhs: disc_op.clone(), rhs: case_val, + ty: Type::Int, }); let next_test = self.labels.fresh(); self.instrs.push(TacInstr::CondJump { @@ -660,6 +708,7 @@ impl Lowerer { } fn lower_prefix(&mut self, op: &PrefixOp, target: &Expr) -> LowerResult { + let ty = self.expr_type_for_codegen(target); let dst = self.lower_assignment_target(target)?; let temp = self.fresh_temp(); self.instrs.push(TacInstr::BinOp { @@ -667,15 +716,17 @@ impl Lowerer { op: prefix_bin_op(op), lhs: dst.clone(), rhs: Operand::Const(ConstValue::Int(1)), + ty: ty.clone(), }); - self.emit_copy(dst.clone(), Operand::Temp(temp))?; + self.emit_copy(dst.clone(), Operand::Temp(temp), ty)?; Ok(dst) } fn lower_postfix(&mut self, op: &PostfixOp, target: &Expr) -> LowerResult { + let ty = self.expr_type_for_codegen(target); let dst = self.lower_assignment_target(target)?; let old = self.fresh_temp(); - self.emit_copy(Operand::Temp(old), dst.clone())?; + self.emit_copy(Operand::Temp(old), dst.clone(), ty.clone())?; let new = self.fresh_temp(); self.instrs.push(TacInstr::BinOp { @@ -683,8 +734,9 @@ impl Lowerer { op: postfix_bin_op(op), lhs: dst.clone(), rhs: Operand::Const(ConstValue::Int(1)), + ty: ty.clone(), }); - self.emit_copy(dst, Operand::Temp(new))?; + self.emit_copy(dst, Operand::Temp(new), ty)?; Ok(Operand::Temp(old)) } @@ -741,10 +793,10 @@ impl Lowerer { } } - fn emit_copy(&mut self, dst: Operand, src: Operand) -> LowerResult<()> { + fn emit_copy(&mut self, dst: Operand, src: Operand, ty: Type) -> LowerResult<()> { match dst { Operand::Temp(_) | Operand::Var(_) | Operand::Deref(_) => { - self.instrs.push(TacInstr::Copy { dst, src }); + self.instrs.push(TacInstr::Copy { dst, src, ty }); Ok(()) } Operand::Const(_) => Err(codegen_error( @@ -1050,6 +1102,7 @@ mod tests { op: BinOp::Add, lhs: Operand::Const(ConstValue::Int(2)), rhs: Operand::Const(ConstValue::Int(3)), + ty: Type::Int, }] ); } @@ -1091,6 +1144,7 @@ mod tests { TacInstr::Copy { dst: Operand::Var("x".to_string()), src: Operand::Const(ConstValue::Int(2)), + ty: Type::Int, } ); assert_eq!(instrs[3], TacInstr::Jump { label: LabelId(2) }); @@ -1100,6 +1154,7 @@ mod tests { TacInstr::Copy { dst: Operand::Var("y".to_string()), src: Operand::Const(ConstValue::Int(3)), + ty: Type::Int, } ); assert_eq!(instrs[6], TacInstr::Label(LabelId(2))); @@ -1173,12 +1228,14 @@ mod tests { op: BinOp::Mul, lhs: Operand::Const(ConstValue::Int(3)), rhs: Operand::Const(ConstValue::Int(4)), + ty: Type::Int, }, TacInstr::BinOp { dst: TempId(1), op: BinOp::Add, lhs: Operand::Const(ConstValue::Int(2)), rhs: Operand::Temp(TempId(0)), + ty: Type::Int, }, ] ); @@ -1201,7 +1258,8 @@ mod tests { assert_eq!( func.instrs, vec![TacInstr::Return { - val: Some(Operand::Var("argc".to_string())) + val: Some(Operand::Var("argc".to_string())), + ty: Some(Type::Int), }] ); } diff --git a/src/ir/tac.rs b/src/ir/tac.rs index dd19918..b1c7084 100644 --- a/src/ir/tac.rs +++ b/src/ir/tac.rs @@ -1,8 +1,7 @@ use std::fmt; +use crate::common::ast::ast::Type; use crate::common::ast::expr::{BinOp, UnOp}; -use crate::common::ast::Type; - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TempId(pub u32); @@ -88,7 +87,7 @@ pub enum TacInstr { dst: TempId, op: UnOp, src: Operand, - ty:Type, + ty: Type, }, Copy { dst: Operand, @@ -110,7 +109,7 @@ pub enum TacInstr { }, Return { val: Option, - ty: Option + ty: Option, }, Label(LabelId), } @@ -169,10 +168,21 @@ impl fmt::Display for Operand { impl fmt::Display for TacInstr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TacInstr::BinOp { dst, op, lhs, rhs, ty: _ } => { + TacInstr::BinOp { + dst, + op, + lhs, + rhs, + ty: _, + } => { write!(f, "{dst} = {lhs} {} {rhs}", bin_op_symbol(op)) } - TacInstr::UnOp { dst, op, src, ty: _ } => { + TacInstr::UnOp { + dst, + op, + src, + ty: _, + } => { write!(f, "{dst} = {}{src}", un_op_symbol(op)) } TacInstr::Copy { dst, src, ty: _ } => write!(f, "{dst} = {src}"), @@ -240,5 +250,3 @@ fn un_op_symbol(op: &UnOp) -> &'static str { UnOp::AddrOf => "&", } } - - diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 431a64a..f2cd75f 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -10,4 +10,5 @@ mod peephole_test; mod semantic_test; mod source_test; mod symbol_test; +mod tac_test; mod token_test; diff --git a/src/tests/tac_test.rs b/src/tests/tac_test.rs index 0a7a5b1..a200e43 100644 --- a/src/tests/tac_test.rs +++ b/src/tests/tac_test.rs @@ -1,34 +1,32 @@ +use crate::common::ast::ast::Type; +use crate::common::ast::expr::BinOp; +use crate::ir::tac::{LabelGen, LabelId, Operand, TacInstr, TempGen, TempId}; -#[cfg(test)] -mod tests { - use super::*; +#[test] +fn tac_instr_display_binop() { + let instr = TacInstr::BinOp { + dst: TempId(0), + op: BinOp::Add, + lhs: Operand::Temp(TempId(1)), + rhs: Operand::Temp(TempId(2)), + ty: Type::Int, + }; - #[test] - fn tac_instr_display_binop() { - let instr = TacInstr::BinOp { - dst: TempId(0), - op: BinOp::Add, - lhs: Operand::Temp(TempId(1)), - rhs: Operand::Temp(TempId(2)), - ty: Type::Int, - }; + assert_eq!(instr.to_string(), "t0 = t1 + t2"); +} - assert_eq!(instr.to_string(), "t0 = t1 + t2"); - } +#[test] +fn temp_gen_increments() { + let mut gen = TempGen::new(); - #[test] - fn temp_gen_increments() { - let mut gen = TempGen::new(); + assert_eq!(gen.fresh(), TempId(0)); + assert_eq!(gen.fresh(), TempId(1)); +} - assert_eq!(gen.fresh(), TempId(0)); - assert_eq!(gen.fresh(), TempId(1)); - } +#[test] +fn label_gen_unique() { + let mut gen = LabelGen::new(); - #[test] - fn label_gen_unique() { - let mut gen = LabelGen::new(); - - assert_eq!(gen.fresh(), LabelId(0)); - assert_eq!(gen.fresh(), LabelId(1)); - } -} \ No newline at end of file + assert_eq!(gen.fresh(), LabelId(0)); + assert_eq!(gen.fresh(), LabelId(1)); +} diff --git a/tests/codegen_smoke.rs b/tests/codegen_smoke.rs index a545663..b34e911 100644 --- a/tests/codegen_smoke.rs +++ b/tests/codegen_smoke.rs @@ -12,6 +12,7 @@ use std::path::PathBuf; use std::process::Command; use crusty::codegen::last::emit_program; +use crusty::common::ast::ast::Type; use crusty::common::ast::expr::BinOp; use crusty::ir::tac::{ConstValue, LabelId, Operand, TacFunction, TacInstr, TacProgram, TempId}; @@ -43,9 +44,11 @@ fn build_soma_program() -> TacProgram { op: BinOp::Add, lhs: Operand::Var("a".to_string()), rhs: Operand::Var("b".to_string()), + ty: Type::Int, }, TacInstr::Return { val: Some(Operand::Temp(TempId(0))), + ty: None, }, ], var_sizes: Default::default(), @@ -65,6 +68,7 @@ fn build_soma_program() -> TacProgram { }, TacInstr::Return { val: Some(Operand::Temp(TempId(0))), + ty: None, }, ], var_sizes: Default::default(), @@ -148,6 +152,7 @@ fn smoke_simple_return_const_runs() { params: Vec::new(), instrs: vec![TacInstr::Return { val: Some(Operand::Const(ConstValue::Int(42))), + ty: None, }], var_sizes: Default::default(), }], @@ -189,51 +194,60 @@ fn smoke_call_with_more_than_six_args_runs() { op: BinOp::Add, lhs: Operand::Var("a1".to_string()), rhs: Operand::Var("a2".to_string()), + ty: Type::Int, }, TacInstr::BinOp { dst: TempId(1), op: BinOp::Add, lhs: Operand::Temp(TempId(0)), rhs: Operand::Var("a3".to_string()), + ty: Type::Int, }, TacInstr::BinOp { dst: TempId(2), op: BinOp::Add, lhs: Operand::Temp(TempId(1)), rhs: Operand::Var("a4".to_string()), + ty: Type::Int, }, TacInstr::BinOp { dst: TempId(3), op: BinOp::Add, lhs: Operand::Temp(TempId(2)), rhs: Operand::Var("a5".to_string()), + ty: Type::Int, }, TacInstr::BinOp { dst: TempId(4), op: BinOp::Add, lhs: Operand::Temp(TempId(3)), rhs: Operand::Var("a6".to_string()), + ty: Type::Int, }, TacInstr::BinOp { dst: TempId(5), op: BinOp::Add, lhs: Operand::Temp(TempId(4)), rhs: Operand::Var("a7".to_string()), + ty: Type::Int, }, TacInstr::BinOp { dst: TempId(6), op: BinOp::Add, lhs: Operand::Temp(TempId(5)), rhs: Operand::Var("a8".to_string()), + ty: Type::Int, }, TacInstr::BinOp { dst: TempId(7), op: BinOp::Add, lhs: Operand::Temp(TempId(6)), rhs: Operand::Var("a9".to_string()), + ty: Type::Int, }, TacInstr::Return { val: Some(Operand::Temp(TempId(7))), + ty: None, }, ], var_sizes: Default::default(), @@ -252,6 +266,7 @@ fn smoke_call_with_more_than_six_args_runs() { }, TacInstr::Return { val: Some(Operand::Temp(TempId(0))), + ty: None, }, ], var_sizes: Default::default(), @@ -304,10 +319,12 @@ fn smoke_control_flow_if_else_runs() { TacInstr::Label(LabelId(0)), TacInstr::Return { val: Some(Operand::Const(ConstValue::Int(10))), + ty: None, }, TacInstr::Label(LabelId(1)), TacInstr::Return { val: Some(Operand::Const(ConstValue::Int(20))), + ty: None, }, ], var_sizes: Default::default(), diff --git a/tests/double_codegen_test.rs b/tests/double_codegen_test.rs new file mode 100644 index 0000000..b0b00f0 --- /dev/null +++ b/tests/double_codegen_test.rs @@ -0,0 +1,332 @@ +//! Smoke tests ponta-a-ponta para o codegen x86-64 de `double` (issue #172). +//! +//! Escopo coberto, conforme delimitado na issue: literais `double`, +//! variaveis locais, aritmetica basica (`+ - * /`), comparacoes e `return`. +//! Argumentos/parametros e retorno de `double` por uma funcao chamada pelo +//! proprio codegen (em vez de por um `main` escrito a mao) permanecem fora +//! de escopo, pois exigiriam estender `codegen/last/abi.rs` para cobrir +//! `xmm0..xmm7` na convencao de chamada — deixado para uma proxima etapa. +//! +//! Como nem toda expressao com `double` pode ser verificada via exit code +//! (o valor de retorno de um processo e sempre truncado a um inteiro de 8 +//! bits), os testes usam duas estrategias: +//! - quando o resultado observavel e naturalmente inteiro (comparacoes, que +//! este backend ja normaliza para 0/1 em `%rax`), o programa inteiro e +//! gerado por este compilador e o exit code e verificado diretamente; +//! - quando o resultado e um `double` em si (ex.: o proprio criterio de +//! aceite da issue, `double x = 1.5; return x + 2.5;`), apenas a funcao +//! `double` e gerada por este compilador (`--emit=obj`); um pequeno +//! programa C convencional, compilado pelo `gcc` do sistema, chama essa +//! funcao e verifica o resultado — exercitando o lado "callee" da ABI +//! (retorno em `%xmm0`) sem depender do lado "caller" deste backend. +//! +//! Se `gcc` nao estiver disponivel no ambiente, os testes sao ignorados +//! (skip) em vez de falhar, espelhando `tests/exe_smoke_test.rs`. + +#![cfg_attr(not(unix), allow(unused_variables))] + +use std::path::PathBuf; +use std::process::{Command, ExitStatus}; + +use crusty::analyser::analyse_with_builtins; +use crusty::codegen::last::emit_program; +use crusty::common::input::source::SourceFile; +use crusty::ir::lower::lower_program; +use crusty::lexer::scanner::Scanner; +use crusty::parser::Parser; + +fn gcc_available() -> bool { + Command::new("gcc") + .arg("--version") + .output() + .map(|o| o.status.success()) + .unwrap_or(false) +} + +/// Ignora o teste quando nao ha `gcc` no ambiente. +macro_rules! require_gcc { + () => { + if !gcc_available() { + eprintln!("gcc indisponivel: pulando teste de smoke"); + return; + } + }; +} + +/// Roda o pipeline completo (lexer -> parser -> semantic -> IR -> codegen) +/// sobre `source` e retorna o assembly x86-64 gerado. Falha o teste (panic) +/// se qualquer estagio reportar diagnosticos, ja que os fixtures usados aqui +/// sao sempre programas C validos. +fn compile_to_asm(source: &str) -> String { + let mut scanner = Scanner::new(SourceFile::from_string(source)); + scanner.scan(); + assert!( + scanner.diagnostics.is_empty(), + "erros de lexer inesperados: {:?}", + scanner.diagnostics + ); + + let mut parser = Parser::new(scanner.tokens); + let program = parser + .parse_program() + .unwrap_or_else(|errors| panic!("erros de parser inesperados: {errors:?}")); + + let sem_diagnostics = analyse_with_builtins(&program, scanner.builtins); + let sem_errors: Vec<_> = sem_diagnostics.iter().filter(|d| d.is_error()).collect(); + assert!( + sem_errors.is_empty(), + "erros semanticos inesperados: {sem_errors:?}" + ); + + let tac_program = lower_program(&program).unwrap(); + emit_program(&tac_program).unwrap() +} + +/// Compila `source` (C) ate um executavel real via `gcc` e o executa, +/// retornando o `ExitStatus` do processo filho. Limpa os arquivos +/// temporarios (.s e binario) ao final. +fn compile_and_run(name: &str, source: &str) -> ExitStatus { + let asm = compile_to_asm(source); + + let mut asm_path = std::env::temp_dir(); + asm_path.push(format!( + "crusty_double_smoke_{name}_{}.s", + std::process::id() + )); + std::fs::write(&asm_path, asm).expect("falha ao escrever .s temporario"); + let exe_path: PathBuf = asm_path.with_extension("bin"); + + let link = Command::new("gcc") + .arg(&asm_path) + .arg("-o") + .arg(&exe_path) + .status() + .expect("falha ao invocar gcc"); + assert!( + link.success(), + "gcc nao conseguiu linkar a saida do codegen" + ); + + let status = Command::new(&exe_path) + .status() + .expect("falha ao executar o binario gerado"); + + let _ = std::fs::remove_file(&asm_path); + let _ = std::fs::remove_file(&exe_path); + + status +} + +/// Compila uma funcao `double` isolada (`source`) com `--emit=obj`-equivalente +/// (aqui via `emit_program`, montado a `.o` pelo `gcc`), e linka com um +/// pequeno harness C escrito a mao que chama `compute()` e verifica o +/// resultado. Retorna o `ExitStatus` do harness. +fn compile_double_fn_and_check(name: &str, source: &str, harness_body: &str) -> ExitStatus { + let asm = compile_to_asm(source); + + let dir = std::env::temp_dir(); + let asm_path = dir.join(format!("crusty_double_fn_{name}_{}.s", std::process::id())); + let obj_path = dir.join(format!("crusty_double_fn_{name}_{}.o", std::process::id())); + let harness_path = dir.join(format!( + "crusty_double_harness_{name}_{}.c", + std::process::id() + )); + let exe_path = dir.join(format!( + "crusty_double_fn_{name}_{}.bin", + std::process::id() + )); + + std::fs::write(&asm_path, asm).expect("falha ao escrever .s temporario"); + + let assemble = Command::new("gcc") + .arg("-c") + .arg(&asm_path) + .arg("-o") + .arg(&obj_path) + .status() + .expect("falha ao invocar gcc -c"); + assert!( + assemble.success(), + "gcc nao conseguiu montar a saida do codegen" + ); + + std::fs::write(&harness_path, harness_body).expect("falha ao escrever harness .c"); + + let link = Command::new("gcc") + .arg(&harness_path) + .arg(&obj_path) + .arg("-o") + .arg(&exe_path) + .status() + .expect("falha ao invocar gcc para linkar harness + objeto"); + assert!( + link.success(), + "gcc nao conseguiu linkar o harness com o objeto gerado pelo codegen" + ); + + let status = Command::new(&exe_path) + .status() + .expect("falha ao executar o binario gerado"); + + let _ = std::fs::remove_file(&asm_path); + let _ = std::fs::remove_file(&obj_path); + let _ = std::fs::remove_file(&harness_path); + let _ = std::fs::remove_file(&exe_path); + + status +} + +/// Criterio de aceite da issue #172: `double x = 1.5; return x + 2.5;` +/// compila e roda via gcc com resultado correto (4.0). +#[test] +fn smoke_double_literal_local_and_addition_returns_correct_value() { + require_gcc!(); + + let status = compile_double_fn_and_check( + "literal_add", + "double compute() { double x = 1.5; return x + 2.5; }", + r#" + double compute(void); + int main() { + double r = compute(); + return (r == 4.0) ? 0 : 1; + } + "#, + ); + + #[cfg(unix)] + assert_eq!(status.code(), Some(0)); +} + +#[test] +fn smoke_double_subtraction_multiplication_division_return_correct_values() { + require_gcc!(); + + let status = compile_double_fn_and_check( + "arith", + "double compute() { \ + double a = 10.0; \ + double b = 4.0; \ + double sub = a - b; \ + double mul = sub * 2.0; \ + double div = mul / 3.0; \ + return div; \ + }", + r#" + double compute(void); + int main() { + double r = compute(); + return (r == 4.0) ? 0 : 1; + } + "#, + ); + + #[cfg(unix)] + assert_eq!(status.code(), Some(0)); +} + +#[test] +fn smoke_double_multiple_locals_runs() { + require_gcc!(); + + let status = compile_double_fn_and_check( + "multi_locals", + "double compute() { \ + double a = 1.5; \ + double b = 2.25; \ + double c = 0.25; \ + return a + b + c; \ + }", + r#" + double compute(void); + int main() { + double r = compute(); + return (r == 4.0) ? 0 : 1; + } + "#, + ); + + #[cfg(unix)] + assert_eq!(status.code(), Some(0)); +} + +/// Comparacoes entre `double` ja produzem um resultado inteiro (0/1) neste +/// backend, entao podem ser verificadas direto pelo exit code de um `int +/// main()` gerado integralmente por este compilador, sem depender de +/// conversao double<->int (ainda nao suportada). +#[test] +fn smoke_double_less_than_comparison_runs() { + require_gcc!(); + + let status = compile_and_run( + "double_less_than", + "int main() { \ + double a = 1.5; \ + double b = 2.5; \ + if (a < b) { return 1; } \ + return 0; \ + }", + ); + + #[cfg(unix)] + assert_eq!(status.code(), Some(1)); +} + +#[test] +fn smoke_double_equality_after_addition_runs() { + require_gcc!(); + + let status = compile_and_run( + "double_eq_after_add", + "int main() { \ + double a = 1.5; \ + double b = 2.5; \ + double c = a + b; \ + if (c == 4.0) { return 1; } \ + return 0; \ + }", + ); + + #[cfg(unix)] + assert_eq!(status.code(), Some(1)); +} + +#[test] +fn smoke_double_greater_or_equal_false_branch_runs() { + require_gcc!(); + + let status = compile_and_run( + "double_geq_false", + "int main() { \ + double a = 1.0; \ + double b = 9.0; \ + if (a >= b) { return 1; } \ + return 0; \ + }", + ); + + #[cfg(unix)] + assert_eq!(status.code(), Some(0)); +} + +/// Garante que o assembly gerado para `double` realmente usa o caminho de +/// ponto flutuante (registradores `xmm`/instrucoes `sd`) em vez do caminho +/// inteiro (`rax`/`rcx`), o que a issue #172 identificou como o gap real do +/// backend antes desta feature. +#[test] +fn double_codegen_emits_xmm_instructions() { + let asm = compile_to_asm("double compute() { double x = 1.5; return x + 2.5; }"); + + assert!( + asm.contains("movsd"), + "esperado mov de double via movsd no assembly gerado:\n{asm}" + ); + assert!( + asm.contains("addsd"), + "esperada soma de double via addsd no assembly gerado:\n{asm}" + ); + assert!( + asm.contains(".double"), + "esperada diretiva .double para os literais double na .rodata:\n{asm}" + ); +}