From 3dfbabffeb2b3dcf78b0c615cbf711798c6b5cbc Mon Sep 17 00:00:00 2001 From: Hugo Freitas Silva Date: Wed, 24 Jun 2026 17:42:37 -0300 Subject: [PATCH 1/8] feat(ir): represent global variables in TAC --- src/ir/tac.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ir/tac.rs b/src/ir/tac.rs index 6ae6852..61714e4 100644 --- a/src/ir/tac.rs +++ b/src/ir/tac.rs @@ -65,7 +65,10 @@ pub enum ConstValue { #[derive(Debug, Clone, PartialEq)] pub enum Operand { Temp(TempId), + /// Variavel automatica da funcao atual, residente no stack frame. Var(String), + /// Objeto com duracao de armazenamento estatica, referenciado por simbolo. + Global(String), Const(ConstValue), /// Endereco indireto: o ponteiro guardado em `Operand` interno e lido (ou /// escrito, quando usado como destino de `Copy`) atraves de deref, ex.: @@ -122,7 +125,18 @@ pub struct TacFunction { } #[derive(Debug, Clone, PartialEq)] +pub struct TacGlobal { + pub name: String, + /// Espaco reservado pelo backend. E no minimo um quadword porque o + /// backend escalar atual faz loads/stores de 64 bits. + pub size: i64, + /// `None` representa a inicializacao estatica implicita com zero. + pub init: Option, +} + +#[derive(Debug, Clone, PartialEq, Default)] pub struct TacProgram { + pub globals: Vec, pub functions: Vec, } @@ -154,6 +168,7 @@ impl fmt::Display for Operand { match self { Operand::Temp(temp) => write!(f, "{temp}"), Operand::Var(name) => write!(f, "{name}"), + Operand::Global(name) => write!(f, "@{name}"), Operand::Const(value) => write!(f, "{value}"), Operand::Deref(inner) => write!(f, "*{inner}"), } From 355f88050323e78c1d0a887f77665f7a0c7c04a7 Mon Sep 17 00:00:00 2001 From: Hugo Freitas Silva Date: Wed, 24 Jun 2026 17:42:43 -0300 Subject: [PATCH 2/8] feat(lowering): lower global variable declarations --- src/ir/lower.rs | 254 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 222 insertions(+), 32 deletions(-) diff --git a/src/ir/lower.rs b/src/ir/lower.rs index 5fe7b70..b438ad8 100644 --- a/src/ir/lower.rs +++ b/src/ir/lower.rs @@ -6,7 +6,8 @@ use crate::common::ast::{ }; use crate::common::errors::types::CodegenError; use crate::ir::tac::{ - ConstValue, LabelGen, LabelId, Operand, TacFunction, TacInstr, TacProgram, TempGen, TempId, + ConstValue, LabelGen, LabelId, Operand, TacFunction, TacGlobal, TacInstr, TacProgram, TempGen, + TempId, }; use std::collections::HashMap; @@ -31,6 +32,12 @@ pub struct Lowerer { /// acesso a membro; nao substitui a analise semantica (que ja validou o /// programa antes do lowering). var_types: HashMap, + /// Declaracoes locais atualmente visiveis, uma tabela por escopo lexico. + /// Evita que um local de bloco continue escondendo um global apos `}`. + local_scopes: Vec>, + /// Tipos dos objetos no nivel de arquivo. Mantidos separados dos locais + /// para que um nome local sempre tenha precedencia durante o lowering. + global_types: HashMap, /// Layout (offsets + tamanho) de cada struct declarada no programa, /// calculado uma vez em `lower_program` e compartilhado entre as /// funcoes. Vazio quando o `Lowerer` e usado isoladamente (ex.: testes @@ -49,18 +56,21 @@ struct ControlLabels { impl Lowerer { pub fn new() -> Self { - Self::with_context(HashMap::new(), HashMap::new()) + Self::with_context(HashMap::new(), HashMap::new(), HashMap::new()) } fn with_context( struct_layouts: HashMap, typedefs: HashMap, + global_types: HashMap, ) -> Self { Self { temps: TempGen::new(), labels: LabelGen::new(), instrs: Vec::new(), var_types: HashMap::new(), + local_scopes: vec![HashMap::new()], + global_types, struct_layouts, typedefs, } @@ -71,6 +81,41 @@ impl Lowerer { /// `VarDecl` conforme o lowering avanca. fn declare_var_type(&mut self, name: &str, ty: &Type) { self.var_types.insert(name.to_string(), ty.clone()); + self.local_scopes + .last_mut() + .expect("lowerer sempre possui um escopo local") + .insert(name.to_string(), ty.clone()); + } + + fn type_of_var(&self, name: &str) -> Option<&Type> { + self.local_scopes + .iter() + .rev() + .find_map(|scope| scope.get(name)) + .or_else(|| self.global_types.get(name)) + } + + fn operand_for_var(&self, name: &str) -> Operand { + let is_local = self + .local_scopes + .iter() + .rev() + .any(|scope| scope.contains_key(name)); + if is_local || !self.global_types.contains_key(name) { + Operand::Var(name.to_string()) + } else { + Operand::Global(name.to_string()) + } + } + + fn with_local_scope( + &mut self, + lower: impl FnOnce(&mut Self) -> LowerResult, + ) -> LowerResult { + self.local_scopes.push(HashMap::new()); + let result = lower(self); + self.local_scopes.pop(); + result } /// Tamanho (em bytes) de cada variavel cujo valor nao cabe num slot @@ -97,8 +142,7 @@ impl Lowerer { fn infer_type(&self, expr: &Expr) -> LowerResult { match expr { Expr::Ident(name, _) => self - .var_types - .get(name) + .type_of_var(name) .map(|ty| resolve_alias(ty, &self.typedefs)) .ok_or_else(|| codegen_error("tipo de variavel desconhecido no lowering", Some("type"))), Expr::Unary(UnOp::Deref, inner, _) => match self.infer_type(inner)? { @@ -260,10 +304,11 @@ impl Lowerer { match expr { Expr::Ident(name, _) => { let temp = self.fresh_temp(); + let src = self.operand_for_var(name); self.instrs.push(TacInstr::UnOp { dst: temp, op: UnOp::AddrOf, - src: Operand::Var(name.clone()), + src, }); Ok(Operand::Temp(temp)) } @@ -280,7 +325,7 @@ impl Lowerer { pub fn lower_expr(&mut self, expr: &Expr) -> LowerResult { match expr { Expr::Literal(value, _) => Ok(Operand::Const(lower_literal(value))), - Expr::Ident(name, _) => Ok(Operand::Var(name.clone())), + Expr::Ident(name, _) => Ok(self.operand_for_var(name)), Expr::Binary(lhs, op, rhs, _) => { let lhs = self.lower_expr(lhs)?; let rhs = self.lower_expr(rhs)?; @@ -388,7 +433,7 @@ impl Lowerer { // informacao de tipo disponivel no lowering. Expr::Sizeof(inner, _) => match inner.as_ref() { Expr::Ident(name, _) => { - let ty = self.var_types.get(name).ok_or_else(|| { + let ty = self.type_of_var(name).ok_or_else(|| { codegen_error( "sizeof(expr): tipo da variavel desconhecido no lowering", Some("sizeof"), @@ -410,12 +455,12 @@ impl Lowerer { fn lower_stmt_with_control(&mut self, stmt: &Stmt, control: ControlLabels) -> LowerResult<()> { match stmt { - Stmt::Block(stmts, _) => { + Stmt::Block(stmts, _) => self.with_local_scope(|lowerer| { for stmt in stmts { - self.lower_stmt_with_control(stmt, control)?; + lowerer.lower_stmt_with_control(stmt, control)?; } Ok(()) - } + }), Stmt::If(cond, then_branch, else_branch, _) => { let cond = self.lower_expr(cond)?; let then_label = self.labels.fresh(); @@ -465,29 +510,29 @@ impl Lowerer { self.instrs.push(TacInstr::Label(end_label)); Ok(()) } - Stmt::For(init, cond, inc, body, _) => { + Stmt::For(init, cond, inc, body, _) => self.with_local_scope(|lowerer| { if let Some(init) = init { - self.lower_stmt_with_control(init, control)?; + lowerer.lower_stmt_with_control(init, control)?; } - let cond_label = self.labels.fresh(); - let body_label = self.labels.fresh(); - let inc_label = inc.as_ref().map(|_| self.labels.fresh()); - let end_label = self.labels.fresh(); + let cond_label = lowerer.labels.fresh(); + let body_label = lowerer.labels.fresh(); + let inc_label = inc.as_ref().map(|_| lowerer.labels.fresh()); + let end_label = lowerer.labels.fresh(); let continue_label = inc_label.unwrap_or(cond_label); - self.instrs.push(TacInstr::Label(cond_label)); + lowerer.instrs.push(TacInstr::Label(cond_label)); if let Some(cond) = cond { - let cond = self.lower_expr(cond)?; - self.instrs.push(TacInstr::CondJump { + let cond = lowerer.lower_expr(cond)?; + lowerer.instrs.push(TacInstr::CondJump { cond, then_label: body_label, else_label: end_label, }); } - self.instrs.push(TacInstr::Label(body_label)); - self.lower_stmt_with_control( + lowerer.instrs.push(TacInstr::Label(body_label)); + lowerer.lower_stmt_with_control( body, ControlLabels { break_label: Some(end_label), @@ -496,16 +541,16 @@ impl Lowerer { )?; if let Some(inc_label) = inc_label { - self.instrs.push(TacInstr::Label(inc_label)); + lowerer.instrs.push(TacInstr::Label(inc_label)); if let Some(inc) = inc { - self.lower_expr(inc)?; + lowerer.lower_expr(inc)?; } } - self.emit_jump_unless_terminated(cond_label); + lowerer.emit_jump_unless_terminated(cond_label); - self.instrs.push(TacInstr::Label(end_label)); + lowerer.instrs.push(TacInstr::Label(end_label)); Ok(()) - } + }), Stmt::DoWhile(cond, body, _) => { let body_label = self.labels.fresh(); let cond_label = self.labels.fresh(); @@ -671,7 +716,7 @@ impl Lowerer { // ja funciona corretamente de gracas; para maiores, // copiaria so os 8 primeiros bytes silenciosamente — // recusa explicitamente em vez disso. - if let Some(ty) = self.var_types.get(name) { + if let Some(ty) = self.type_of_var(name) { if let Type::Struct(struct_name) = resolve_alias(ty, &self.typedefs) { let size = self .struct_layouts @@ -686,7 +731,7 @@ impl Lowerer { } } } - Ok(Operand::Var(name.clone())) + Ok(self.operand_for_var(name)) } // `*p` como destino (`*p = x;`, `*p += 1;`, `(*p)++` etc.): o // ponteiro em si e um rvalue comum, mas o destino da escrita e o @@ -716,7 +761,7 @@ impl Lowerer { fn emit_copy(&mut self, dst: Operand, src: Operand) -> LowerResult<()> { match dst { - Operand::Temp(_) | Operand::Var(_) | Operand::Deref(_) => { + Operand::Temp(_) | Operand::Var(_) | Operand::Global(_) | Operand::Deref(_) => { self.instrs.push(TacInstr::Copy { dst, src }); Ok(()) } @@ -744,17 +789,22 @@ impl Default for Lowerer { } pub fn lower_function(decl: &Decl) -> LowerResult { - lower_function_with_context(decl, &HashMap::new(), &HashMap::new()) + lower_function_with_context(decl, &HashMap::new(), &HashMap::new(), &HashMap::new()) } fn lower_function_with_context( decl: &Decl, struct_layouts: &HashMap, typedefs: &HashMap, + global_types: &HashMap, ) -> LowerResult { match decl { Decl::Function(_, name, params, body, _) => { - let mut lowerer = Lowerer::with_context(struct_layouts.clone(), typedefs.clone()); + let mut lowerer = Lowerer::with_context( + struct_layouts.clone(), + typedefs.clone(), + global_types.clone(), + ); for (qty, param_name) in params { lowerer.declare_var_type(param_name, &qty.ty); } @@ -780,6 +830,8 @@ fn lower_function_with_context( pub fn lower_program(prog: &Program) -> LowerResult { let typedefs = build_typedefs(prog); let struct_layouts = build_struct_layouts(prog, &typedefs); + let global_types = build_global_types(prog); + let globals = lower_globals(prog, &typedefs, &struct_layouts)?; let mut functions = Vec::new(); for decl in &prog.decls { @@ -788,11 +840,149 @@ pub fn lower_program(prog: &Program) -> LowerResult { decl, &struct_layouts, &typedefs, + &global_types, )?); } } - Ok(TacProgram { functions }) + Ok(TacProgram { globals, functions }) +} + +fn build_global_types(prog: &Program) -> HashMap { + prog.decls + .iter() + .filter_map(|decl| match decl { + Decl::GlobalVar(qty, name, _, _) => Some((name.clone(), qty.ty.clone())), + _ => None, + }) + .collect() +} + +fn lower_globals( + prog: &Program, + typedefs: &HashMap, + struct_layouts: &HashMap, +) -> LowerResult> { + let mut globals = Vec::new(); + for decl in &prog.decls { + let Decl::GlobalVar(qty, name, init, _) = decl else { + continue; + }; + let ty = resolve_alias(&qty.ty, typedefs); + let size = global_storage_size(&ty, struct_layouts)?; + let init = init.as_ref().map(lower_static_initializer).transpose()?; + globals.push(TacGlobal { + name: name.clone(), + size, + init, + }); + } + Ok(globals) +} + +fn global_storage_size( + ty: &Type, + struct_layouts: &HashMap, +) -> LowerResult { + let raw = match ty { + Type::Struct(name) => { + let layout = struct_layouts.get(name).ok_or_else(|| { + codegen_error( + "layout de struct global desconhecido no lowering", + Some("global"), + ) + })?; + // A selecao de instrucoes atual acessa campos com movq. Reserva + // tambem os bytes alcancados pelo ultimo campo para evitar que + // esse acesso invada o simbolo global seguinte. + layout + .fields + .iter() + .fold(layout.size, |size, (_, offset, _)| size.max(offset + 8)) + } + Type::Char + | Type::Short + | Type::Int + | Type::Long + | Type::Float + | Type::Double + | Type::Pointer(_) + | Type::Enum(_) => 8, + Type::Array(_) | Type::Void | Type::Alias(_) | Type::Function(_, _) => { + return Err(codegen_error( + "tipo de variavel global sem tamanho suportado no lowering", + Some("global"), + )); + } + }; + Ok(align_up(raw.max(8), 8)) +} + +fn lower_static_initializer(expr: &Expr) -> LowerResult { + match expr { + Expr::Literal(value, _) => Ok(lower_literal(value)), + Expr::Cast(_, inner, _) => lower_static_initializer(inner), + _ => eval_const_int(expr).map(ConstValue::Int), + } +} + +fn eval_const_int(expr: &Expr) -> LowerResult { + match expr { + Expr::Literal(Literal::Int(value), _) => Ok(*value), + Expr::Literal(Literal::Char(value), _) => Ok(*value as i64), + Expr::Cast(_, inner, _) => eval_const_int(inner), + Expr::Unary(op, inner, _) => { + let value = eval_const_int(inner)?; + match op { + UnOp::Neg => Ok(value.wrapping_neg()), + UnOp::Not => Ok((value == 0) as i64), + UnOp::BitNot => Ok(!value), + UnOp::Deref | UnOp::AddrOf => Err(codegen_error( + "inicializador global nao e uma constante inteira", + Some("global-init"), + )), + } + } + Expr::Binary(lhs, op, rhs, _) => { + let lhs = eval_const_int(lhs)?; + let rhs = eval_const_int(rhs)?; + match op { + BinOp::Add => Ok(lhs.wrapping_add(rhs)), + BinOp::Sub => Ok(lhs.wrapping_sub(rhs)), + BinOp::Mul => Ok(lhs.wrapping_mul(rhs)), + BinOp::Div if rhs != 0 => Ok(lhs.wrapping_div(rhs)), + BinOp::Mod if rhs != 0 => Ok(lhs.wrapping_rem(rhs)), + BinOp::Eq => Ok((lhs == rhs) as i64), + BinOp::Neq => Ok((lhs != rhs) as i64), + BinOp::Less => Ok((lhs < rhs) as i64), + BinOp::Greater => Ok((lhs > rhs) as i64), + BinOp::Leq => Ok((lhs <= rhs) as i64), + BinOp::Geq => Ok((lhs >= rhs) as i64), + BinOp::And => Ok((lhs != 0 && rhs != 0) as i64), + BinOp::Or => Ok((lhs != 0 || rhs != 0) as i64), + BinOp::BitAnd => Ok(lhs & rhs), + BinOp::BitOr => Ok(lhs | rhs), + BinOp::BitXor => Ok(lhs ^ rhs), + BinOp::Shl => Ok(lhs.wrapping_shl(rhs as u32)), + BinOp::Shr => Ok(lhs.wrapping_shr(rhs as u32)), + BinOp::Div | BinOp::Mod => Err(codegen_error( + "divisao por zero em inicializador global", + Some("global-init"), + )), + } + } + Expr::Ternary(cond, then_expr, else_expr, _) => { + if eval_const_int(cond)? != 0 { + eval_const_int(then_expr) + } else { + eval_const_int(else_expr) + } + } + _ => Err(codegen_error( + "inicializador global deve ser uma expressao constante", + Some("global-init"), + )), + } } /// Segue a cadeia de `Type::Alias` ate um tipo concreto, usando a tabela de From 4af48b6335703c7cd6b7b9a67ae354dc742a71a3 Mon Sep 17 00:00:00 2001 From: Hugo Freitas Silva Date: Wed, 24 Jun 2026 17:42:49 -0300 Subject: [PATCH 3/8] fix(codegen): keep globals out of stack frames --- src/codegen/last/frame.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/last/frame.rs b/src/codegen/last/frame.rs index 05bd762..6bbb48a 100644 --- a/src/codegen/last/frame.rs +++ b/src/codegen/last/frame.rs @@ -41,7 +41,7 @@ impl SlotKey { match op { Operand::Temp(temp) => Some(Self::Temp(temp.0)), Operand::Var(name) => Some(Self::Var(name.clone())), - Operand::Const(_) => None, + Operand::Global(_) | Operand::Const(_) => None, Operand::Deref(inner) => Self::from_operand(inner), } } From 76a95415b9124ba0a0afd7953090382be8e6d028 Mon Sep 17 00:00:00 2001 From: Hugo Freitas Silva Date: Wed, 24 Jun 2026 17:42:55 -0300 Subject: [PATCH 4/8] feat(codegen): emit global storage and RIP-relative access --- src/codegen/last/x86_64.rs | 80 +++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/src/codegen/last/x86_64.rs b/src/codegen/last/x86_64.rs index 8cb90cd..7396260 100644 --- a/src/codegen/last/x86_64.rs +++ b/src/codegen/last/x86_64.rs @@ -45,6 +45,11 @@ struct StringPool { impl StringPool { fn collect(prog: &TacProgram) -> Self { let mut pool = Self::default(); + for global in &prog.globals { + if let Some(value) = &global.init { + pool.visit_operand(&Operand::Const(value.clone())); + } + } for func in &prog.functions { for instr in &func.instrs { pool.visit_instr(instr); @@ -80,8 +85,12 @@ 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::Deref(inner) => self.visit_operand(inner), + _ => {} } } @@ -147,18 +156,63 @@ pub fn emit_program(prog: &TacProgram) -> EmitResult { } em.blank(); } + emit_globals(&mut em, prog, &strings)?; em.raw(".text"); for func in &prog.functions { em.blank(); em.append_str(&emit_function(func, &strings)?); } - // Marca a stack como nao-executavel (boa pratica; evita aviso do linker e - // e o que o proprio GCC adiciona a saida assembly). + // Marca a stack como nao-executavel em formatos ELF. Essa secao nao + // existe no COFF usado pelo MinGW e tornaria o assembly invalido la. em.blank(); - em.raw(".section .note.GNU-stack,\"\",@progbits"); + #[cfg(not(target_os = "windows"))] + { + em.raw(".section .note.GNU-stack,\"\",@progbits"); + } Ok(em.into_string()) } +fn emit_globals(em: &mut Emitter, prog: &TacProgram, strings: &StringPool) -> EmitResult<()> { + let initialized: Vec<_> = prog.globals.iter().filter(|g| g.init.is_some()).collect(); + if !initialized.is_empty() { + em.raw(".data"); + for global in initialized { + em.raw(".balign 8"); + em.raw(&format!(".globl {}", global.name)); + em.raw(&format!("{}:", global.name)); + match global.init.as_ref().expect("filtrado acima") { + ConstValue::Int(value) => em.raw(&format!(" .quad {value}")), + ConstValue::Char(value) => em.raw(&format!(" .quad {}", *value as i64)), + ConstValue::Double(value) => em.raw(&format!(" .quad {}", value.to_bits())), + ConstValue::String(value) => { + let label = strings + .labels + .get(value) + .expect("string global deve ter sido coletada"); + em.raw(&format!(" .quad {label}")); + } + } + if global.size > 8 { + em.raw(&format!(" .zero {}", global.size - 8)); + } + } + em.blank(); + } + + let zeroed: Vec<_> = prog.globals.iter().filter(|g| g.init.is_none()).collect(); + if !zeroed.is_empty() { + em.raw(".bss"); + for global in zeroed { + em.raw(".balign 8"); + em.raw(&format!(".globl {}", global.name)); + em.raw(&format!("{}:", global.name)); + em.raw(&format!(" .zero {}", global.size)); + } + em.blank(); + } + Ok(()) +} + /// Emite o assembly de uma unica funcao: directiva `.globl`, rotulo, /// prologue, corpo e epilogue. fn emit_function(func: &TacFunction, strings: &StringPool) -> EmitResult { @@ -448,6 +502,11 @@ fn emit_unop( // passa por `load_op` (que faria `movq slot(%rbp), %reg`, carregando o // conteudo em vez do endereco). if matches!(op, UnOp::AddrOf) { + if let Operand::Global(name) = src { + em.insn(&format!("leaq {name}(%rip), %rax")); + store_op(em, frame, &Operand::Temp(dst), "rax", strings)?; + return Ok(()); + } let key = SlotKey::from_operand(src).ok_or_else(|| { codegen_error( "endereco-de (&) requer uma variavel ou temporario com slot", @@ -558,6 +617,10 @@ fn load_op( em.insn(&format!("movq {offset}(%rbp), %{reg}")); Ok(()) } + Operand::Global(name) => { + em.insn(&format!("movq {name}(%rip), %{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 @@ -583,6 +646,11 @@ fn store_op( return Ok(()); } + if let Operand::Global(name) = op { + em.insn(&format!("movq %{reg}, {name}(%rip)")); + return Ok(()); + } + let offset = match op { Operand::Temp(temp) => frame .offset_of(&SlotKey::Temp(temp.0)) @@ -590,6 +658,7 @@ fn store_op( Operand::Var(name) => frame .offset_of(&SlotKey::Var(name.clone())) .expect("var sem slot alocado"), + Operand::Global(_) => unreachable!("tratado antes do match acima"), Operand::Const(_) => { return Err(codegen_error( "nao e possivel armazenar em uma constante", @@ -918,6 +987,7 @@ mod tests { #[test] fn emit_program_prepends_text_section() { let prog = TacProgram { + globals: Vec::new(), functions: vec![asm_simple_return_const()], }; From 4f188b0ce95baf9004f0b376d231d038c362ecfd Mon Sep 17 00:00:00 2001 From: Hugo Freitas Silva Date: Wed, 24 Jun 2026 17:43:01 -0300 Subject: [PATCH 5/8] fix(optimizer): preserve global variable assignments --- src/codegen/inter/optimizations.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/codegen/inter/optimizations.rs b/src/codegen/inter/optimizations.rs index b594733..40fdde8 100644 --- a/src/codegen/inter/optimizations.rs +++ b/src/codegen/inter/optimizations.rs @@ -496,6 +496,10 @@ fn has_side_effects(instr: &TacInstr) -> bool { dst: Operand::Var(_), .. } + | TacInstr::Copy { + dst: Operand::Global(_), + .. + } ) } From f4a75aebe22f3440e4422674dbd1e3ad598d111e Mon Sep 17 00:00:00 2001 From: Hugo Freitas Silva Date: Wed, 24 Jun 2026 17:43:07 -0300 Subject: [PATCH 6/8] test(codegen): initialize globals in TAC smoke fixtures --- tests/codegen_smoke.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/codegen_smoke.rs b/tests/codegen_smoke.rs index fc801b9..1f04026 100644 --- a/tests/codegen_smoke.rs +++ b/tests/codegen_smoke.rs @@ -69,6 +69,7 @@ fn build_soma_program() -> TacProgram { }; TacProgram { + globals: Vec::new(), functions: vec![soma, main], } } @@ -141,6 +142,7 @@ fn smoke_simple_return_const_runs() { require_gcc!(); let prog = TacProgram { + globals: Vec::new(), functions: vec![TacFunction { name: "main".to_string(), params: Vec::new(), @@ -256,6 +258,7 @@ fn smoke_call_with_more_than_six_args_runs() { }; let prog = TacProgram { + globals: Vec::new(), functions: vec![sum9, main], }; @@ -290,6 +293,7 @@ fn smoke_control_flow_if_else_runs() { // main: if (1) return 10; else return 20; -> espera-se 10. let prog = TacProgram { + globals: Vec::new(), functions: vec![TacFunction { name: "main".to_string(), params: Vec::new(), From e5f6d11947ce59b086a9c9a64172e313e570321d Mon Sep 17 00:00:00 2001 From: Hugo Freitas Silva Date: Wed, 24 Jun 2026 17:43:12 -0300 Subject: [PATCH 7/8] test(codegen): cover global variable execution --- tests/exe_smoke_test.rs | 48 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/exe_smoke_test.rs b/tests/exe_smoke_test.rs index e75c6ad..1ad20ea 100644 --- a/tests/exe_smoke_test.rs +++ b/tests/exe_smoke_test.rs @@ -364,3 +364,51 @@ fn smoke_switch_fallthrough_runs() { #[cfg(unix)] assert_eq!(status.code(), Some(5)); } + +#[test] +fn smoke_zero_initialized_global_read_and_write_runs() { + require_gcc!(); + + let status = compile_and_run( + "global_counter", + "int counter; int main(void) { counter = 41; return counter + 1; }", + ); + + assert_eq!(status.code(), Some(42)); +} + +#[test] +fn smoke_constant_initialized_global_and_local_shadowing_run() { + require_gcc!(); + + let status = compile_and_run( + "global_init_shadow", + "int value = 8 * 5; int main(void) { int observed = 0; { int value = 11; observed = value; } return value + observed - 9; }", + ); + + assert_eq!(status.code(), Some(42)); +} + +#[test] +fn smoke_global_struct_fixture_runs() { + require_gcc!(); + + let status = compile_and_run( + "global_struct_fixture", + include_str!("integration/valid/structs.c"), + ); + + assert_eq!(status.code(), Some(1)); +} + +#[test] +fn smoke_global_typedef_struct_fixture_runs() { + require_gcc!(); + + let status = compile_and_run( + "global_typedef_fixture", + include_str!("integration/valid/typedef.c"), + ); + + assert_eq!(status.code(), Some(10)); +} From b22aced2a80ee65cc921cfb22ed5e615dea91cdc Mon Sep 17 00:00:00 2001 From: guxvr Date: Wed, 24 Jun 2026 21:17:34 -0300 Subject: [PATCH 8/8] =?UTF-8?q?corrige=20erro=20de=20formata=C3=A7=C3=A3o?= =?UTF-8?q?=20em=20lower.rs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ir/lower.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/lower.rs b/src/ir/lower.rs index 7f5751f..1abc00a 100644 --- a/src/ir/lower.rs +++ b/src/ir/lower.rs @@ -935,7 +935,7 @@ fn global_storage_size( | Type::Double | Type::Pointer(_) | Type::Enum(_) => 8, - Type::Array(_) | Type::Void | Type::Alias(_) | Type::Function(_, _) => { + Type::Array(_, _) | Type::Void | Type::Alias(_) | Type::Function(_, _) => { return Err(codegen_error( "tipo de variavel global sem tamanho suportado no lowering", Some("global"),