diff --git a/src/ast.rs b/src/ast.rs index cf08fe7..2555745 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -38,7 +38,7 @@ pub use expr::{ pub use stmt::{ AssignmentStmt, BreakStmt, CallStmt, CodeBlockStmt, CodeBlockStmtInner, ContinueStmt, IfStmt, - NullStmt, ReturnStmt, WhileStmt, + NullStmt, ReturnStmt, TraceStmt, WhileStmt, }; pub use decl::{ diff --git a/src/ast/display.rs b/src/ast/display.rs index f3850fc..12d0e42 100644 --- a/src/ast/display.rs +++ b/src/ast/display.rs @@ -255,6 +255,7 @@ impl Display for ExprUnitInner { ExprUnitInner::Id(id) => write!(f, "{}", id), ExprUnitInner::ArithExpr(a) => write!(f, "{}", a), ExprUnitInner::FnCall(fc) => write!(f, "{}", fc), + ExprUnitInner::TraceCall(fc) => write!(f, "trace {}", fc), ExprUnitInner::ArrayExpr(ae) => write!(f, "{}", ae), ExprUnitInner::MemberExpr(me) => write!(f, "{}", me), ExprUnitInner::Reference(id) => write!(f, "&{}", id), diff --git a/src/ast/expr.rs b/src/ast/expr.rs index b4e91fd..168e1d5 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -200,6 +200,8 @@ pub enum ExprUnitInner { ArithExpr(Box), /// A function call whose return value is used as a value. FnCall(Box), + /// A traced function call whose return value is used as a value. + TraceCall(Box), /// An array element access used as a value. ArrayExpr(Box), /// A struct member access used as a value. diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index ad5f92e..866e870 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -25,6 +25,13 @@ pub struct CallStmt { pub fn_call: Box, } +/// A traced function call statement, e.g. `trace lower_bound(6);`. +#[derive(Debug, Clone)] +pub struct TraceStmt { + /// The function call whose execution should be traced. + pub fn_call: Box, +} + /// A `return` statement, optionally carrying a value. #[derive(Debug, Clone)] pub struct ReturnStmt { @@ -76,6 +83,8 @@ pub enum CodeBlockStmtInner { Assignment(Box), /// A function-call statement. Call(Box), + /// A traced function-call statement. + Trace(Box), /// An `if` (possibly with `else`) statement. If(Box), /// A `while` loop statement. diff --git a/src/ast/tree.rs b/src/ast/tree.rs index a0171cd..941a9d8 100644 --- a/src/ast/tree.rs +++ b/src/ast/tree.rs @@ -340,6 +340,31 @@ impl DisplayAsTree for CallStmt { } } +impl DisplayAsTree for TraceStmt { + fn fmt_tree( + &self, + f: &mut Formatter<'_>, + indent_levels: &[bool], + is_last: bool, + ) -> Result<(), Error> { + writeln!( + f, + "{}TraceStmt {}", + tree_indent(indent_levels, is_last), + self.fn_call.name + )?; + + let mut new_indent = indent_levels.to_vec(); + new_indent.push(is_last); + + let last_index = self.fn_call.vals.len().saturating_sub(1); + for (i, val) in self.fn_call.vals.iter().enumerate() { + val.fmt_tree(f, &new_indent, i == last_index)?; + } + Ok(()) + } +} + /// Delegates to the concrete statement variant inside a code block. impl DisplayAsTree for CodeBlockStmtInner { fn fmt_tree( @@ -352,6 +377,7 @@ impl DisplayAsTree for CodeBlockStmtInner { CodeBlockStmtInner::VarDecl(stmt) => stmt.fmt_tree(f, indent_levels, is_last), CodeBlockStmtInner::Assignment(stmt) => stmt.fmt_tree(f, indent_levels, is_last), CodeBlockStmtInner::Call(stmt) => stmt.fmt_tree(f, indent_levels, is_last), + CodeBlockStmtInner::Trace(stmt) => stmt.fmt_tree(f, indent_levels, is_last), CodeBlockStmtInner::If(stmt) => stmt.fmt_tree(f, indent_levels, is_last), CodeBlockStmtInner::While(stmt) => stmt.fmt_tree(f, indent_levels, is_last), CodeBlockStmtInner::Return(stmt) => stmt.fmt_tree(f, indent_levels, is_last), @@ -699,6 +725,10 @@ impl DisplayAsTree for ExprUnit { ExprUnitInner::Id(id) => writeln!(f, "{}Id({})", tree_indent(&new_indent, true), id), ExprUnitInner::ArithExpr(ae) => ae.fmt_tree(f, &new_indent, true), ExprUnitInner::FnCall(fc) => fc.fmt_tree(f, &new_indent, true), + ExprUnitInner::TraceCall(fc) => { + writeln!(f, "{}TraceCall", tree_indent(&new_indent, false))?; + fc.fmt_tree(f, &new_indent, true) + } ExprUnitInner::ArrayExpr(ae) => ae.fmt_tree(f, &new_indent, true), ExprUnitInner::MemberExpr(me) => me.fmt_tree(f, &new_indent, true), ExprUnitInner::Reference(id) => { diff --git a/src/experimental/return_infer.rs b/src/experimental/return_infer.rs index 33d9148..7087c21 100644 --- a/src/experimental/return_infer.rs +++ b/src/experimental/return_infer.rs @@ -428,6 +428,10 @@ impl Collector<'_> { self.type_of_fn_call(&s.fn_call)?; Ok(()) } + ast::CodeBlockStmtInner::Trace(s) => { + self.type_of_fn_call(&s.fn_call)?; + Ok(()) + } ast::CodeBlockStmtInner::Return(s) => self.process_return(s), ast::CodeBlockStmtInner::Continue(_) | ast::CodeBlockStmtInner::Break(_) @@ -687,6 +691,7 @@ impl Collector<'_> { ast::ExprUnitInner::Id(id) => self.resolve_variable(id), ast::ExprUnitInner::ArithExpr(expr) => self.type_of_arith_expr(expr), ast::ExprUnitInner::FnCall(call) => self.type_of_fn_call(call), + ast::ExprUnitInner::TraceCall(call) => self.type_of_fn_call(call), ast::ExprUnitInner::ArrayExpr(expr) => self.type_of_array_expr(expr), ast::ExprUnitInner::MemberExpr(expr) => self.type_of_member_expr(expr), ast::ExprUnitInner::Reference(id) => self.type_of_reference(id), diff --git a/src/ir/function.rs b/src/ir/function.rs index e7c188a..e581eea 100644 --- a/src/ir/function.rs +++ b/src/ir/function.rs @@ -121,6 +121,8 @@ pub struct FunctionGenerator<'ir> { /// Counter for allocating unique basic block label indices; starts at `1` /// because index `0` is reserved for the implicit function-entry block. pub next_basic_block: usize, + /// Whether this function should emit trace instrumentation while lowering. + pub trace_enabled: bool, } impl<'ir> FunctionGenerator<'ir> { @@ -134,6 +136,7 @@ impl<'ir> FunctionGenerator<'ir> { registry: &'ir Registry, global_variables: &'ir IndexMap, GlobalDef>, resolved_types: HashMap, + trace_enabled: bool, ) -> Self { Self { registry, @@ -145,6 +148,7 @@ impl<'ir> FunctionGenerator<'ir> { arguments: Vec::new(), next_vreg: 0, next_basic_block: 1, + trace_enabled, } } diff --git a/src/ir/gen.rs b/src/ir/gen.rs index 35ce5dd..47b8901 100644 --- a/src/ir/gen.rs +++ b/src/ir/gen.rs @@ -7,4 +7,5 @@ pub(super) mod conversions; mod function_gen; mod module_gen; mod static_eval; +mod trace; mod type_infer; diff --git a/src/ir/gen/function_gen.rs b/src/ir/gen/function_gen.rs index 51fc354..0510ecb 100644 --- a/src/ir/gen/function_gen.rs +++ b/src/ir/gen/function_gen.rs @@ -9,6 +9,7 @@ use crate::ast::{self, ArrayInitializer, AssignmentStmt, RightValList}; use crate::ir::function::{BlockLabel, FunctionGenerator}; use crate::ir::gen::conversions::{compose_var_decl_dtype, compose_var_def_dtype}; +use crate::ir::gen::trace; use crate::ir::stmt::{ArithBinOp, CmpPredicate, StmtInner}; use crate::ir::types::Dtype; use crate::ir::value::{Local, Operand}; @@ -80,6 +81,7 @@ impl FunctionGenerator<'_> { self.emit_label(BlockLabel::Function(entry_label)); // Spill every argument to the stack (alloca + store) so they are addressable. + let mut trace_args = Vec::new(); for (id, dtype) in &arguments { if self.local_variables.contains_key(id) { return Err(Error::VariableRedefinition { symbol: id.clone() }); @@ -88,6 +90,7 @@ impl FunctionGenerator<'_> { // Allocate a virtual register that carries the incoming argument value. let arg_local = self.fresh_local(dtype.clone()); self.arguments.push(arg_local.clone()); + trace_args.push((id.clone(), Operand::from(&arg_local))); // Allocate a stack slot (pointer to the argument type) for the argument. let slot = self.fresh_local(Dtype::ptr_to(dtype.clone())); @@ -97,9 +100,14 @@ impl FunctionGenerator<'_> { self.local_variables.insert(id.clone(), slot); } + if self.trace_enabled { + self.emit_trace_call_line(0, identifier, &trace_args); + self.emit_trace_function_enter(); + } + // Lower the function body statement by statement. for stmt in &from.stmts { - self.handle_block(stmt, None, None)?; + self.handle_block(stmt, None, None, 0)?; } // Append an implicit return if the last instruction is not already a @@ -109,8 +117,8 @@ impl FunctionGenerator<'_> { if let Some(stmt) = self.irs.last() { if !matches!(stmt.inner, StmtInner::Return(_)) { match &return_dtype { - Dtype::I32 => self.emit_return(Some(Operand::from(0))), - Dtype::Void => self.emit_return(None), + Dtype::I32 => self.emit_function_return(0, Some(Operand::from(0))), + Dtype::Void => self.emit_function_return(0, None), other => unreachable!( "function {} has return type {other} which \ FunctionType::try_from should have rejected", @@ -129,6 +137,13 @@ impl FunctionGenerator<'_> { // ----------------------------------------------------------------------- impl FunctionGenerator<'_> { + fn emit_function_return(&mut self, trace_indent: usize, value: Option) { + if self.trace_enabled { + self.emit_trace_function_return(trace_indent, value.clone()); + } + self.emit_return(value); + } + /// Dispatches a single code-block statement to the appropriate handler. /// /// `con_label` and `bre_label` are the jump targets for `continue` and @@ -139,17 +154,21 @@ impl FunctionGenerator<'_> { stmt: &ast::CodeBlockStmt, con_label: Option<&BlockLabel>, bre_label: Option<&BlockLabel>, + trace_indent: usize, ) -> Result<(), Error> { match &stmt.inner { - ast::CodeBlockStmtInner::Assignment(s) => self.handle_assignment_stmt(s), + ast::CodeBlockStmtInner::Assignment(s) => self.handle_assignment_stmt(s, trace_indent), ast::CodeBlockStmtInner::VarDecl(s) => match &s.inner { ast::VarDeclStmtInner::Decl(d) => self.handle_local_var_decl(d), - ast::VarDeclStmtInner::Def(d) => self.handle_local_var_def(d), + ast::VarDeclStmtInner::Def(d) => self.handle_local_var_def(d, trace_indent), }, ast::CodeBlockStmtInner::Call(s) => self.handle_call_stmt(s), - ast::CodeBlockStmtInner::If(s) => self.handle_if_stmt(s, con_label, bre_label), - ast::CodeBlockStmtInner::While(s) => self.handle_while_stmt(s), - ast::CodeBlockStmtInner::Return(s) => self.handle_return_stmt(s), + ast::CodeBlockStmtInner::Trace(s) => self.handle_trace_stmt(s), + ast::CodeBlockStmtInner::If(s) => { + self.handle_if_stmt(s, con_label, bre_label, trace_indent) + } + ast::CodeBlockStmtInner::While(s) => self.handle_while_stmt(s, trace_indent), + ast::CodeBlockStmtInner::Return(s) => self.handle_return_stmt(s, trace_indent), ast::CodeBlockStmtInner::Continue(_) => self.handle_continue_stmt(con_label), ast::CodeBlockStmtInner::Break(_) => self.handle_break_stmt(bre_label), ast::CodeBlockStmtInner::Null(_) => Ok(()), @@ -161,10 +180,33 @@ impl FunctionGenerator<'_> { /// `handle_left_val` yields a pointer to the destination's stack slot and /// `handle_right_val` yields the value to store, so the assignment is a /// single `store` instruction. - pub fn handle_assignment_stmt(&mut self, stmt: &AssignmentStmt) -> Result<(), Error> { + pub fn handle_assignment_stmt( + &mut self, + stmt: &AssignmentStmt, + trace_indent: usize, + ) -> Result<(), Error> { let left = self.handle_left_val(&stmt.left_val)?; let right = self.handle_right_val(&stmt.right_val)?; - self.emit_store(right, left); + + let target = match left.dtype() { + Dtype::Pointer { pointee } => pointee.as_ref(), + Dtype::I32 if matches!(&left, Operand::Global(_)) => left.dtype(), + other => unreachable!( + "assignment lhs is neither a pointer nor an assignable global: {other}" + ), + }; + let old = if self.trace_enabled && matches!(target, Dtype::I32) { + let old = Operand::from(self.fresh_local(Dtype::I32)); + self.emit_load(old.clone(), left.clone()); + Some(old) + } else { + None + }; + + self.emit_store(right.clone(), left); + if let Some(old) = old { + self.emit_trace_change_line(trace_indent, &trace::left_val(&stmt.left_val), old, right); + } Ok(()) } @@ -173,11 +215,7 @@ impl FunctionGenerator<'_> { /// Records the identifier for scope-exit cleanup via [`record_scoped_local`]. /// Returns `VariableRedefinition` if a variable with the same name already /// exists in the symbol table. - fn insert_scoped_local( - &mut self, - identifier: &str, - variable: Local, - ) -> Result<(), Error> { + fn insert_scoped_local(&mut self, identifier: &str, variable: Local) -> Result<(), Error> { if self .local_variables .insert(identifier.to_string(), variable) @@ -267,7 +305,11 @@ impl FunctionGenerator<'_> { let element_ptr = Operand::from(self.fresh_local(elem_ptr_dtype.clone())); let right_elem = self.handle_right_val(val)?; - self.emit_gep(element_ptr.clone(), base_ptr.clone(), array_index_operand(i)); + self.emit_gep( + element_ptr.clone(), + base_ptr.clone(), + array_index_operand(i), + ); self.emit_store(right_elem, element_ptr); } Ok(()) @@ -290,7 +332,11 @@ impl FunctionGenerator<'_> { let fill_val = self.handle_right_val(val)?; for i in 0..*count { let element_ptr = Operand::from(self.fresh_local(elem_ptr_dtype.clone())); - self.emit_gep(element_ptr.clone(), base_ptr.clone(), array_index_operand(i)); + self.emit_gep( + element_ptr.clone(), + base_ptr.clone(), + array_index_operand(i), + ); self.emit_store(fill_val.clone(), element_ptr); } Ok(()) @@ -300,16 +346,22 @@ impl FunctionGenerator<'_> { /// Lowers a local variable definition (declaration with an initializer) /// by allocating a stack slot and storing the initial value into it. - pub fn handle_local_var_def(&mut self, def: &ast::VarDef) -> Result<(), Error> { + pub fn handle_local_var_def( + &mut self, + def: &ast::VarDef, + trace_indent: usize, + ) -> Result<(), Error> { let identifier = def.identifier.as_str(); let explicit = def.type_specifier.as_ref().map(Dtype::from); let is_scalar = matches!(&def.inner, ast::VarDefInner::Scalar(_)); let base = self.local_base_dtype(identifier, explicit.as_ref(), is_scalar); let pointee = compose_var_def_dtype(base, &def.inner); + let mut trace_value = None; let variable: Local = match &def.inner { ast::VarDefInner::Scalar(scalar) => { let right_val = self.handle_right_val(&scalar.val)?; + trace_value = Some(right_val.clone()); self.define_scalar_local(pointee, right_val) } ast::VarDefInner::Array(array) => { @@ -319,7 +371,13 @@ impl FunctionGenerator<'_> { } }; - self.insert_scoped_local(identifier, variable) + self.insert_scoped_local(identifier, variable)?; + if self.trace_enabled { + if let Some(value) = trace_value { + self.emit_trace_let_line(trace_indent, identifier, value); + } + } + Ok(()) } /// Lowers a standalone function call statement. @@ -328,35 +386,69 @@ impl FunctionGenerator<'_> { /// value (which is subsequently discarded), and emits the `call` instruction. pub fn handle_call_stmt(&mut self, stmt: &ast::CallStmt) -> Result<(), Error> { let function_name = stmt.fn_call.qualified_name(); - let mut args = Vec::new(); - for arg in &stmt.fn_call.vals { - let right_val = self.handle_right_val(arg)?; - args.push(right_val); - } + let return_dtype = self + .registry + .function_types + .get(&function_name) + .ok_or_else(|| Error::FunctionNotDefined { + symbol: function_name.clone(), + })? + .return_dtype + .clone(); + let args = self.eval_call_args(&stmt.fn_call)?; + let retval = self.call_result_slot(&function_name, &return_dtype); + let link_name = self.resolve_link_name(&function_name); + self.emit_call(link_name, retval, args); + Ok(()) + } - match self.registry.function_types.get(&function_name) { - None => Err(Error::FunctionNotDefined { - symbol: function_name, - }), - Some(function_type) => { - // `FunctionType::try_from` whitelists return types to Void/I32 - // at registration; any other variant here would indicate a - // broken invariant in the front-end. - let retval = match &function_type.return_dtype { - Dtype::Void => None, - Dtype::I32 => Some(Operand::from(self.fresh_local(Dtype::I32))), - other => unreachable!( - "registered function {function_name} has return type {other} \ - which FunctionType::try_from should have rejected" - ), - }; - let link_name = self.resolve_link_name(&function_name); - self.emit_call(link_name, retval, args); - Ok(()) - } + pub fn handle_trace_stmt(&mut self, stmt: &ast::TraceStmt) -> Result<(), Error> { + self.emit_traced_call(&stmt.fn_call)?; + Ok(()) + } + + fn eval_call_args(&mut self, fn_call: &ast::FnCall) -> Result, Error> { + fn_call + .vals + .iter() + .map(|arg| self.handle_right_val(arg)) + .collect() + } + + fn call_result_slot(&mut self, function_name: &str, return_dtype: &Dtype) -> Option { + match return_dtype { + Dtype::Void => None, + Dtype::I32 => Some(Operand::from(self.fresh_local(Dtype::I32))), + other => unreachable!( + "registered function {function_name} has return type {other} \ + which FunctionType::try_from should have rejected" + ), } } + fn emit_traced_call(&mut self, fn_call: &ast::FnCall) -> Result, Error> { + let function_name = fn_call.qualified_name(); + let return_dtype = self + .registry + .function_types + .get(&function_name) + .ok_or_else(|| Error::FunctionNotDefined { + symbol: function_name.clone(), + })? + .return_dtype + .clone(); + let args = self.eval_call_args(fn_call)?; + + self.emit_trace_begin_line(&function_name, &args); + + let retval = self.call_result_slot(&function_name, &return_dtype); + let link_name = self.resolve_link_name(&function_name); + self.emit_call(link_name, retval.clone(), args); + + self.emit_trace_end_line(); + Ok(retval) + } + /// Lowers an `if` / `else` statement into branching IR. /// /// Allocates three basic blocks (`true_label`, `false_label`, `after_label`) @@ -371,19 +463,33 @@ impl FunctionGenerator<'_> { stmt: &ast::IfStmt, con_label: Option<&BlockLabel>, bre_label: Option<&BlockLabel>, + trace_indent: usize, ) -> Result<(), Error> { let true_label = self.alloc_basic_block(); let false_label = self.alloc_basic_block(); let after_label = self.alloc_basic_block(); // Evaluate the condition; jump to the appropriate branch. - self.handle_bool_unit(&stmt.bool_unit, true_label.clone(), false_label.clone())?; + if self.trace_enabled { + let cond_value = self.handle_bool_unit_as_value(&stmt.bool_unit)?; + self.emit_trace_condition_line( + trace_indent, + "if", + &trace::bool_unit(&stmt.bool_unit), + cond_value.clone(), + ); + let cond = Operand::from(self.fresh_local(Dtype::I1)); + self.emit_cmp(CmpPredicate::Ne, cond_value, Operand::from(0), cond.clone()); + self.emit_cjump(cond, true_label.clone(), false_label.clone()); + } else { + self.handle_bool_unit(&stmt.bool_unit, true_label.clone(), false_label.clone())?; + } // Emit the then-branch; a new scope is opened so that any locals are cleaned up. self.emit_label(true_label); self.enter_scope(); for s in &stmt.if_stmts { - self.handle_block(s, con_label, bre_label)?; + self.handle_block(s, con_label, bre_label, trace_indent + 1)?; } self.exit_scope(); // Jump past the else-branch to the merge point. @@ -393,8 +499,11 @@ impl FunctionGenerator<'_> { self.emit_label(false_label); self.enter_scope(); if let Some(else_stmts) = &stmt.else_stmts { + if self.trace_enabled { + self.emit_trace_else_line(trace_indent); + } for s in else_stmts { - self.handle_block(s, con_label, bre_label)?; + self.handle_block(s, con_label, bre_label, trace_indent + 1)?; } } self.exit_scope(); @@ -415,23 +524,44 @@ impl FunctionGenerator<'_> { /// true_label false_label /// ``` /// `continue` inside the body targets `test_label`; `break` targets `false_label`. - pub fn handle_while_stmt(&mut self, stmt: &ast::WhileStmt) -> Result<(), Error> { + pub fn handle_while_stmt( + &mut self, + stmt: &ast::WhileStmt, + trace_indent: usize, + ) -> Result<(), Error> { let test_label = self.alloc_basic_block(); let true_label = self.alloc_basic_block(); let false_label = self.alloc_basic_block(); + let loop_slot = self.trace_enabled.then(|| self.emit_trace_loop_slot()); // Jump unconditionally into the loop test from the predecessor block. self.emit_jump(test_label.clone()); // Emit the loop condition test. self.emit_label(test_label.clone()); - self.handle_bool_unit(&stmt.bool_unit, true_label.clone(), false_label.clone())?; + if self.trace_enabled { + let cond_value = self.handle_bool_unit_as_value(&stmt.bool_unit)?; + self.emit_trace_condition_line( + trace_indent, + "while", + &trace::bool_unit(&stmt.bool_unit), + cond_value.clone(), + ); + let cond = Operand::from(self.fresh_local(Dtype::I1)); + self.emit_cmp(CmpPredicate::Ne, cond_value, Operand::from(0), cond.clone()); + self.emit_cjump(cond, true_label.clone(), false_label.clone()); + } else { + self.handle_bool_unit(&stmt.bool_unit, true_label.clone(), false_label.clone())?; + } // Loop body; `continue` → test_label, `break` → false_label. self.emit_label(true_label); + if let Some(slot) = &loop_slot { + self.bump_trace_loop(slot, trace_indent); + } self.enter_scope(); for s in &stmt.stmts { - self.handle_block(s, Some(&test_label), Some(&false_label))?; + self.handle_block(s, Some(&test_label), Some(&false_label), trace_indent + 1)?; } self.exit_scope(); // Back-edge: jump back to the loop condition. @@ -445,14 +575,18 @@ impl FunctionGenerator<'_> { /// /// Emits a void `return` when no value is present, or evaluates the return /// expression and emits a value-carrying `return` otherwise. - pub fn handle_return_stmt(&mut self, stmt: &ast::ReturnStmt) -> Result<(), Error> { + pub fn handle_return_stmt( + &mut self, + stmt: &ast::ReturnStmt, + trace_indent: usize, + ) -> Result<(), Error> { match &stmt.val { None => { - self.emit_return(None); + self.emit_function_return(trace_indent, None); } Some(val) => { let val = self.handle_right_val(val)?; - self.emit_return(Some(val)); + self.emit_function_return(trace_indent, Some(val)); } } Ok(()) @@ -496,12 +630,7 @@ impl FunctionGenerator<'_> { let right = self.handle_expr_unit(&expr.right)?; let dst = Operand::from(self.fresh_local(Dtype::I1)); - self.emit_cmp( - CmpPredicate::from(&expr.op), - left, - right, - dst.clone(), - ); + self.emit_cmp(CmpPredicate::from(&expr.op), left, right, dst.clone()); self.emit_cjump(dst, true_label, false_label); Ok(()) @@ -530,41 +659,32 @@ impl FunctionGenerator<'_> { ast::ExprUnitInner::ArithExpr(expr) => self.handle_arith_expr(expr), ast::ExprUnitInner::FnCall(fn_call) => { let name = fn_call.qualified_name(); - let return_dtype = &self + let return_dtype = self .registry .function_types .get(&name) .ok_or_else(|| Error::InvalidExprUnit { expr_unit: unit.clone(), })? - .return_dtype; - - // `FunctionType::try_from` whitelists return types to Void/I32. - // In expression position, only I32 is usable; a void call in - // an expression is a source-level mistake. - let res = match return_dtype { - Dtype::I32 => Operand::from(self.fresh_local(Dtype::I32)), - Dtype::Void => { - return Err(Error::InvalidExprUnit { - expr_unit: unit.clone(), - }); + .return_dtype + .clone(); + let res = self.call_result_slot(&name, &return_dtype).ok_or_else(|| { + Error::InvalidExprUnit { + expr_unit: unit.clone(), } - other => unreachable!( - "registered function {name} has return type {other} \ - which FunctionType::try_from should have rejected" - ), - }; - - let mut args: Vec = Vec::new(); - for arg in &fn_call.vals { - let rval = self.handle_right_val(arg)?; - args.push(rval); - } + })?; + let args = self.eval_call_args(fn_call)?; let link_name = self.resolve_link_name(&name); self.emit_call(link_name, Some(res.clone()), args); Ok(res) } + ast::ExprUnitInner::TraceCall(fn_call) => { + self.emit_traced_call(fn_call)? + .ok_or_else(|| Error::InvalidExprUnit { + expr_unit: unit.clone(), + }) + } ast::ExprUnitInner::ArrayExpr(expr) => self.handle_array_expr(expr), ast::ExprUnitInner::MemberExpr(expr) => self.handle_member_expr(expr), ast::ExprUnitInner::Reference(id) => { @@ -701,14 +821,11 @@ impl FunctionGenerator<'_> { .map(|elem| &elem.1) .ok_or_else(|| Error::InvalidStructMemberExpression { expr: expr.clone() })?; let member_dtype = member.dtype.clone(); - let member_index = i32::try_from(member.index).map_err(|_| { - Error::InvalidStructMemberExpression { expr: expr.clone() } - })?; + let member_index = i32::try_from(member.index) + .map_err(|_| Error::InvalidStructMemberExpression { expr: expr.clone() })?; let target = match &member_dtype { - Dtype::Void => { - return Err(Error::InvalidStructMemberExpression { expr: expr.clone() }) - } + Dtype::Void => return Err(Error::InvalidStructMemberExpression { expr: expr.clone() }), _ => Operand::from(self.fresh_local(Dtype::ptr_to(member_dtype))), }; @@ -789,6 +906,28 @@ impl FunctionGenerator<'_> { Ok(loaded) } + fn handle_bool_unit_as_value(&mut self, unit: &ast::BoolUnit) -> Result { + let true_label = self.alloc_basic_block(); + let false_label = self.alloc_basic_block(); + let after_label = self.alloc_basic_block(); + + let bool_evaluated = Operand::from(self.fresh_local(Dtype::ptr_to(Dtype::I32))); + self.emit_alloca(bool_evaluated.clone()); + + self.handle_bool_unit(unit, true_label.clone(), false_label.clone())?; + self.emit_bool_materialization( + true_label, + false_label, + after_label, + bool_evaluated.clone(), + ); + + let loaded = Operand::from(self.fresh_local(Dtype::I32)); + self.emit_load(loaded.clone(), bool_evaluated); + + Ok(loaded) + } + /// Lowers a boolean expression as a branching construct. /// /// Jumps to `true_label` if the expression evaluates to true, or to diff --git a/src/ir/gen/module_gen.rs b/src/ir/gen/module_gen.rs index e56e4d8..f45c8c3 100644 --- a/src/ir/gen/module_gen.rs +++ b/src/ir/gen/module_gen.rs @@ -10,6 +10,7 @@ use crate::ast; use crate::ir::compute_link_name; use crate::ir::function::{BasicBlock, BlockLabel, Function, FunctionBody, FunctionGenerator}; +use crate::ir::gen::trace; use crate::ir::gen::type_infer; use crate::ir::module::IrGenerator; use crate::ir::printer::IrPrinter; @@ -81,6 +82,11 @@ impl Generator for IrGenerator<'_> { result?; } + let trace_plan = trace::plan(input); + if trace_plan.needs_runtime { + trace::register_runtime(self); + } + // Pass 3: generate IR bodies for every function definition. for elem in &input.elements { if let ast::ProgramElementInner::FnDef(fn_def) = &elem.inner { @@ -100,6 +106,7 @@ impl Generator for IrGenerator<'_> { &self.registry, &self.module.global_list, resolved_types, + trace_plan.functions.contains(&fn_def.fn_decl.identifier), ); function_generator.generate(fn_def)?; diff --git a/src/ir/gen/trace.rs b/src/ir/gen/trace.rs new file mode 100644 index 0000000..176ac03 --- /dev/null +++ b/src/ir/gen/trace.rs @@ -0,0 +1,490 @@ +use crate::ast; +use crate::ir::function::FunctionGenerator; +use crate::ir::module::IrGenerator; +use crate::ir::stmt::{ArithBinOp, CmpPredicate}; +use crate::ir::types::{Dtype, FunctionType}; +use crate::ir::value::{Local, Operand}; +use crate::ir::Function; +use std::collections::{HashMap, HashSet}; + +pub(super) struct TracePlan { + pub functions: HashSet, + pub needs_runtime: bool, +} + +pub(super) fn plan(program: &ast::Program) -> TracePlan { + let mut graph = HashMap::new(); + let mut roots = HashSet::new(); + let mut defined = HashSet::new(); + + for elem in &program.elements { + if let ast::ProgramElementInner::FnDef(fn_def) = &elem.inner { + let name = fn_def.fn_decl.identifier.clone(); + let sites = collect_call_sites(&fn_def.stmts); + defined.insert(name.clone()); + roots.extend(sites.roots); + graph.insert(name, sites.calls); + } + } + + let needs_runtime = !roots.is_empty(); + let mut traced = HashSet::new(); + let mut worklist = roots.into_iter().collect::>(); + while let Some(function) = worklist.pop() { + if defined.contains(&function) && traced.insert(function.clone()) { + for call in &graph[&function] { + worklist.push(call.clone()); + } + } + } + + TracePlan { + functions: traced, + needs_runtime, + } +} + +pub(super) fn register_runtime(gen: &mut IrGenerator<'_>) { + let fns = [ + ("__teac_trace_begin", Dtype::Void, vec![]), + ("__teac_trace_finish", Dtype::Void, vec![]), + ("__teac_trace_enter", Dtype::Void, vec![]), + ("__teac_trace_leave", Dtype::Void, vec![]), + ("__teac_trace_event", Dtype::Void, vec![Dtype::I32]), + ("__teac_trace_putch", Dtype::Void, vec![Dtype::I32]), + ("__teac_trace_putint", Dtype::Void, vec![Dtype::I32]), + ("__teac_trace_putbool", Dtype::Void, vec![Dtype::I32]), + ]; + + for (name, return_dtype, args) in fns { + gen.registry.function_types.insert( + name.to_string(), + FunctionType { + return_dtype, + arguments: args + .into_iter() + .enumerate() + .map(|(i, dtype)| (format!("arg{i}"), dtype)) + .collect(), + }, + ); + gen.registry + .link_names + .insert(name.to_string(), name.to_string()); + gen.module.function_list.insert( + name.to_string(), + Function { + identifier: name.to_string(), + link_name: name.to_string(), + body: None, + }, + ); + } +} + +#[derive(Default)] +struct CallSites { + calls: HashSet, + roots: HashSet, +} + +fn collect_call_sites(stmts: &[ast::CodeBlockStmt]) -> CallSites { + let mut sites = CallSites::default(); + collect_stmts(stmts, &mut sites); + sites +} + +fn collect_stmts(stmts: &[ast::CodeBlockStmt], sites: &mut CallSites) { + for stmt in stmts { + match &stmt.inner { + ast::CodeBlockStmtInner::VarDecl(s) => { + if let ast::VarDeclStmtInner::Def(def) = &s.inner { + collect_var_def(def, sites); + } + } + ast::CodeBlockStmtInner::Assignment(s) => collect_right_val(&s.right_val, sites), + ast::CodeBlockStmtInner::Call(s) => collect_fn_call(&s.fn_call, sites), + ast::CodeBlockStmtInner::Trace(s) => collect_trace_call(&s.fn_call, sites), + ast::CodeBlockStmtInner::If(s) => { + collect_bool_unit(&s.bool_unit, sites); + collect_stmts(&s.if_stmts, sites); + if let Some(else_stmts) = &s.else_stmts { + collect_stmts(else_stmts, sites); + } + } + ast::CodeBlockStmtInner::While(s) => { + collect_bool_unit(&s.bool_unit, sites); + collect_stmts(&s.stmts, sites); + } + ast::CodeBlockStmtInner::Return(s) => { + if let Some(val) = &s.val { + collect_right_val(val, sites); + } + } + ast::CodeBlockStmtInner::Continue(_) + | ast::CodeBlockStmtInner::Break(_) + | ast::CodeBlockStmtInner::Null(_) => {} + } + } +} + +fn collect_var_def(def: &ast::VarDef, sites: &mut CallSites) { + match &def.inner { + ast::VarDefInner::Scalar(scalar) => collect_right_val(&scalar.val, sites), + ast::VarDefInner::Array(array) => match &array.initializer { + ast::ArrayInitializer::ExplicitList(vals) => { + for val in vals { + collect_right_val(val, sites); + } + } + ast::ArrayInitializer::Fill { val, .. } => collect_right_val(val, sites), + }, + } +} + +fn collect_fn_call(call: &ast::FnCall, sites: &mut CallSites) { + sites.calls.insert(call.qualified_name()); + for arg in &call.vals { + collect_right_val(arg, sites); + } +} + +fn collect_trace_call(call: &ast::FnCall, sites: &mut CallSites) { + sites.roots.insert(call.qualified_name()); + collect_fn_call(call, sites); +} + +fn collect_expr_unit(unit: &ast::ExprUnit, sites: &mut CallSites) { + match &unit.inner { + ast::ExprUnitInner::ArithExpr(expr) => collect_arith_expr(expr, sites), + ast::ExprUnitInner::FnCall(call) => collect_fn_call(call, sites), + ast::ExprUnitInner::TraceCall(call) => collect_trace_call(call, sites), + ast::ExprUnitInner::ArrayExpr(_) + | ast::ExprUnitInner::MemberExpr(_) + | ast::ExprUnitInner::Num(_) + | ast::ExprUnitInner::Id(_) + | ast::ExprUnitInner::Reference(_) => {} + } +} + +fn collect_arith_expr(expr: &ast::ArithExpr, sites: &mut CallSites) { + match &expr.inner { + ast::ArithExprInner::ArithBiOpExpr(expr) => { + collect_arith_expr(&expr.left, sites); + collect_arith_expr(&expr.right, sites); + } + ast::ArithExprInner::ExprUnit(unit) => collect_expr_unit(unit, sites), + } +} + +fn collect_bool_unit(unit: &ast::BoolUnit, sites: &mut CallSites) { + match &unit.inner { + ast::BoolUnitInner::ComExpr(expr) => { + collect_expr_unit(&expr.left, sites); + collect_expr_unit(&expr.right, sites); + } + ast::BoolUnitInner::BoolExpr(expr) => collect_bool_expr(expr, sites), + ast::BoolUnitInner::BoolUOpExpr(expr) => collect_bool_unit(&expr.cond, sites), + } +} + +fn collect_bool_expr(expr: &ast::BoolExpr, sites: &mut CallSites) { + match &expr.inner { + ast::BoolExprInner::BoolBiOpExpr(expr) => { + collect_bool_expr(&expr.left, sites); + collect_bool_expr(&expr.right, sites); + } + ast::BoolExprInner::BoolUnit(unit) => collect_bool_unit(unit, sites), + } +} + +fn collect_right_val(val: &ast::RightVal, sites: &mut CallSites) { + match &val.inner { + ast::RightValInner::ArithExpr(expr) => collect_arith_expr(expr, sites), + ast::RightValInner::BoolExpr(expr) => collect_bool_expr(expr, sites), + } +} + +fn arith_op(op: &ast::ArithBiOp) -> &'static str { + match op { + ast::ArithBiOp::Add => "+", + ast::ArithBiOp::Sub => "-", + ast::ArithBiOp::Mul => "*", + ast::ArithBiOp::Div => "/", + } +} + +fn bool_op(op: &ast::BoolBiOp) -> &'static str { + match op { + ast::BoolBiOp::And => "&&", + ast::BoolBiOp::Or => "||", + } +} + +fn cmp_op(op: &ast::ComOp) -> &'static str { + match op { + ast::ComOp::Eq => "==", + ast::ComOp::Ne => "!=", + ast::ComOp::Gt => ">", + ast::ComOp::Ge => ">=", + ast::ComOp::Lt => "<", + ast::ComOp::Le => "<=", + } +} + +fn index_expr(expr: &ast::IndexExpr) -> String { + match &expr.inner { + ast::IndexExprInner::Num(n) => n.to_string(), + ast::IndexExprInner::Id(id) => id.clone(), + } +} + +pub(super) fn left_val(val: &ast::LeftVal) -> String { + match &val.inner { + ast::LeftValInner::Id(id) => id.clone(), + ast::LeftValInner::ArrayExpr(expr) => array_expr(expr), + ast::LeftValInner::MemberExpr(expr) => member_expr(expr), + } +} + +fn array_expr(expr: &ast::ArrayExpr) -> String { + format!("{}[{}]", left_val(&expr.arr), index_expr(&expr.idx)) +} + +fn member_expr(expr: &ast::MemberExpr) -> String { + format!("{}.{}", left_val(&expr.struct_id), expr.member_id) +} + +fn fn_call(call: &ast::FnCall) -> String { + let args = call + .vals + .iter() + .map(right_val) + .collect::>() + .join(", "); + format!("{}({args})", call.qualified_name()) +} + +fn expr_unit(unit: &ast::ExprUnit) -> String { + match &unit.inner { + ast::ExprUnitInner::Num(n) => n.to_string(), + ast::ExprUnitInner::Id(id) => id.clone(), + ast::ExprUnitInner::ArithExpr(expr) => format!("({})", arith_expr(expr)), + ast::ExprUnitInner::FnCall(call) => fn_call(call), + ast::ExprUnitInner::TraceCall(call) => format!("trace {}", fn_call(call)), + ast::ExprUnitInner::ArrayExpr(expr) => array_expr(expr), + ast::ExprUnitInner::MemberExpr(expr) => member_expr(expr), + ast::ExprUnitInner::Reference(id) => format!("&{id}"), + } +} + +fn arith_expr(expr: &ast::ArithExpr) -> String { + match &expr.inner { + ast::ArithExprInner::ArithBiOpExpr(expr) => format!( + "{} {} {}", + arith_expr(&expr.left), + arith_op(&expr.op), + arith_expr(&expr.right) + ), + ast::ArithExprInner::ExprUnit(unit) => expr_unit(unit), + } +} + +fn com_expr(expr: &ast::ComExpr) -> String { + format!( + "{} {} {}", + expr_unit(&expr.left), + cmp_op(&expr.op), + expr_unit(&expr.right) + ) +} + +pub(super) fn bool_unit(unit: &ast::BoolUnit) -> String { + match &unit.inner { + ast::BoolUnitInner::ComExpr(expr) => com_expr(expr), + ast::BoolUnitInner::BoolExpr(expr) => bool_expr(expr), + ast::BoolUnitInner::BoolUOpExpr(expr) => format!("!{}", bool_unit(&expr.cond)), + } +} + +fn bool_expr(expr: &ast::BoolExpr) -> String { + match &expr.inner { + ast::BoolExprInner::BoolBiOpExpr(expr) => format!( + "{} {} {}", + bool_expr(&expr.left), + bool_op(&expr.op), + bool_expr(&expr.right) + ), + ast::BoolExprInner::BoolUnit(unit) => bool_unit(unit), + } +} + +pub(super) fn right_val(val: &ast::RightVal) -> String { + match &val.inner { + ast::RightValInner::ArithExpr(expr) => arith_expr(expr), + ast::RightValInner::BoolExpr(expr) => bool_expr(expr), + } +} + +impl FunctionGenerator<'_> { + fn emit_trace_runtime(&mut self, name: &str, args: Vec) { + self.emit_call(name.to_string(), None, args); + } + + fn emit_trace_event(&mut self, indent: usize) { + self.emit_trace_runtime("__teac_trace_event", vec![Operand::from(indent as i32)]); + } + + fn emit_trace_text(&mut self, text: &str) { + for byte in text.bytes() { + self.emit_trace_runtime("__teac_trace_putch", vec![Operand::from(i32::from(byte))]); + } + } + + fn emit_trace_value(&mut self, value: Operand) { + match value.dtype() { + Dtype::I1 => self.emit_trace_runtime("__teac_trace_putbool", vec![value]), + Dtype::I32 => self.emit_trace_runtime("__teac_trace_putint", vec![value]), + dtype => unreachable!("trace value has unsupported dtype {dtype}"), + } + } + + pub(super) fn emit_trace_call_line( + &mut self, + indent: usize, + name: &str, + args: &[(String, Operand)], + ) { + self.emit_trace_event(indent); + self.emit_trace_text("call "); + self.emit_trace_text(name); + self.emit_trace_text("("); + for (i, (arg_name, value)) in args.iter().enumerate() { + if i > 0 { + self.emit_trace_text(", "); + } + self.emit_trace_text(arg_name); + self.emit_trace_text(" = "); + self.emit_trace_value(value.clone()); + } + self.emit_trace_text(")\n"); + } + + pub(super) fn emit_trace_function_enter(&mut self) { + self.emit_trace_runtime("__teac_trace_enter", vec![]); + } + + pub(super) fn emit_trace_begin_line(&mut self, name: &str, args: &[Operand]) { + self.emit_trace_runtime("__teac_trace_begin", vec![]); + self.emit_trace_event(0); + self.emit_trace_text("trace "); + self.emit_trace_text(name); + self.emit_trace_text("("); + for (i, value) in args.iter().enumerate() { + if i > 0 { + self.emit_trace_text(", "); + } + self.emit_trace_value(value.clone()); + } + self.emit_trace_text(")\n"); + } + + pub(super) fn emit_trace_end_line(&mut self) { + self.emit_trace_event(0); + self.emit_trace_text("end trace\n"); + self.emit_trace_runtime("__teac_trace_finish", vec![]); + } + + pub(super) fn emit_trace_function_return(&mut self, indent: usize, value: Option) { + self.emit_trace_event(indent); + self.emit_trace_text("return"); + if let Some(value) = value { + self.emit_trace_text(" "); + self.emit_trace_value(value); + } + self.emit_trace_text("\n"); + self.emit_trace_runtime("__teac_trace_leave", vec![]); + } + + pub(super) fn emit_trace_let_line(&mut self, indent: usize, name: &str, value: Operand) { + self.emit_trace_event(indent); + self.emit_trace_text("let "); + self.emit_trace_text(name); + self.emit_trace_text(" = "); + self.emit_trace_value(value); + self.emit_trace_text("\n"); + } + + pub(super) fn emit_trace_change_line( + &mut self, + indent: usize, + name: &str, + old: Operand, + new: Operand, + ) { + let print_label = self.alloc_basic_block(); + let after_label = self.alloc_basic_block(); + let cond = Operand::from(self.fresh_local(Dtype::I1)); + + self.emit_cmp(CmpPredicate::Ne, old.clone(), new.clone(), cond.clone()); + self.emit_cjump(cond, print_label.clone(), after_label.clone()); + + self.emit_label(print_label); + self.emit_trace_event(indent); + self.emit_trace_text(name); + self.emit_trace_text(": "); + self.emit_trace_value(old); + self.emit_trace_text(" -> "); + self.emit_trace_value(new); + self.emit_trace_text("\n"); + self.emit_jump(after_label.clone()); + + self.emit_label(after_label); + } + + pub(super) fn emit_trace_condition_line( + &mut self, + indent: usize, + kind: &str, + cond: &str, + value: Operand, + ) { + self.emit_trace_event(indent); + self.emit_trace_text(kind); + self.emit_trace_text(" "); + self.emit_trace_text(cond); + self.emit_trace_text(" -> "); + self.emit_trace_runtime("__teac_trace_putbool", vec![value]); + self.emit_trace_text("\n"); + } + + pub(super) fn emit_trace_else_line(&mut self, indent: usize) { + self.emit_trace_event(indent); + self.emit_trace_text("else\n"); + } + + pub(super) fn emit_trace_loop_slot(&mut self) -> Local { + let slot = self.fresh_local(Dtype::ptr_to(Dtype::I32)); + self.emit_alloca(Operand::from(&slot)); + self.emit_store(Operand::from(0), Operand::from(&slot)); + slot + } + + pub(super) fn bump_trace_loop_count(&mut self, slot: &Local) -> Operand { + let cur = Operand::from(self.fresh_local(Dtype::I32)); + let next = Operand::from(self.fresh_local(Dtype::I32)); + self.emit_load(cur.clone(), Operand::from(slot)); + self.emit_biop(ArithBinOp::Add, cur, Operand::from(1), next.clone()); + self.emit_store(next.clone(), Operand::from(slot)); + next + } + + pub(super) fn bump_trace_loop(&mut self, slot: &Local, indent: usize) -> Operand { + let next = self.bump_trace_loop_count(slot); + self.emit_trace_event(indent); + self.emit_trace_text("loop #"); + self.emit_trace_value(next.clone()); + self.emit_trace_text("\n"); + next + } +} diff --git a/src/ir/gen/type_infer.rs b/src/ir/gen/type_infer.rs index 9f025ca..0ddf7a8 100644 --- a/src/ir/gen/type_infer.rs +++ b/src/ir/gen/type_infer.rs @@ -164,6 +164,7 @@ impl TypeInference<'_> { ast::CodeBlockStmtInner::If(s) => self.process_if(s), ast::CodeBlockStmtInner::While(s) => self.process_while(s), ast::CodeBlockStmtInner::Call(s) => self.check_call_args(&s.fn_call), + ast::CodeBlockStmtInner::Trace(s) => self.check_call_args(&s.fn_call), ast::CodeBlockStmtInner::Return(s) => self.process_return(s), ast::CodeBlockStmtInner::Continue(_) | ast::CodeBlockStmtInner::Break(_) @@ -433,6 +434,7 @@ impl TypeInference<'_> { ast::ExprUnitInner::Id(id) => self.resolve_variable(id), ast::ExprUnitInner::ArithExpr(expr) => self.type_of_arith_expr(expr), ast::ExprUnitInner::FnCall(call) => self.type_of_fn_call(call), + ast::ExprUnitInner::TraceCall(call) => self.type_of_fn_call(call), ast::ExprUnitInner::ArrayExpr(expr) => self.type_of_array_expr(expr), ast::ExprUnitInner::MemberExpr(expr) => self.type_of_member_expr(expr), ast::ExprUnitInner::Reference(id) => self.type_of_reference(id), diff --git a/src/parser/expr.rs b/src/parser/expr.rs index 1a6d94b..2398045 100644 --- a/src/parser/expr.rs +++ b/src/parser/expr.rs @@ -448,10 +448,11 @@ impl<'a> ParseContext<'a> { /// precedence: /// 1. Negated integer literal: `-`. /// 2. Parenthesised arithmetic expression: `()`. - /// 3. Function call: ``. - /// 4. Plain integer literal: ``. - /// 5. Reference: `&`. - /// 6. Identifier with optional field/index suffixes (left-value chain). + /// 3. Traced function call: `trace `. + /// 4. Function call: ``. + /// 5. Plain integer literal: ``. + /// 6. Reference: `&`. + /// 7. Identifier with optional field/index suffixes (left-value chain). /// /// Returns [`Error::Grammar`] if none of the forms matches. /// @@ -489,6 +490,14 @@ impl<'a> ParseContext<'a> { })); } + // `trace ` — traced call in expression position. + if !filtered.is_empty() && filtered[0].as_rule() == Rule::trace_call { + return Ok(Box::new(ast::ExprUnit { + pos, + inner: ast::ExprUnitInner::TraceCall(self.parse_trace_call(filtered[0].clone())?), + })); + } + // `` — a function or method call. if !filtered.is_empty() && filtered[0].as_rule() == Rule::fn_call { return Ok(Box::new(ast::ExprUnit { @@ -598,6 +607,17 @@ impl<'a> ParseContext<'a> { Err(grammar_error("fn_call", &pair_for_error)) } + /// Parses a `trace_call` node into a boxed [`ast::FnCall`]. + fn parse_trace_call(&self, pair: Pair) -> ParseResult> { + let pair_for_error = pair.clone(); + for inner in pair.into_inner() { + if inner.as_rule() == Rule::fn_call { + return self.parse_fn_call(inner); + } + } + Err(grammar_error("trace_call", &pair_for_error)) + } + /// Parses a `module_prefixed_call` node into a boxed [`ast::FnCall`]. /// /// A module-prefixed call has the form `mod1::mod2::func(args)`. All diff --git a/src/parser/stmt.rs b/src/parser/stmt.rs index 7c16c63..b8de3f5 100644 --- a/src/parser/stmt.rs +++ b/src/parser/stmt.rs @@ -1,7 +1,7 @@ use crate::ast; +use super::common::{get_pos, grammar_error, Pair, ParseResult, Rule}; use super::ParseContext; -use super::common::{ParseResult, Pair, Rule, get_pos, grammar_error}; impl<'a> ParseContext<'a> { /// Parses a `code_block_stmt` node into a boxed [`ast::CodeBlockStmt`]. @@ -10,6 +10,7 @@ impl<'a> ParseContext<'a> { /// rule: /// * `var_decl_stmt` → [`Self::parse_var_decl_stmt`] /// * `assignment_stmt` → [`Self::parse_assignment_stmt`] + /// * `trace_stmt` → [`Self::parse_trace_stmt`] /// * `call_stmt` → [`Self::parse_call_stmt`] /// * `if_stmt` → [`Self::parse_if_stmt`] /// * `while_stmt` → [`Self::parse_while_stmt`] @@ -38,6 +39,11 @@ impl<'a> ParseContext<'a> { ), })); } + Rule::trace_stmt => { + return Ok(Box::new(ast::CodeBlockStmt { + inner: ast::CodeBlockStmtInner::Trace(self.parse_trace_stmt(inner)?), + })); + } Rule::call_stmt => { return Ok(Box::new(ast::CodeBlockStmt { inner: ast::CodeBlockStmtInner::Call(self.parse_call_stmt(inner)?), @@ -131,6 +137,20 @@ impl<'a> ParseContext<'a> { Err(grammar_error("call_stmt", &pair_for_error)) } + /// Parses a `trace_stmt` node into a boxed [`ast::TraceStmt`]. + fn parse_trace_stmt(&self, pair: Pair) -> ParseResult> { + let pair_for_error = pair.clone(); + for inner in pair.into_inner() { + if inner.as_rule() == Rule::fn_call { + return Ok(Box::new(ast::TraceStmt { + fn_call: self.parse_fn_call(inner)?, + })); + } + } + + Err(grammar_error("trace_stmt", &pair_for_error)) + } + /// Parses a `return_stmt` node into a boxed [`ast::ReturnStmt`]. /// /// The return value is optional: `return;` and `return expr;` are both @@ -245,8 +265,7 @@ impl<'a> ParseContext<'a> { } Ok(Box::new(ast::WhileStmt { - bool_unit: bool_unit - .ok_or_else(|| grammar_error("cond.bool_unit", &pair_for_error))?, + bool_unit: bool_unit.ok_or_else(|| grammar_error("cond.bool_unit", &pair_for_error))?, stmts, })) } diff --git a/src/tealang.pest b/src/tealang.pest index ded335d..80bd4a3 100644 --- a/src/tealang.pest +++ b/src/tealang.pest @@ -106,6 +106,8 @@ kw_return = @{ "return" ~ &(WHITESPACE | semicolon) } kw_i32 = @{ "i32" ~ &(WHITESPACE | semicolon | comma | rparen | lbrace | rbrace | rbracket | op_assign) } // Example: "use std;" kw_use = @{ "use" ~ WHITESPACE } +// Example: "trace lower_bound(6);" +kw_trace = @{ "trace" ~ WHITESPACE } // Identifier: starts with letter or underscore, followed by letters, digits, or underscores // Cannot be a keyword: this step is performed after the keyword is recognized. @@ -235,6 +237,7 @@ fn_def = { code_block_stmt = { var_decl_stmt | assignment_stmt + | trace_stmt | call_stmt | if_stmt | while_stmt @@ -256,6 +259,12 @@ call_stmt = { fn_call ~ semicolon } +// Trace statement: function call with compiler-generated execution trace +// Example: "trace lower_bound(6);" +trace_stmt = { + kw_trace ~ fn_call ~ semicolon +} + // Return statement: return from a function with or without a value // Examples: "return 0;", "return x + y;", "return;" return_stmt = { @@ -374,6 +383,7 @@ arith_mul_op = { op_mul | op_div } // Examples: "x", "10", "arr[i]", "node.value", "std::getint()", "0-x" (negative), "&arr" expr_unit = { lparen ~ arith_expr ~ rparen + | trace_call | fn_call | ampersand ~ identifier // address-of: &arr produces &[T] reference from array | op_sub ~ num // negative number @@ -408,6 +418,12 @@ fn_call = { | local_call } +// Function call wrapped in trace, usable as an expression. +// Example: "trace inc(4)" +trace_call = { + kw_trace ~ fn_call +} + // Function call with module prefix, supporting multi-level paths // Examples: "std::getint()", "std::putch(10)", "a::b::func(x)" module_prefixed_call = { diff --git a/tests/std/std.c b/tests/std/std.c index 4aebe3d..5a98842 100644 --- a/tests/std/std.c +++ b/tests/std/std.c @@ -3,6 +3,7 @@ #include #define MAX_TIMERS 1024 +#define TEAC_TRACE_LIMIT 1000 static struct timeval timer_start_ts; static struct timeval timer_end_ts; @@ -13,6 +14,10 @@ static int timer_m[MAX_TIMERS]; static int timer_s[MAX_TIMERS]; static int timer_us[MAX_TIMERS]; static int timer_idx; +static int teac_trace_active; +static int teac_trace_events; +static int teac_trace_stopped; +static int teac_trace_depth; int getint(void) { int t; @@ -37,6 +42,53 @@ float getfloat(void) { void putfloat(float a) { printf("%f", a); } +void __teac_trace_begin(void) { + teac_trace_active = 1; + teac_trace_events = 0; + teac_trace_stopped = 0; + teac_trace_depth = 0; +} + +void __teac_trace_finish(void) { + teac_trace_active = 0; + teac_trace_depth = 0; +} + +void __teac_trace_enter(void) { + if (teac_trace_active) teac_trace_depth++; +} + +void __teac_trace_leave(void) { + if (teac_trace_active) teac_trace_depth--; +} + +void __teac_trace_event(int indent) { + if (!teac_trace_active) return; + if (teac_trace_events >= TEAC_TRACE_LIMIT) { + if (!teac_trace_stopped) { + printf("trace stopped: event limit reached\n"); + teac_trace_stopped = 1; + } + teac_trace_active = 0; + return; + } + teac_trace_events++; + indent += teac_trace_depth; + for (int i = 0; i < indent; i++) printf(" "); +} + +void __teac_trace_putch(int c) { + if (teac_trace_active) printf("%c", c); +} + +void __teac_trace_putint(int v) { + if (teac_trace_active) printf("%d", v); +} + +void __teac_trace_putbool(int v) { + if (teac_trace_active) printf("%s", v ? "true" : "false"); +} + void putarray(int n, int a[]) { printf("%d:", n); for (int i = 0; i < n; i++) printf(" %d", a[i]); diff --git a/tests/tests.rs b/tests/tests.rs index eba5c23..5617518 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -921,6 +921,11 @@ full_tests! { sort_test5, sort_test7, sort, + trace_array, + trace_basic, + trace_nested_tree, + trace_recursive, + trace_value, unique_path, type_infer_basic, } diff --git a/tests/trace_array/std.teah b/tests/trace_array/std.teah new file mode 100644 index 0000000..26dde53 --- /dev/null +++ b/tests/trace_array/std.teah @@ -0,0 +1,7 @@ +fn getint()->i32; + +fn getch()->i32; +fn timer_start(lineno:i32); +fn timer_stop(lineno:i32); +fn putint(a:i32); +fn putch(a:i32); diff --git a/tests/trace_array/trace_array.out b/tests/trace_array/trace_array.out new file mode 100644 index 0000000..03eaf30 --- /dev/null +++ b/tests/trace_array/trace_array.out @@ -0,0 +1,6 @@ +trace update() +call update() + a[1]: 1 -> 4 + return 4 +end trace +0 diff --git a/tests/trace_array/trace_array.tea b/tests/trace_array/trace_array.tea new file mode 100644 index 0000000..ecdc335 --- /dev/null +++ b/tests/trace_array/trace_array.tea @@ -0,0 +1,12 @@ +use std; + +fn update() -> i32 { + let a:[i32; 3] = [3, 1, 2]; + a[1] = 4; + return a[1]; +} + +fn main() -> i32 { + trace update(); + return 0; +} diff --git a/tests/trace_basic/std.teah b/tests/trace_basic/std.teah new file mode 100644 index 0000000..26dde53 --- /dev/null +++ b/tests/trace_basic/std.teah @@ -0,0 +1,7 @@ +fn getint()->i32; + +fn getch()->i32; +fn timer_start(lineno:i32); +fn timer_stop(lineno:i32); +fn putint(a:i32); +fn putch(a:i32); diff --git a/tests/trace_basic/trace_basic.out b/tests/trace_basic/trace_basic.out new file mode 100644 index 0000000..0595b90 --- /dev/null +++ b/tests/trace_basic/trace_basic.out @@ -0,0 +1,25 @@ +trace lower_bound(3) +call lower_bound(x = 3) + let l = 0 + let r = 5 + while l < r -> true + loop #1 + let m = 2 + if m < x -> true + l: 0 -> 3 + while l < r -> true + loop #2 + let m = 4 + if m < x -> false + else + r: 5 -> 4 + while l < r -> true + loop #3 + let m = 3 + if m < x -> false + else + r: 4 -> 3 + while l < r -> false + return 3 +end trace +0 diff --git a/tests/trace_basic/trace_basic.tea b/tests/trace_basic/trace_basic.tea new file mode 100644 index 0000000..807483c --- /dev/null +++ b/tests/trace_basic/trace_basic.tea @@ -0,0 +1,20 @@ +use std; + +fn lower_bound(x:i32) -> i32 { + let l:i32 = 0; + let r:i32 = 5; + while l < r { + let m:i32 = (l + r) / 2; + if m < x { + l = m + 1; + } else { + r = m; + } + } + return l; +} + +fn main() -> i32 { + trace lower_bound(3); + return 0; +} diff --git a/tests/trace_nested_tree/std.teah b/tests/trace_nested_tree/std.teah new file mode 100644 index 0000000..26dde53 --- /dev/null +++ b/tests/trace_nested_tree/std.teah @@ -0,0 +1,7 @@ +fn getint()->i32; + +fn getch()->i32; +fn timer_start(lineno:i32); +fn timer_stop(lineno:i32); +fn putint(a:i32); +fn putch(a:i32); diff --git a/tests/trace_nested_tree/trace_nested_tree.out b/tests/trace_nested_tree/trace_nested_tree.out new file mode 100644 index 0000000..e13d836 --- /dev/null +++ b/tests/trace_nested_tree/trace_nested_tree.out @@ -0,0 +1,10 @@ +trace twice(3) +call twice(x = 3) + call inc(x = 3) + return 4 + let y = 4 + call inc(x = 4) + return 5 + return 5 +end trace +0 diff --git a/tests/trace_nested_tree/trace_nested_tree.tea b/tests/trace_nested_tree/trace_nested_tree.tea new file mode 100644 index 0000000..c489398 --- /dev/null +++ b/tests/trace_nested_tree/trace_nested_tree.tea @@ -0,0 +1,15 @@ +use std; + +fn inc(x:i32) -> i32 { + return x + 1; +} + +fn twice(x:i32) -> i32 { + let y:i32 = inc(x); + return inc(y); +} + +fn main() -> i32 { + trace twice(3); + return 0; +} diff --git a/tests/trace_recursive/std.teah b/tests/trace_recursive/std.teah new file mode 100644 index 0000000..26dde53 --- /dev/null +++ b/tests/trace_recursive/std.teah @@ -0,0 +1,7 @@ +fn getint()->i32; + +fn getch()->i32; +fn timer_start(lineno:i32); +fn timer_stop(lineno:i32); +fn putint(a:i32); +fn putch(a:i32); diff --git a/tests/trace_recursive/trace_recursive.out b/tests/trace_recursive/trace_recursive.out new file mode 100644 index 0000000..adbd9e8 --- /dev/null +++ b/tests/trace_recursive/trace_recursive.out @@ -0,0 +1,12 @@ +trace down(2) +call down(n = 2) + if n == 0 -> false + call down(n = 1) + if n == 0 -> false + call down(n = 0) + if n == 0 -> true + return 0 + return 0 + return 0 +end trace +0 diff --git a/tests/trace_recursive/trace_recursive.tea b/tests/trace_recursive/trace_recursive.tea new file mode 100644 index 0000000..73de310 --- /dev/null +++ b/tests/trace_recursive/trace_recursive.tea @@ -0,0 +1,13 @@ +use std; + +fn down(n:i32) -> i32 { + if n == 0 { + return 0; + } + return down(n - 1); +} + +fn main() -> i32 { + trace down(2); + return 0; +} diff --git a/tests/trace_value/std.teah b/tests/trace_value/std.teah new file mode 100644 index 0000000..133ebb3 --- /dev/null +++ b/tests/trace_value/std.teah @@ -0,0 +1,4 @@ +fn putint(x:i32); +fn putch(x:i32); +fn getint() -> i32; +fn getch() -> i32; diff --git a/tests/trace_value/trace_value.out b/tests/trace_value/trace_value.out new file mode 100644 index 0000000..0cda570 --- /dev/null +++ b/tests/trace_value/trace_value.out @@ -0,0 +1,5 @@ +trace inc(4) +call inc(x = 4) + return 5 +end trace +50 diff --git a/tests/trace_value/trace_value.tea b/tests/trace_value/trace_value.tea new file mode 100644 index 0000000..90e25bf --- /dev/null +++ b/tests/trace_value/trace_value.tea @@ -0,0 +1,11 @@ +use std; + +fn inc(x:i32) -> i32 { + return x + 1; +} + +fn main() -> i32 { + let x:i32 = trace inc(4); + std::putint(x); + return 0; +}