diff --git a/src/ast/unit.rs b/src/ast/unit.rs index addf195..68165d5 100644 --- a/src/ast/unit.rs +++ b/src/ast/unit.rs @@ -1,3 +1,4 @@ +use crate::ast::EventDecl; use crate::ir::ctx::CtxMethod; use crate::parser::SourceLoc; @@ -9,6 +10,7 @@ pub struct Unit { pub sections: Vec, pub kind: ProgramKind, pub license: Option, + pub events: Vec, pub body: Vec, } @@ -122,8 +124,10 @@ pub enum ExprKind { SizeOf { name: String, }, - - + FieldAccess { + base: Box, + field: String, + }, } #[derive(Debug, Clone)] @@ -148,7 +152,7 @@ pub struct HeapLookup { #[derive(Debug, Clone)] pub struct MethodCall { - pub receiver: String, + pub receiver: Box, pub method: String, pub arg: Vec, } @@ -202,7 +206,13 @@ impl ProgramKind { ProgramKind::Socket => &[LoadU8, LoadU16, LoadU32], - ProgramKind::Kprobe => &[LoadU64, GetPidTgid, GetUidGid], + ProgramKind::Kprobe => &[ + LoadU64, + GetPidTgid, + GetUidGid, + ProbeReadUserStr, + ProbeReadKernelStr, + ], ProgramKind::Tracepoint => &[ LoadU8, @@ -218,9 +228,11 @@ impl ProgramKind { GetCurrentComm, GetCurrentTask, GetKtimeNs, + ProbeReadUserStr, + ProbeReadKernelStr, ], - ProgramKind::RawTracepoint => &[LoadU64], + ProgramKind::RawTracepoint => &[LoadU64, ProbeReadUserStr, ProbeReadKernelStr], ProgramKind::Fentry | ProgramKind::Fexit => &[LoadU64], diff --git a/src/compiler.rs b/src/compiler.rs index f0e44da..ea5cd3c 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -48,26 +48,27 @@ impl ErrorHandler { .build(span, &self.source_manager) } - // Create a lexical error diagnostic - // pub fn lexical_error( - // &self, - // code: ErrorCode, - // message: impl Into, - // span: &Span, - // ) -> Result { - // DiagnosticBuilder::new(ErrorCategory::Lexical, code, message) - // .build(span, &self.source_manager) - // } - - // Create a codegen error diagnostic - // pub fn codegen_error( - // &self, - // message: impl Into, - // span: &Span, - // ) -> Result { - // DiagnosticBuilder::new(ErrorCategory::Codegen, ErrorCode::CodegenError, message) - // .build(span, &self.source_manager) - // } + /// Create a lexical error diagnostic + pub fn lexical_error( + &self, + code: ErrorCode, + message: impl Into, + span: &Span, + ) -> Result { + DiagnosticBuilder::new(ErrorCategory::Lexical, code, message) + .build(span, &self.source_manager) + } + + /// Create a codegen error diagnostic + pub fn codegen_error( + &self, + message: impl Into, + span: &Span, + ) -> Result { + DiagnosticBuilder::new(ErrorCategory::Codegen, ErrorCode::CodegenError, message) + .build(span, &self.source_manager) + } + } pub fn compile(input_path: &Path, _output_path: &Path) -> Result<(), miette::Report> { @@ -105,7 +106,24 @@ pub fn compile(input_path: &Path, _output_path: &Path) -> Result<(), miette::Rep Ok(prog) => prog, Err(e) => { let span = Span::new(_file_id, e.0.offset..e.0.offset + 1); - match error_handler.parse_error(e.1.clone(), &span) { + + let error_result = if e.1.contains("Invalid character") + || e.1.contains("Unterminated") + || e.1.contains("Invalid escape sequence") + || e.1.contains("Unexpected character") { + let code = match e.1.as_str() { + msg if msg.contains("Invalid character") => ErrorCode::InvalidCharacter, + msg if msg.contains("Unterminated comment") => ErrorCode::UnterminatedComment, + msg if msg.contains("Unterminated string") => ErrorCode::UnterminatedString, + msg if msg.contains("Invalid escape sequence") => ErrorCode::InvalidEscapeSequence, + _ => ErrorCode::InvalidCharacter, + }; + error_handler.lexical_error(code, e.1.clone(), &span) + } else { + error_handler.parse_error(e.1.clone(), &span) + }; + + match error_result { Ok(diag) => { let report = error_handler.report_error(diag); eprintln!("{}", report); @@ -119,8 +137,7 @@ pub fn compile(input_path: &Path, _output_path: &Path) -> Result<(), miette::Rep } } }; - - // Run semantic analysis and emit a CompileDiagnostic with source span on error + if let Err(sem_err) = crate::sema::check_program(&program) { use crate::diagnostics::ErrorCode; use crate::parser::SourceLoc; @@ -258,9 +275,24 @@ pub fn compile(input_path: &Path, _output_path: &Path) -> Result<(), miette::Rep .map_err(|e| miette::miette!("{:?}", e)) .wrap_err("Lowering failed")?; - crate::emit::ebpf_c::program::emit_program(&program_ir, &output) - .map_err(|e| miette::miette!("{}", e)) - .wrap_err("Emit failed")?; + // Emit eBPF code with stage-aware error handling + if let Err(e) = crate::emit::ebpf_c::program::emit_program(&program_ir, &output) { + let span = Span::new(_file_id, 0..1); + let error_msg = format!("Code generation failed: {}", e); + + match error_handler.codegen_error(error_msg.clone(), &span) { + Ok(diag) => { + let report = error_handler.report_error(diag); + eprintln!("{}", report); + return Err(report); + } + Err(_) => { + let report = miette::miette!("{}", error_msg); + eprintln!("{}", report); + return Err(report); + } + } + } Ok(()) } diff --git a/src/diagnostics/category.rs b/src/diagnostics/category.rs index c917e6e..fd6f7d2 100644 --- a/src/diagnostics/category.rs +++ b/src/diagnostics/category.rs @@ -1,7 +1,34 @@ -//! Error categories for the Solnix compiler -//! -//! Each error is classified into one of these categories to help users -//! understand where in the compilation pipeline the error occurred. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ErrorType { + /// Invalid token or lexical error (from Lexer stage) + InvalidToken, + /// Syntax error (from Parser stage) + SyntaxError, + /// Type error or semantic error (from Semantic stage) + TypeError, + /// Internal error from IR/Codegen stage + InternalError, + /// I/O error + IoError, +} + +impl ErrorType { + pub fn as_str(&self) -> &'static str { + match self { + Self::InvalidToken => "Invalid token", + Self::SyntaxError => "Syntax error", + Self::TypeError => "Type error", + Self::InternalError => "Internal error", + Self::IoError => "I/O error", + } + } +} + +impl std::fmt::Display for ErrorType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_str()) + } +} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ErrorCategory { @@ -30,6 +57,17 @@ impl ErrorCategory { Self::Io => "I/O", } } + + /// Map compilation stage category to a clean, user-friendly error type + pub fn to_error_type(&self) -> ErrorType { + match self { + Self::Lexical => ErrorType::InvalidToken, + Self::Parse => ErrorType::SyntaxError, + Self::Semantic => ErrorType::TypeError, + Self::Codegen | Self::Optimizer => ErrorType::InternalError, + Self::Io => ErrorType::IoError, + } + } } impl std::fmt::Display for ErrorCategory { diff --git a/src/diagnostics/diagnostic.rs b/src/diagnostics/diagnostic.rs index a42037b..5cf384f 100644 --- a/src/diagnostics/diagnostic.rs +++ b/src/diagnostics/diagnostic.rs @@ -1,11 +1,11 @@ -use super::category::ErrorCategory; +use super::category::{ErrorCategory, ErrorType}; use super::error_code::ErrorCode; use super::source_manager::{Span, SourceManager}; use miette::{Diagnostic, SourceSpan}; use thiserror::Error; #[derive(Error, Debug, Diagnostic)] -#[error("{category} error: {code}\n {message}")] +#[error("{error_type}: {code}\n {message}")] pub struct CompileDiagnostic { #[source_code] pub file_content: String, @@ -15,6 +15,7 @@ pub struct CompileDiagnostic { pub code: String, pub category: ErrorCategory, + pub error_type: ErrorType, pub message: String, pub label_message: String, } @@ -37,6 +38,11 @@ impl DiagnosticBuilder { } } + pub fn with_label(mut self, label: impl Into) -> Self { + self.label_message = Some(label.into()); + self + } + pub fn build( self, span: &Span, @@ -56,6 +62,7 @@ impl DiagnosticBuilder { span: span.to_source_span(), code: self.code.code(), category: self.category, + error_type: self.category.to_error_type(), message: self.message, label_message, }) diff --git a/src/diagnostics/mod.rs b/src/diagnostics/mod.rs index b86fe3c..bf3ee1d 100644 --- a/src/diagnostics/mod.rs +++ b/src/diagnostics/mod.rs @@ -4,7 +4,7 @@ pub mod error_code; pub mod source_manager; // Re-export commonly used types -pub use category::ErrorCategory; +pub use category::{ErrorCategory, ErrorType}; pub use diagnostic::{CompileDiagnostic, DiagnosticBuilder}; pub use error_code::ErrorCode; pub use source_manager::{FileId, Span, SourceManager}; diff --git a/src/emit/ebpf_c/tracepoint.rs b/src/emit/ebpf_c/tracepoint.rs index da8f344..69aa810 100644 --- a/src/emit/ebpf_c/tracepoint.rs +++ b/src/emit/ebpf_c/tracepoint.rs @@ -10,7 +10,7 @@ pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), writeln!(out, "int {}(void *ctx) {{", unit.name).map_err(err)?; writeln!(out, " (void)ctx;").map_err(err)?; writeln!(out).map_err(err)?; - + let mut used_vars: HashSet = HashSet::new(); for block in &unit.blocks { @@ -37,7 +37,6 @@ pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), } } - let mut branch_cond_vars: HashSet = HashSet::new(); for block in &unit.blocks { if let crate::ir::unit::Terminator::Branch { condition, .. } = &block.terminator { @@ -46,14 +45,14 @@ pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), } } } - + let mut var_types: HashMap = HashMap::new(); for block in &unit.blocks { for inst in &block.instructions { var_types.entry(inst.result.0).or_insert(inst.result_type); } } - + let mut pointer_vars: HashSet = HashSet::new(); for block in &unit.blocks { for inst in &block.instructions { @@ -62,10 +61,10 @@ pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), } } } - + let mut vars_sorted: Vec = used_vars.iter().copied().collect(); vars_sorted.sort_unstable(); - + let mut temp_counter: u32 = 0; let mut temps: Vec<(String, String)> = Vec::new(); @@ -79,7 +78,7 @@ pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), temps.push((name.clone(), init.to_string())); name } - + let mut imm_addr_cache: HashMap<(u32, String), String> = HashMap::new(); let mut inst_index: u32 = 0; @@ -118,7 +117,7 @@ pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), inst_index += 1; } } - + for &id in vars_sorted.iter() { let ty = var_types.get(&id).copied().unwrap_or(crate::ast::Type::U64); let c_type = match ty { @@ -142,7 +141,7 @@ pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), if !vars_sorted.is_empty() || !temps.is_empty() { writeln!(out).map_err(err)?; } - + writeln!(out, " goto __block_{};", unit.blocks[0].id.0).map_err(err)?; writeln!(out).map_err(err)?; @@ -150,7 +149,7 @@ pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), for b in &unit.blocks { block_ids.insert(b.id.0); } - + inst_index = 0; for block in &unit.blocks { writeln!(out, "__block_{}:", block.id.0).map_err(err)?; @@ -167,7 +166,7 @@ pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), inst.opcode, Opcode::Store { .. } | Opcode::UpdateMap { .. } | Opcode::NullCheck ); - + if !is_side_effect && !res_used { inst_index += 1; continue; @@ -197,15 +196,15 @@ pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), if let Some(operand) = inst.operands.get(0) { let ptr = format_operand(operand); let res = res_name.unwrap(); - writeln!(out, " {} = *{};", res, ptr).map_err(err)?; + writeln!(out, " {} = *((__u64 *)(void *)({}));", res, ptr).map_err(err)?; } } - Opcode::Store { .. } => { + Opcode::Store { size } => { if inst.operands.len() >= 2 { let ptr = format_operand(&inst.operands[0]); let val = format_operand(&inst.operands[1]); - writeln!(out, " *{} = {};", ptr, val).map_err(err)?; + writeln!(out, " *((__u{} *)(void *)({})) = {};", size * 8, ptr, val).map_err(err)?; } } @@ -327,7 +326,7 @@ pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), Opcode::NullCheck => { if let Some(ptr_op) = inst.operands.get(0) { let ptr_expr = format_operand(ptr_op); - + if branch_cond_vars.contains(&inst.result.0) { let res = res_name.unwrap_or_else(|| format!("v{}", inst.result.0)); writeln!(out, " {} = ({} != 0);", res, ptr_expr).map_err(err)?; @@ -341,6 +340,24 @@ pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), } } } + + Opcode::RingBufReserve { map_name, size } => { + let res = res_name.unwrap(); + + writeln!( + out, + " {} = (__u64)bpf_ringbuf_reserve(&{}, {}, 0);", + res, map_name, size + ) + .map_err(err)?; + } + Opcode::RingBufSubmit { map_name: _ } => { + if let Some(ptr_op) = inst.operands.get(0) { + let ptr = format_operand(ptr_op); + + writeln!(out, " bpf_ringbuf_submit({}, 0);", ptr).map_err(err)?; + } + } } inst_index += 1; diff --git a/src/ir/ctx.rs b/src/ir/ctx.rs index 3527f4b..a315b6d 100644 --- a/src/ir/ctx.rs +++ b/src/ir/ctx.rs @@ -24,6 +24,12 @@ pub enum CtxMethod { // Time helpers // ================================ GetKtimeNs, // bpf_ktime_get_ns + + // ================================ + // Memory probe helpers + // ================================ + ProbeReadUserStr, // bpf_probe_read_user_str (helper 202) + ProbeReadKernelStr, // bpf_probe_read_kernel_str (helper 204) } @@ -49,6 +55,10 @@ impl CtxMethod { // Time helpers "get_ktime_ns" => Some(Self::GetKtimeNs), + // Memory probe helpers + "probe_read_user_str" => Some(Self::ProbeReadUserStr), + "probe_read_kernel_str" => Some(Self::ProbeReadKernelStr), + _ => None, } } diff --git a/src/ir/instruction.rs b/src/ir/instruction.rs index 647c831..1dc8798 100644 --- a/src/ir/instruction.rs +++ b/src/ir/instruction.rs @@ -30,8 +30,11 @@ pub enum Opcode { Binary { op: BinaryOp }, // maps - CallMap { map_name: String }, // lookup -> returns pointer (u64) - UpdateMap { map_name: String }, // update(key, value) -> returns status (u64) + CallMap { map_name: String }, // lookup -> returns pointer (u64) + UpdateMap { map_name: String }, // update(key, value) -> returns status (u64) + + RingBufReserve { map_name: String, size: u32 }, + RingBufSubmit { map_name: String }, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src/ir/program.rs b/src/ir/program.rs index 477055e..dc155de 100644 --- a/src/ir/program.rs +++ b/src/ir/program.rs @@ -5,17 +5,29 @@ use crate::ast::{MapDecl, Program}; pub struct ProgramIr { pub maps: Vec, pub units: Vec, + pub events: std::collections::HashMap, } pub fn lower_program(program: &Program) -> Result { let mut units = Vec::new(); + + // Build event map at program level + let mut event_sizes = std::collections::HashMap::new(); + let mut event_decls = std::collections::HashMap::new(); + + for event in &program.events { + let size = crate::sema::event::compute_event_size(event); + event_sizes.insert(event.name.clone(), size); + event_decls.insert(event.name.clone(), event.clone()); + } for unit in &program.units { - units.push(UnitIr::lower(unit)?); + units.push(UnitIr::lower(unit, &event_sizes, &event_decls)?); } Ok(ProgramIr { maps: program.maps.clone(), units, + events: event_sizes, }) } diff --git a/src/ir/unit.rs b/src/ir/unit.rs index 1b161c3..2cf207a 100644 --- a/src/ir/unit.rs +++ b/src/ir/unit.rs @@ -12,7 +12,6 @@ pub struct UnitIr { pub blocks: Vec, pub next_var_id: u32, pub program_type: ProgramKind, - next_block_id: u32, } @@ -43,7 +42,11 @@ struct LowerCtx { } impl UnitIr { - pub fn lower(unit: &Unit) -> Result { + pub fn lower( + unit: &Unit, + events: &std::collections::HashMap, + event_decls: &std::collections::HashMap, + ) -> Result { let program_kind = unit.kind; let mut ir = Self { @@ -69,7 +72,7 @@ impl UnitIr { }; for stmt in &unit.body { - lower_statement(stmt, &mut ctx, &mut ir, &mut current_block)?; + lower_statement(stmt, &mut ctx, &mut ir, &mut current_block, events, event_decls)?; } ir.blocks.push(current_block); @@ -105,7 +108,7 @@ fn lower_ctx_helper( CtxMethod::GetCurrentTask => (35, crate::ast::Type::U64), CtxMethod::GetKtimeNs => (5, crate::ast::Type::U64), - // Not helpers + // Not helpers - these are handled separately CtxMethod::LoadU8 | CtxMethod::LoadU16 | CtxMethod::LoadU32 @@ -113,9 +116,11 @@ fn lower_ctx_helper( | CtxMethod::LoadI8 | CtxMethod::LoadI16 | CtxMethod::LoadI32 - | CtxMethod::LoadI64 => { + | CtxMethod::LoadI64 + | CtxMethod::ProbeReadUserStr + | CtxMethod::ProbeReadKernelStr => { return Err(LoweringError::UnitLowering(format!( - "Internal error: load_* is not a helper call: {:?}", + "Internal error: {:?} is not a simple helper call", method ))); } @@ -141,12 +146,14 @@ fn lower_statement( ctx: &mut LowerCtx, ir: &mut UnitIr, block: &mut BasicBlock, + events: &std::collections::HashMap, + event_decls: &std::collections::HashMap, ) -> Result<(), LoweringError> { match &stmt.kind { StmtKind::VarDecl(var_decl) => { let ty: crate::ast::Type = vartype_to_type(&var_decl.var_type)?; let var_id = ir.alloc_var(ty.clone()); - let value = lower_expr(&var_decl.value, ctx, ir, block)?; + let value = lower_expr(&var_decl.value, ctx, ir, block, events, event_decls)?; ctx.vars.insert(var_decl.name.clone(), var_id); @@ -160,49 +167,113 @@ fn lower_statement( } StmtKind::Return(expr) => { - let ret_value = lower_expr(expr, ctx, ir, block)?; + let ret_value = lower_expr(expr, ctx, ir, block, events, event_decls)?; block.terminator = Terminator::Return(ret_value); } StmtKind::HeapVarDecl(heap_decl) => { - let (map_name, key_expr) = match &heap_decl.init.kind { - ExprKind::MethodCall(call) if call.method == "lookup" => { - if call.arg.len() != 1 { - return Err(LoweringError::UnitLowering(format!( - "map.lookup expects 1 argument, got {}", - call.arg.len() - ))); + match &heap_decl.init.kind { + ExprKind::MethodCall(call) => { + let receiver_name = if let ExprKind::Variable(name) = &call.receiver.kind { + name.clone() + } else { + return Err(LoweringError::UnitLowering( + "heap initializer receiver must be identifier".to_string(), + )); + }; + + match call.method.as_str() { + // ---------------------------- + // map.lookup(key) + // ---------------------------- + "lookup" => { + if call.arg.len() != 1 { + return Err(LoweringError::UnitLowering(format!( + "{}.lookup expects 1 argument", + receiver_name + ))); + } + + let key = lower_expr(&call.arg[0], ctx, ir, block, events, event_decls)?; + let result = ir.alloc_var(crate::ast::Type::U64); + + block.instructions.push(Instruction { + result, + opcode: Opcode::CallMap { + map_name: receiver_name.clone(), + }, + operands: vec![key], + result_type: crate::ast::Type::U64, + }); + + ctx.vars.insert(heap_decl.name.clone(), result); + ctx.map_ptr_vars.insert(result); + } + + // ---------------------------- + // map.reserve(event_type) + // ---------------------------- + "reserve" => { + if call.arg.len() != 1 { + return Err(LoweringError::UnitLowering(format!( + "{}.reserve expects event type name", + receiver_name + ))); + } + + let event_name = if let ExprKind::Variable(name) = &call.arg[0].kind { + name.clone() + } else { + return Err(LoweringError::UnitLowering( + "reserve requires event type name".to_string(), + )); + }; + + let size = *events.get(&event_name).ok_or_else(|| { + LoweringError::UnitLowering(format!( + "Unknown event type: {}", + event_name + )) + })?; + + let result = ir.alloc_var(crate::ast::Type::U64); + + block.instructions.push(Instruction { + result, + opcode: Opcode::RingBufReserve { + map_name: receiver_name.clone(), + size, + }, + operands: vec![], + result_type: crate::ast::Type::U64, + }); + + ctx.vars.insert(heap_decl.name.clone(), result); + ctx.map_ptr_vars.insert(result); + } + + _ => { + return Err(LoweringError::UnitLowering(format!( + "heap var must be initialized with map.lookup or map.reserve" + ))); + } } - (call.receiver.clone(), &call.arg[0]) } - ExprKind::HeapLookup(hl) => (hl.map_name.clone(), hl.key_expr.as_ref()), + _ => { return Err(LoweringError::UnitLowering( - "heap var must be initialized with map.lookup(key)".to_string(), + "heap var must be initialized with map.lookup or map.reserve".to_string(), )); } - }; - - let key = lower_expr(key_expr, ctx, ir, block)?; - let result = ir.alloc_var(crate::ast::Type::U64); - - block.instructions.push(Instruction { - result, - opcode: Opcode::CallMap { map_name }, - operands: vec![key], - result_type: crate::ast::Type::U64, - }); - - ctx.vars.insert(heap_decl.name.clone(), result); - ctx.map_ptr_vars.insert(result); // mark as pointer from map + } } StmtKind::Assignment(assign) => { - let value = lower_expr(&assign.value, ctx, ir, block)?; + let value = lower_expr(&assign.value, ctx, ir, block, events, event_decls)?; match &assign.target.kind { ExprKind::Dereference(ptr_expr) => { - let ptr = lower_expr(ptr_expr, ctx, ir, block)?; + let ptr = lower_expr(ptr_expr, ctx, ir, block, events, event_decls)?; let needs_null_check = matches!(ptr, Operand::Var(v) if ctx.map_ptr_vars.contains(&v)); @@ -309,6 +380,52 @@ fn lower_statement( ctx.vars.insert(var_name.clone(), new_id); } + ExprKind::FieldAccess { base, field } => { + // Handle field assignment like evt.pid = value + // First, get the base pointer/variable + let base_operand = lower_expr(base, ctx, ir, block, events, event_decls)?; + + // Get the variable ID (should be a pointer from reserve) + let base_var = if let Operand::Var(v) = base_operand { + v + } else { + return Err(LoweringError::UnitLowering( + "Field access requires a pointer variable".to_string(), + )); + }; + + // Look up field offset across all events + let mut field_offset: Option = None; + for (_, event_decl) in event_decls { + if let Some(offset) = crate::sema::event::compute_field_offset(event_decl, field) { + field_offset = Some(offset); + break; + } + } + + let _offset = field_offset.ok_or_else(|| { + LoweringError::UnitLowering(format!("Unknown field: {}", field)) + })?; + + // For now, we only support simple field assignments (no compound ops on fields) + if !matches!(assign.op, crate::ast::AssignmentOp::Assign) { + return Err(LoweringError::UnitLowering( + "Compound assignment operators not supported on struct fields".to_string(), + )); + } + + // Create a store instruction for the field + // TODO: Currently we store at the base pointer with fixed size + // In the future, we should track field offsets and sizes per event type + let result = ir.alloc_var(crate::ast::Type::U64); + block.instructions.push(Instruction { + result, + opcode: Opcode::Store { size: 8 }, + operands: vec![Operand::Var(base_var), value.clone()], + result_type: crate::ast::Type::U64, + }); + } + _ => { return Err(LoweringError::UnitLowering( "Invalid assignment target".to_string(), @@ -363,7 +480,7 @@ fn lower_statement( terminator: Terminator::Jump(merge_id), }; for s in &if_guard.then_body { - lower_statement(s, ctx, ir, &mut tb)?; + lower_statement(s, ctx, ir, &mut tb, events, event_decls)?; } if !matches!( tb.terminator, @@ -378,7 +495,7 @@ fn lower_statement( }; if let Some(else_body) = &if_guard.else_body { for s in else_body { - lower_statement(s, ctx, ir, &mut eb)?; + lower_statement(s, ctx, ir, &mut eb, events, event_decls)?; } } if !matches!( @@ -393,7 +510,7 @@ fn lower_statement( } StmtKind::ExprStmt(expr) => { - let _ = lower_expr(expr, ctx, ir, block)?; + let _ = lower_expr(expr, ctx, ir, block, events, event_decls)?; } } Ok(()) @@ -404,6 +521,8 @@ fn lower_expr( ctx: &mut LowerCtx, ir: &mut UnitIr, block: &mut BasicBlock, + events: &std::collections::HashMap, + event_decls: &std::collections::HashMap, ) -> Result { match &expr.kind { ExprKind::Variable(name) => { @@ -416,7 +535,7 @@ fn lower_expr( ExprKind::Number(n) => Ok(Operand::Immediate(*n)), ExprKind::HeapLookup(hl) => { - let key = lower_expr(&hl.key_expr, ctx, ir, block)?; + let key = lower_expr(&hl.key_expr, ctx, ir, block, events, event_decls)?; let result = ir.alloc_var(crate::ast::Type::U64); block.instructions.push(Instruction { @@ -433,7 +552,15 @@ fn lower_expr( } ExprKind::MethodCall(call) => { - if call.receiver == "ctx" { + let receiver_name = if let ExprKind::Variable(name) = &call.receiver.kind { + name.clone() + } else { + return Err(LoweringError::UnitLowering( + "Method receiver must be identifier".to_string(), + )); + }; + + if receiver_name == "ctx" { let method = CtxMethod::from_str(&call.method).ok_or_else(|| { LoweringError::UnitLowering(format!("Unknown ctx method: {}", call.method)) })?; @@ -462,7 +589,7 @@ fn lower_expr( ))); } - let offset_expr = lower_expr(&call.arg[0], ctx, ir, block)?; + let offset_expr = lower_expr(&call.arg[0], ctx, ir, block, events, event_decls)?; let offset = match offset_expr { Operand::Immediate(n) => n as i32, _ => { @@ -502,6 +629,72 @@ fn lower_expr( Ok(Operand::Var(result)) } + // ctx.probe_read_user_str(dest, size, src) + CtxMethod::ProbeReadUserStr => { + if call.arg.len() != 3 { + return Err(LoweringError::UnitLowering(format!( + "ctx.probe_read_user_str expects 3 arguments, got {}", + call.arg.len() + ))); + } + + let dest = lower_expr(&call.arg[0], ctx, ir, block, events, event_decls)?; + let size_expr = lower_expr(&call.arg[1], ctx, ir, block, events, event_decls)?; + let src = lower_expr(&call.arg[2], ctx, ir, block, events, event_decls)?; + + let size = match size_expr { + Operand::Immediate(n) => n as u32, + _ => { + return Err(LoweringError::UnitLowering( + "probe_read_user_str size must be immediate".to_string(), + )) + } + }; + + let result = ir.alloc_var(crate::ast::Type::U64); + block.instructions.push(Instruction { + result, + opcode: Opcode::HelperCall { id: 202 }, + operands: vec![dest, Operand::Immediate(size as i64), src], + result_type: crate::ast::Type::U64, + }); + + Ok(Operand::Var(result)) + } + + // ctx.probe_read_kernel_str(dest, size, src) + CtxMethod::ProbeReadKernelStr => { + if call.arg.len() != 3 { + return Err(LoweringError::UnitLowering(format!( + "ctx.probe_read_kernel_str expects 3 arguments, got {}", + call.arg.len() + ))); + } + + let dest = lower_expr(&call.arg[0], ctx, ir, block, events, event_decls)?; + let size_expr = lower_expr(&call.arg[1], ctx, ir, block, events, event_decls)?; + let src = lower_expr(&call.arg[2], ctx, ir, block, events, event_decls)?; + + let size = match size_expr { + Operand::Immediate(n) => n as u32, + _ => { + return Err(LoweringError::UnitLowering( + "probe_read_kernel_str size must be immediate".to_string(), + )) + } + }; + + let result = ir.alloc_var(crate::ast::Type::U64); + block.instructions.push(Instruction { + result, + opcode: Opcode::HelperCall { id: 204 }, + operands: vec![dest, Operand::Immediate(size as i64), src], + result_type: crate::ast::Type::U64, + }); + + Ok(Operand::Var(result)) + } + // ctx helper methods (0 args) _ => { if !call.arg.is_empty() { @@ -524,24 +717,23 @@ fn lower_expr( if call.arg.len() != 1 { return Err(LoweringError::UnitLowering(format!( "{}.lookup expects 1 argument, got {}", - call.receiver, + receiver_name, call.arg.len() ))); } - let key = lower_expr(&call.arg[0], ctx, ir, block)?; + let key = lower_expr(&call.arg[0], ctx, ir, block, events, event_decls)?; let result = ir.alloc_var(crate::ast::Type::U64); block.instructions.push(Instruction { result, opcode: Opcode::CallMap { - map_name: call.receiver.clone(), + map_name: receiver_name.clone(), }, operands: vec![key], result_type: crate::ast::Type::U64, }); - // mark as map pointer for deref null-check ctx.map_ptr_vars.insert(result); Ok(Operand::Var(result)) @@ -551,19 +743,20 @@ fn lower_expr( if call.arg.len() != 2 { return Err(LoweringError::UnitLowering(format!( "{}.update expects 2 arguments, got {}", - call.receiver, + receiver_name, call.arg.len() ))); } - let key = lower_expr(&call.arg[0], ctx, ir, block)?; - let value = lower_expr(&call.arg[1], ctx, ir, block)?; + let key = lower_expr(&call.arg[0], ctx, ir, block, events, event_decls)?; + let value = lower_expr(&call.arg[1], ctx, ir, block, events, event_decls)?; let result = ir.alloc_var(crate::ast::Type::U64); + block.instructions.push(Instruction { result, opcode: Opcode::UpdateMap { - map_name: call.receiver.clone(), + map_name: receiver_name.clone(), }, operands: vec![key, value], result_type: crate::ast::Type::U64, @@ -572,15 +765,75 @@ fn lower_expr( Ok(Operand::Var(result)) } + "reserve" => { + if call.arg.len() != 1 { + return Err(LoweringError::UnitLowering(format!( + "{}.reserve expects 1 argument (event type)", + receiver_name + ))); + } + + let event_name = if let ExprKind::Variable(name) = &call.arg[0].kind { + name.clone() + } else { + return Err(LoweringError::UnitLowering( + "reserve requires event type name".to_string(), + )); + }; + + let size = *events.get(&event_name).ok_or_else(|| { + LoweringError::UnitLowering(format!("Unknown event type: {}", event_name)) + })?; + + let result = ir.alloc_var(crate::ast::Type::U64); + + block.instructions.push(Instruction { + result, + opcode: Opcode::RingBufReserve { + map_name: receiver_name.clone(), + size: size, + }, + operands: vec![], + result_type: crate::ast::Type::U64, + }); + + ctx.map_ptr_vars.insert(result); // mark as nullable pointer + + Ok(Operand::Var(result)) + } + + "submit" => { + if call.arg.len() != 1 { + return Err(LoweringError::UnitLowering(format!( + "{}.submit expects 1 argument", + receiver_name + ))); + } + + let ptr = lower_expr(&call.arg[0], ctx, ir, block, events, event_decls)?; + + let result = ir.alloc_var(crate::ast::Type::U64); + + block.instructions.push(Instruction { + result, + opcode: Opcode::RingBufSubmit { + map_name: receiver_name.clone(), + }, + operands: vec![ptr], + result_type: crate::ast::Type::U64, + }); + + Ok(Operand::Var(result)) + } _ => Err(LoweringError::UnitLowering(format!( "Unknown method: {}.{}", - call.receiver, call.method + receiver_name, call.method ))), } } ExprKind::Dereference(ptr_expr) => { - let ptr = lower_expr(ptr_expr, ctx, ir, block)?; + let ptr = lower_expr(ptr_expr, ctx, ir, block, events, event_decls)?; let result = ir.alloc_var(crate::ast::Type::U64); block.instructions.push(Instruction { @@ -594,8 +847,8 @@ fn lower_expr( } ExprKind::Binary(bin) => { - let left = lower_expr(&bin.left, ctx, ir, block)?; - let right = lower_expr(&bin.right, ctx, ir, block)?; + let left = lower_expr(&bin.left, ctx, ir, block, events, event_decls)?; + let right = lower_expr(&bin.right, ctx, ir, block, events, event_decls)?; let result = ir.alloc_var(crate::ast::Type::U64); @@ -652,6 +905,46 @@ fn lower_expr( } } + ExprKind::FieldAccess { base, field } => { + // Handle field access like evt.filename + let base_operand = lower_expr(base, ctx, ir, block, events, event_decls)?; + + // Get the variable ID (should be a pointer from reserve) + let base_var = if let Operand::Var(v) = base_operand { + v + } else { + return Err(LoweringError::UnitLowering( + "Field access requires a pointer variable".to_string(), + )); + }; + + // Look up field offset across all events + let mut field_offset: Option = None; + for (_, event_decl) in event_decls { + if let Some(offset) = crate::sema::event::compute_field_offset(event_decl, field) { + field_offset = Some(offset); + break; + } + } + + let _offset = field_offset.ok_or_else(|| { + LoweringError::UnitLowering(format!("Unknown field: {}", field)) + })?; + + // Create a load instruction from the field + // TODO: Currently we load from base pointer with fixed size + // In the future, we should track field offsets and sizes per event type + let result = ir.alloc_var(crate::ast::Type::U64); + block.instructions.push(Instruction { + result, + opcode: Opcode::LoadKey, + operands: vec![Operand::Var(base_var)], + result_type: crate::ast::Type::U64, + }); + + Ok(Operand::Var(result)) + } + other => Err(LoweringError::UnitLowering(format!( "InvalidOperand: unsupported expr kind: {other:?}" ))), diff --git a/src/parser/unit.rs b/src/parser/unit.rs index 2c11b33..ebdabe8 100644 --- a/src/parser/unit.rs +++ b/src/parser/unit.rs @@ -56,6 +56,7 @@ pub fn parse_unit(parser: &mut Parser) -> Result { sections, kind, license, + events: Vec::new(), body, }) } @@ -327,86 +328,87 @@ fn parse_unary(parser: &mut Parser) -> Result { } fn parse_primary(parser: &mut Parser) -> Result { - // number literal + let mut expr; + + // number if parser.check(TokenKind::Number) { let num_tok = parser.expect(TokenKind::Number)?; let value = num_tok .int_value .ok_or_else(|| parser.error("Invalid number literal"))?; - return Ok(Expr { + expr = Expr { kind: ExprKind::Number(value), loc: num_tok.loc, - }); + }; } - // (expr) - if parser.r#match(TokenKind::LParen) { - let e = parse_expr(parser)?; + else if parser.r#match(TokenKind::LParen) { + expr = parse_expr(parser)?; expect_token(parser, TokenKind::RParen)?; - return Ok(e); } - // identifier - let ident_tok = parser.expect(TokenKind::Identifier)?; - - // receiver.method(arg1, arg2, ...) - if parser.r#match(TokenKind::Dot) { - let method_tok = parser.expect(TokenKind::Identifier)?; - expect_token(parser, TokenKind::LParen)?; - - let mut args = Vec::new(); - if !parser.check(TokenKind::RParen) { - loop { - args.push(parse_expr(parser)?); - if parser.r#match(TokenKind::Comma) { - continue; - } - break; - } - } - - expect_token(parser, TokenKind::RParen)?; - - return Ok(Expr { - kind: ExprKind::MethodCall(MethodCall { - receiver: ident_tok.lexeme, - method: method_tok.lexeme, - arg: args, // NOTE: matches your AST field name `arg` - }), + else { + let ident_tok = parser.expect(TokenKind::Identifier)?; + expr = Expr { + kind: ExprKind::Variable(ident_tok.lexeme), loc: ident_tok.loc, - }); + }; } - // function call: name(arg1, arg2, ...) - if parser.r#match(TokenKind::LParen) { - let mut args = Vec::new(); - if !parser.check(TokenKind::RParen) { - loop { - args.push(parse_expr(parser)?); - if parser.r#match(TokenKind::Comma) { - continue; - } else { - break; + // POSTFIX LOOP (supports chaining) + loop { + // field access OR method call + if parser.r#match(TokenKind::Dot) { + let field_tok = parser.expect(TokenKind::Identifier)?; + + // method call + if parser.r#match(TokenKind::LParen) { + let mut args = Vec::new(); + if !parser.check(TokenKind::RParen) { + loop { + args.push(parse_expr(parser)?); + if parser.r#match(TokenKind::Comma) { + continue; + } + break; + } } + expect_token(parser, TokenKind::RParen)?; + + expr = Expr { + kind: ExprKind::MethodCall(MethodCall { + receiver: match expr.kind { + ExprKind::Variable(ref name) => Box::new(Expr { + kind: ExprKind::Variable(name.clone()), + loc: expr.loc, + }), + _ => panic!("Unsupported method receiver"), + }, + method: field_tok.lexeme, + arg: args, + }), + loc: expr.loc, + }; } + // field access + else { + expr = Expr { + kind: ExprKind::FieldAccess { + base: Box::new(expr), + field: field_tok.lexeme, + }, + loc: field_tok.loc, + }; + } + + continue; } - expect_token(parser, TokenKind::RParen)?; - return Ok(Expr { - kind: ExprKind::Call(crate::ast::CallExpr { - name: ident_tok.lexeme, - args, - }), - loc: ident_tok.loc, - }); + break; } - // variable - Ok(Expr { - kind: ExprKind::Variable(ident_tok.lexeme), - loc: ident_tok.loc, - }) + Ok(expr) } fn parse_shift(parser: &mut Parser) -> Result { diff --git a/src/sema/event.rs b/src/sema/event.rs index 5b54321..19c6d53 100644 --- a/src/sema/event.rs +++ b/src/sema/event.rs @@ -1,4 +1,4 @@ -use crate::ast::{EventDecl, EventField, EventType, PrimitiveType}; +use crate::ast::{EventDecl, EventType, PrimitiveType}; use crate::parser::SourceLoc; use std::collections::HashSet; @@ -138,3 +138,49 @@ fn primitive_layout(p: &PrimitiveType) -> (u32, u32) { fn align_up(offset: u32, align: u32) -> u32 { (offset + align - 1) & !(align - 1) } + + +pub fn compute_event_size(event: &EventDecl) -> u32 { + let mut offset: u32 = 0; + let mut max_align: u32 = 1; + + for field in &event.fields { + let (size, align) = validate_event_type(&field.ty, field.loc) + .expect("Event should already be validated"); + + max_align = max_align.max(align); + + offset = align_up(offset, align); + offset += size; + } + + align_up(offset, max_align) +} + +pub fn compute_field_offset(event: &EventDecl, field_name: &str) -> Option { + let mut offset: u32 = 0; + let mut max_align: u32 = 1; + + // First pass: calculate alignments + for field in &event.fields { + let (_, align) = validate_event_type(&field.ty, field.loc) + .expect("Event should already be validated"); + max_align = max_align.max(align); + } + + // Second pass: calculate field offset + for field in &event.fields { + let (size, align) = validate_event_type(&field.ty, field.loc) + .expect("Event should already be validated"); + + offset = align_up(offset, align); + + if field.name == field_name { + return Some(offset); + } + + offset += size; + } + + None +} \ No newline at end of file