diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 5f09d73..cb029e8 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8,6 +8,6 @@ pub use program::Program; pub use map::{MapDecl, MapType, Type}; pub use unit::{ Assignment, AssignmentOp, Expr, ExprKind, HeapVarDecl, - IfGuard, MethodCall, Stmt, StmtKind, Unit, VarDecl, VarType,BinaryExpr, BinOp + IfGuard, MethodCall, Stmt, StmtKind, Unit, VarDecl, VarType,BinaryExpr, BinOp, CallExpr }; pub use event::{EventDecl, EventField, EventType, PrimitiveType}; \ No newline at end of file diff --git a/src/compiler.rs b/src/compiler.rs index 194544c..ea5cd3c 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -276,7 +276,7 @@ pub fn compile(input_path: &Path, _output_path: &Path) -> Result<(), miette::Rep .wrap_err("Lowering failed")?; // Emit eBPF code with stage-aware error handling - if let Err(e) = crate::emit::ebpf_obj::emit_program(&program_ir, &output) { + 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); diff --git a/src/diagnostics/category.rs b/src/diagnostics/category.rs index d22fba4..fd6f7d2 100644 --- a/src/diagnostics/category.rs +++ b/src/diagnostics/category.rs @@ -8,6 +8,8 @@ pub enum ErrorType { TypeError, /// Internal error from IR/Codegen stage InternalError, + /// I/O error + IoError, } impl ErrorType { @@ -17,6 +19,7 @@ impl ErrorType { Self::SyntaxError => "Syntax error", Self::TypeError => "Type error", Self::InternalError => "Internal error", + Self::IoError => "I/O error", } } } @@ -37,6 +40,10 @@ pub enum ErrorCategory { Semantic, /// Errors from the code generation phase Codegen, + /// Errors from the optimizer phase + Optimizer, + /// I/O errors + Io, } impl ErrorCategory { @@ -46,6 +53,8 @@ impl ErrorCategory { Self::Parse => "Parse", Self::Semantic => "Semantic", Self::Codegen => "Codegen", + Self::Optimizer => "Optimizer", + Self::Io => "I/O", } } @@ -55,7 +64,8 @@ impl ErrorCategory { Self::Lexical => ErrorType::InvalidToken, Self::Parse => ErrorType::SyntaxError, Self::Semantic => ErrorType::TypeError, - Self::Codegen => ErrorType::InternalError, + Self::Codegen | Self::Optimizer => ErrorType::InternalError, + Self::Io => ErrorType::IoError, } } } diff --git a/src/diagnostics/diagnostic.rs b/src/diagnostics/diagnostic.rs index 45bc01e..5cf384f 100644 --- a/src/diagnostics/diagnostic.rs +++ b/src/diagnostics/diagnostic.rs @@ -8,14 +8,11 @@ use thiserror::Error; #[error("{error_type}: {code}\n {message}")] pub struct CompileDiagnostic { #[source_code] - pub source_code: String, + pub file_content: String, #[label("{label_message}")] pub span: SourceSpan, - #[help] - pub file_name: String, - pub code: String, pub category: ErrorCategory, pub error_type: ErrorType, @@ -23,6 +20,7 @@ pub struct CompileDiagnostic { pub label_message: String, } + pub struct DiagnosticBuilder { category: ErrorCategory, code: ErrorCode, @@ -40,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, @@ -50,19 +53,13 @@ impl DiagnosticBuilder { .ok_or("File content not found")? .to_string(); - let file_name = source_manager - .file_name(span.file) - .ok_or("File name not found")? - .to_string(); - let label_message = self .label_message .unwrap_or_else(|| String::from("error here")); Ok(CompileDiagnostic { - source_code: file_content, + file_content, span: span.to_source_span(), - file_name, // Include the file name code: self.code.code(), category: self.category, error_type: self.category.to_error_type(), @@ -70,4 +67,8 @@ impl DiagnosticBuilder { label_message, }) } -} \ No newline at end of file +} + +impl CompileDiagnostic { + +} diff --git a/src/diagnostics/mod.rs b/src/diagnostics/mod.rs index 9be2d57..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/diagnostics/source_manager.rs b/src/diagnostics/source_manager.rs index 16cfaee..e7406e6 100644 --- a/src/diagnostics/source_manager.rs +++ b/src/diagnostics/source_manager.rs @@ -1,9 +1,12 @@ + + use std::collections::HashMap; use std::ops::Range; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct FileId(pub u32); + #[derive(Debug, Clone)] pub struct Span { pub file: FileId, @@ -21,6 +24,7 @@ pub struct SourceManager { } impl SourceManager { + /// Create a new source manager pub fn new() -> Self { Self { files: HashMap::new(), @@ -28,29 +32,56 @@ impl SourceManager { } } + /// Add a new source file and return its ID pub fn add_file(&mut self, name: String, content: String) -> FileId { let id = FileId(self.next_id); self.next_id += 1; - self.files.insert(id, SourceFile { name, content }); - id } - pub fn file_content(&self, id: FileId) -> Option<&str> { - self.files.get(&id).map(|f| f.content.as_str()) + /// Get a source file by ID + pub fn get(&self, id: FileId) -> Option<&SourceFile> { + self.files.get(&id) } + /// Get the file name for a given FileId pub fn file_name(&self, id: FileId) -> Option<&str> { self.files.get(&id).map(|f| f.name.as_str()) } + + /// Get the file content for a given FileId + pub fn file_content(&self, id: FileId) -> Option<&str> { + self.files.get(&id).map(|f| f.content.as_str()) + } + + /// Get line and column information for a byte offset + pub fn line_col(&self, file: FileId, byte_offset: usize) -> Option<(usize, usize)> { + let content = self.file_content(file)?; + + let mut line = 1; + let mut col = 1; + + for (i, ch) in content.chars().enumerate() { + if i >= byte_offset { + break; + } + if ch == '\n' { + line += 1; + col = 1; + } else { + col += 1; + } + } + + Some((line, col)) + } } impl Span { pub fn new(file: FileId, range: Range) -> Self { Self { file, range } } - pub fn to_source_span(&self) -> miette::SourceSpan { (self.range.start..self.range.end).into() } diff --git a/src/emit/btf/ext.rs b/src/emit/btf/ext.rs deleted file mode 100644 index e1e4ffd..0000000 --- a/src/emit/btf/ext.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::mem::size_of; - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct BtfExtHeader { - pub magic: u16, - pub version: u8, - pub flags: u8, - pub hdr_len: u32, - pub func_info_off: u32, - pub func_info_len: u32, - pub line_info_off: u32, - pub line_info_len: u32, -} - -pub fn build_btf_ext() -> Vec { - let header = BtfExtHeader { - magic: 0xeb9f, - version: 1, - flags: 0, - hdr_len: size_of::() as u32, - func_info_off: 0, - func_info_len: 0, - line_info_off: 0, - line_info_len: 0, - }; - - let mut out = Vec::new(); - - unsafe { - let ptr = &header as *const BtfExtHeader as *const u8; - out.extend_from_slice(std::slice::from_raw_parts(ptr, size_of::())); - } - - out -} \ No newline at end of file diff --git a/src/emit/btf/mod.rs b/src/emit/btf/mod.rs deleted file mode 100644 index 7bc7bbe..0000000 --- a/src/emit/btf/mod.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::mem::size_of; -pub mod ext; - -const BTF_MAGIC: u16 = 0xeb9f; - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct BtfHeader { - pub magic: u16, - pub version: u8, - pub flags: u8, - pub hdr_len: u32, - pub type_off: u32, - pub type_len: u32, - pub str_off: u32, - pub str_len: u32, -} - -pub struct BtfBuilder { - types: Vec, - strings: Vec, -} - -impl BtfBuilder { - pub fn new() -> Self { - let mut strings = Vec::new(); - strings.push(0); // string table must start with empty string - Self { - types: Vec::new(), - strings, - } - } - - pub fn add_string(&mut self, s: &str) -> u32 { - let off = self.strings.len() as u32; - self.strings.extend_from_slice(s.as_bytes()); - self.strings.push(0); - off - } - - pub fn emit(self) -> Vec { - let header = BtfHeader { - magic: BTF_MAGIC, - version: 1, - flags: 0, - hdr_len: size_of::() as u32, - type_off: 0, - type_len: self.types.len() as u32, - str_off: self.types.len() as u32, - str_len: self.strings.len() as u32, - }; - - let mut out = Vec::new(); - - unsafe { - let ptr = &header as *const BtfHeader as *const u8; - out.extend_from_slice(std::slice::from_raw_parts(ptr, size_of::())); - } - - out.extend(self.types); - out.extend(self.strings); - - out - } -} \ No newline at end of file diff --git a/src/emit/ebpf_c/cgroup.rs b/src/emit/ebpf_c/cgroup.rs new file mode 100644 index 0000000..6d906ce --- /dev/null +++ b/src/emit/ebpf_c/cgroup.rs @@ -0,0 +1,38 @@ +use std::fmt::Write; +use crate::ir::UnitIr; + +pub fn emit_cgroup(out: &mut String, unit: &UnitIr, section: &str) -> Result<(), String> { + emit_license(out, &unit.license)?; + + writeln!(out, "SEC(\"{}\")", section).map_err(err)?; + writeln!(out, "int {}(struct __sk_buff *skb) {{", unit.name).map_err(err)?; + writeln!(out, " void *data = (void *)(long)skb->data;").map_err(err)?; + writeln!(out, " void *data_end = (void *)(long)skb->data_end;").map_err(err)?; + writeln!(out, " if (data >= data_end) return SK_DROP;").map_err(err)?; + writeln!(out, " return SK_PASS;").map_err(err)?; + writeln!(out, "}}").map_err(err)?; + writeln!(out).map_err(err)?; + + Ok(()) +} + +pub fn emit_cgroup_sock_addr(out: &mut String, unit: &UnitIr) -> Result<(), String> { + emit_license(out, &unit.license)?; + + writeln!(out, "SEC(\"cgroup/sock_addr\")").map_err(err)?; + writeln!(out, "int {}(struct bpf_sock_addr *ctx) {{", unit.name).map_err(err)?; + writeln!(out, " return 1; // allow connection").map_err(err)?; + writeln!(out, "}}").map_err(err)?; + writeln!(out).map_err(err)?; + Ok(()) +} + +fn emit_license(out: &mut String, lic: &str) -> Result<(), String> { + writeln!(out, "char LICENSE[] SEC(\"license\") = \"{}\";", lic).map_err(err)?; + writeln!(out).map_err(err)?; + Ok(()) +} + +fn err(e: std::fmt::Error) -> String { + e.to_string() +} diff --git a/src/emit/ebpf_c/fentry.rs b/src/emit/ebpf_c/fentry.rs new file mode 100644 index 0000000..f9c4714 --- /dev/null +++ b/src/emit/ebpf_c/fentry.rs @@ -0,0 +1,27 @@ +use std::fmt::Write; + +use crate::ir::UnitIr; + +pub fn emit_fentry(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), String> { + emit_license(out, &unit.license)?; + + // sec: "fentry/" or "fexit/" + writeln!(out, "SEC(\"{}\")", sec).map_err(err)?; + writeln!(out, "int {}(void *ctx) {{", unit.name).map_err(err)?; + writeln!(out, " (void)ctx;").map_err(err)?; + writeln!(out, " return 0;").map_err(err)?; + writeln!(out, "}}").map_err(err)?; + writeln!(out).map_err(err)?; + + Ok(()) +} + +fn emit_license(out: &mut String, lic: &str) -> Result<(), String> { + writeln!(out, "char LICENSE[] SEC(\"license\") = \"{}\";", lic).map_err(err)?; + writeln!(out).map_err(err)?; + Ok(()) +} + +fn err(e: std::fmt::Error) -> String { + e.to_string() +} diff --git a/src/emit/ebpf_c/helpers.rs b/src/emit/ebpf_c/helpers.rs new file mode 100644 index 0000000..f516127 --- /dev/null +++ b/src/emit/ebpf_c/helpers.rs @@ -0,0 +1,3 @@ +pub fn emit_helpers(_out: &mut String) -> Result<(), String> { + Ok(()) +} diff --git a/src/emit/ebpf_c/kprobe.rs b/src/emit/ebpf_c/kprobe.rs new file mode 100644 index 0000000..c2fd5fa --- /dev/null +++ b/src/emit/ebpf_c/kprobe.rs @@ -0,0 +1,25 @@ +use std::fmt::Write; +use crate::ir::UnitIr; + +pub fn emit_kprobe(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), String> { + emit_license(out, &unit.license)?; + + writeln!(out, "SEC(\"{}\")", sec).map_err(err)?; + writeln!(out, "int {}(struct pt_regs *ctx) {{", unit.name).map_err(err)?; + writeln!(out, " (void)ctx;").map_err(err)?; + writeln!(out, " return 0;").map_err(err)?; + writeln!(out, "}}").map_err(err)?; + writeln!(out).map_err(err)?; + + Ok(()) +} + +fn emit_license(out: &mut String, lic: &str) -> Result<(), String> { + writeln!(out, "char LICENSE[] SEC(\"license\") = \"{}\";", lic).map_err(err)?; + writeln!(out).map_err(err)?; + Ok(()) +} + +fn err(e: std::fmt::Error) -> String { + e.to_string() +} diff --git a/src/emit/ebpf_c/lsm.rs b/src/emit/ebpf_c/lsm.rs new file mode 100644 index 0000000..20bc4a6 --- /dev/null +++ b/src/emit/ebpf_c/lsm.rs @@ -0,0 +1,27 @@ +use std::fmt::Write; +use crate::ir::UnitIr; + +pub fn emit_lsm(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), String> { + emit_license(out, &unit.license)?; + + // LSM section + writeln!(out, "SEC(\"{}\")", sec).map_err(err)?; + writeln!(out, "int {}(void *ctx) {{", unit.name).map_err(err)?; + writeln!(out, " // ctx is hook-specific (file, task, socket, etc)").map_err(err)?; + writeln!(out, " // return 0 to allow, -EPERM to deny").map_err(err)?; + writeln!(out, " return 0;").map_err(err)?; + writeln!(out, "}}").map_err(err)?; + writeln!(out).map_err(err)?; + + Ok(()) +} + +fn emit_license(out: &mut String, lic: &str) -> Result<(), String> { + writeln!(out, "char LICENSE[] SEC(\"license\") = \"{}\";", lic).map_err(err)?; + writeln!(out).map_err(err)?; + Ok(()) +} + +fn err(e: std::fmt::Error) -> String { + e.to_string() +} diff --git a/src/emit/ebpf_c/maps.rs b/src/emit/ebpf_c/maps.rs new file mode 100644 index 0000000..a6580aa --- /dev/null +++ b/src/emit/ebpf_c/maps.rs @@ -0,0 +1,83 @@ +use std::fmt::Write; + +use crate::ast::{MapDecl, MapType, Type}; +use crate::emit::util::fmt_err; + +pub fn emit_maps(out: &mut String, maps: &[MapDecl]) -> Result<(), String> { + for m in maps { + let bpf_map_type = map_type_to_c(m.map_type); + let name = sanitize_ident(&m.name); + + writeln!(out, "struct {{").map_err(fmt_err)?; + writeln!(out, " __uint(type, {});", bpf_map_type).map_err(fmt_err)?; + + // max_entries + if let Some(max) = m.max_entries { + writeln!(out, " __uint(max_entries, {});", max).map_err(fmt_err)?; + } + + match m.map_type { + MapType::Ringbuf => { + // Ringbuf does NOT have key/value + // nothing to emit + } + + _ => { + let key_ty = m + .key_type + .ok_or_else(|| format!("Map '{}' missing key type", m.name))?; + + let val_ty = m + .value_type + .ok_or_else(|| format!("Map '{}' missing value type", m.name))?; + + writeln!(out, " __type(key, {});", type_to_c(key_ty)).map_err(fmt_err)?; + writeln!(out, " __type(value, {});", type_to_c(val_ty)).map_err(fmt_err)?; + } + } + + writeln!(out, "}} {} SEC(\".maps\");", name).map_err(fmt_err)?; + writeln!(out).map_err(fmt_err)?; + } + + Ok(()) +} + +fn map_type_to_c(t: MapType) -> &'static str { + match t { + MapType::Hash => "BPF_MAP_TYPE_HASH", + MapType::Array => "BPF_MAP_TYPE_ARRAY", + MapType::Ringbuf => "BPF_MAP_TYPE_RINGBUF", + MapType::LruHash => "BPF_MAP_TYPE_LRU_HASH", + MapType::ProgArray => "BPF_MAP_TYPE_PROG_ARRAY", + MapType::PerfEventArray => "BPF_MAP_TYPE_PERF_EVENT_ARRAY", + } +} + +fn type_to_c(t: Type) -> &'static str { + match t { + Type::U32 => "__u32", + Type::U64 => "__u64", + Type::I32 => "__s32", + Type::I64 => "__s64", + } +} + +fn sanitize_ident(name: &str) -> String { + let mut out = String::with_capacity(name.len()); + for (i, ch) in name.chars().enumerate() { + if (i == 0 && (ch.is_ascii_alphabetic() || ch == '_')) + || (i != 0 && (ch.is_ascii_alphanumeric() || ch == '_')) + { + out.push(ch); + } else { + out.push('_'); + } + } + + if out.is_empty() { + "_map".to_string() + } else { + out + } +} diff --git a/src/emit/ebpf_c/mod.rs b/src/emit/ebpf_c/mod.rs new file mode 100644 index 0000000..0d2ca4e --- /dev/null +++ b/src/emit/ebpf_c/mod.rs @@ -0,0 +1,13 @@ +pub mod program; +pub mod maps; +pub mod xdp; +pub mod write; +pub mod helpers; +pub mod tc; +pub mod sk; +pub mod cgroup; +pub mod kprobe; +pub mod raw_tracepoint; +pub mod tracepoint; +pub mod fentry; +pub mod lsm; \ No newline at end of file diff --git a/src/emit/ebpf_c/program.rs b/src/emit/ebpf_c/program.rs new file mode 100644 index 0000000..49dd1d5 --- /dev/null +++ b/src/emit/ebpf_c/program.rs @@ -0,0 +1,113 @@ +use std::fmt::Write; +use std::path::Path; + +use super::{helpers, maps, write, xdp}; +use crate::{ + emit::ebpf_c::{cgroup, fentry, kprobe, lsm, raw_tracepoint, sk, tc, tracepoint}, + ir::ProgramIr, +}; + +pub fn emit_program(program: &ProgramIr, output: &Path) -> Result<(), String> { + let mut c = String::new(); + + emit_prelude(&mut c, program)?; + + helpers::emit_helpers(&mut c)?; + maps::emit_maps(&mut c, &program.maps)?; + + for unit in &program.units { + let sec0 = unit + .sections + .get(0) + .map(|s| s.as_str()) + .unwrap_or("unknown"); + + match sec0 { + "xdp" => xdp::emit_xdp(&mut c, unit)?, + + "tc" | "classifier" => tc::emit_tc(&mut c, unit, "classifier")?, + "tcx" | "tcx/egress" | "tc/egress" => tc::emit_tc(&mut c, unit, "tcx/egress")?, + "tcx/ingress" | "tc/ingress" => tc::emit_tc(&mut c, unit, "tcx/ingress")?, + + "sk_skb/stream_parser" => sk::emit_sk_skb(&mut c, unit, "sk_skb/stream_parser")?, + "sk_skb/stream_verdict" => sk::emit_sk_skb(&mut c, unit, "sk_skb/stream_verdict")?, + "sk_msg" => sk::emit_sk_msg(&mut c, unit)?, + + "cgroup/skb/ingress" => cgroup::emit_cgroup(&mut c, unit, "cgroup/skb/ingress")?, + "cgroup/skb/egress" => cgroup::emit_cgroup(&mut c, unit, "cgroup/skb/egress")?, + "cgroup/sock" => cgroup::emit_cgroup(&mut c, unit, "cgroup/sock")?, + "cgroup/sock_addr" => cgroup::emit_cgroup_sock_addr(&mut c, unit)?, + + s if s.starts_with("kprobe/") || s.starts_with("kretprobe/") => { + kprobe::emit_kprobe(&mut c, unit, s)? + } + s if s.starts_with("raw_tracepoint/") => { + raw_tracepoint::emit_raw_tracepoint(&mut c, unit, s)? + } + s if s.starts_with("tracepoint/") => tracepoint::emit_tracepoint(&mut c, unit, s)?, + + s if s.starts_with("fentry/") || s.starts_with("fexit/") => { + fentry::emit_fentry(&mut c, unit, s)? + } + s if s.starts_with("lsm/") => lsm::emit_lsm(&mut c, unit, s)?, + + s => return Err(format!("Unsupported section: {}", s)), + } + } + + write::compile_to_object(&c, output)?; + Ok(()) +} + +fn emit_prelude(out: &mut String, program: &ProgramIr) -> Result<(), String> { + writeln!(out, "#include \"vmlinux.h\"").map_err(err)?; + writeln!(out, "#include ").map_err(err)?; + writeln!(out, "#include \n").map_err(err)?; + + let mut needs_tc = false; + let mut needs_xdp = false; + let mut needs_sk = false; + + for unit in &program.units { + for sec in &unit.sections { + let s = sec.as_str(); + if s.contains("tc") || s == "classifier" { needs_tc = true; } + if s.contains("xdp") { needs_xdp = true; } + if s.contains("sk_") { needs_sk = true; } + } + } + + if needs_tc { + writeln!(out, "/* TC Action codes */").map_err(err)?; + writeln!(out, "#ifndef TC_ACT_OK").map_err(err)?; + writeln!(out, "#define TC_ACT_OK 0").map_err(err)?; + writeln!(out, "#define TC_ACT_SHOT 2").map_err(err)?; + writeln!(out, "#define TC_ACT_UNSPEC -1").map_err(err)?; + writeln!(out, "#endif\n").map_err(err)?; + } + + if needs_xdp { + writeln!(out, "/* XDP Action codes */").map_err(err)?; + writeln!(out, "#ifndef XDP_ABORTED").map_err(err)?; + writeln!(out, "#define XDP_ABORTED 0").map_err(err)?; + writeln!(out, "#define XDP_DROP 1").map_err(err)?; + writeln!(out, "#define XDP_PASS 2").map_err(err)?; + writeln!(out, "#define XDP_TX 3").map_err(err)?; + writeln!(out, "#define XDP_REDIRECT 4").map_err(err)?; + writeln!(out, "#endif\n").map_err(err)?; + } + + if needs_sk { + writeln!(out, "/* Socket Action codes */").map_err(err)?; + writeln!(out, "#ifndef SK_PASS").map_err(err)?; + writeln!(out, "#define SK_PASS 1").map_err(err)?; + writeln!(out, "#define SK_DROP 0").map_err(err)?; + writeln!(out, "#endif\n").map_err(err)?; + } + + Ok(()) +} + +fn err(e: std::fmt::Error) -> String { + e.to_string() +} \ No newline at end of file diff --git a/src/emit/ebpf_c/raw_tracepoint.rs b/src/emit/ebpf_c/raw_tracepoint.rs new file mode 100644 index 0000000..c22bce9 --- /dev/null +++ b/src/emit/ebpf_c/raw_tracepoint.rs @@ -0,0 +1,30 @@ +use std::fmt::Write; +use crate::ir::UnitIr; + +pub fn emit_raw_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), String> { + emit_license(out, &unit.license)?; + + writeln!(out, "SEC(\"{}\")", sec).map_err(err)?; + writeln!( + out, + "int {}(struct bpf_raw_tracepoint_args *ctx) {{", + unit.name + ) + .map_err(err)?; + writeln!(out, " (void)ctx;").map_err(err)?; + writeln!(out, " return 0;").map_err(err)?; + writeln!(out, "}}").map_err(err)?; + writeln!(out).map_err(err)?; + + Ok(()) +} + +fn emit_license(out: &mut String, lic: &str) -> Result<(), String> { + writeln!(out, "char LICENSE[] SEC(\"license\") = \"{}\";", lic).map_err(err)?; + writeln!(out).map_err(err)?; + Ok(()) +} + +fn err(e: std::fmt::Error) -> String { + e.to_string() +} diff --git a/src/emit/ebpf_c/sk.rs b/src/emit/ebpf_c/sk.rs new file mode 100644 index 0000000..ae7bd36 --- /dev/null +++ b/src/emit/ebpf_c/sk.rs @@ -0,0 +1,40 @@ +use std::fmt::Write; +use crate::ir::UnitIr; + +pub fn emit_sk_skb(out: &mut String, unit: &UnitIr, section: &str) -> Result<(), String> { + emit_license(out, &unit.license)?; + + writeln!(out, "SEC(\"{}\")", section).map_err(err)?; + writeln!(out, "int {}(struct __sk_buff *skb) {{", unit.name).map_err(err)?; + + writeln!(out, " void *data = (void *)(long)skb->data;").map_err(err)?; + writeln!(out, " void *data_end = (void *)(long)skb->data_end;").map_err(err)?; + writeln!(out, " if (data >= data_end) return SK_DROP;").map_err(err)?; + + writeln!(out, " return SK_PASS;").map_err(err)?; + writeln!(out, "}}").map_err(err)?; + writeln!(out).map_err(err)?; + Ok(()) +} + +pub fn emit_sk_msg(out: &mut String, unit: &UnitIr) -> Result<(), String> { + emit_license(out, &unit.license)?; + + writeln!(out, "SEC(\"sk_msg\")").map_err(err)?; + writeln!(out, "int {}(struct sk_msg_md *msg) {{", unit.name).map_err(err)?; + + writeln!(out, " return SK_PASS;").map_err(err)?; + writeln!(out, "}}").map_err(err)?; + writeln!(out).map_err(err)?; + Ok(()) +} + +fn emit_license(out: &mut String, lic: &str) -> Result<(), String> { + writeln!(out, "char LICENSE[] SEC(\"license\") = \"{}\";", lic).map_err(err)?; + writeln!(out).map_err(err)?; + Ok(()) +} + +fn err(e: std::fmt::Error) -> String { + e.to_string() +} diff --git a/src/emit/ebpf_c/tc.rs b/src/emit/ebpf_c/tc.rs new file mode 100644 index 0000000..08a5654 --- /dev/null +++ b/src/emit/ebpf_c/tc.rs @@ -0,0 +1,28 @@ +use std::fmt::Write; +use crate::ir::UnitIr; + +pub fn emit_tc(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), String> { + emit_license(out, &unit.license)?; + + writeln!(out, "SEC(\"{}\")", sec).map_err(err)?; + writeln!(out, "int {}(struct __sk_buff *ctx) {{", unit.name).map_err(err)?; + writeln!(out, " void *data = (void *)(long)ctx->data;").map_err(err)?; + writeln!(out, " void *data_end = (void *)(long)ctx->data_end;").map_err(err)?; + writeln!(out).map_err(err)?; + writeln!(out, " if (data + 14 > data_end) return TC_ACT_OK;").map_err(err)?; + writeln!(out, " return TC_ACT_OK;").map_err(err)?; + writeln!(out, "}}").map_err(err)?; + writeln!(out).map_err(err)?; + + Ok(()) +} + +fn emit_license(out: &mut String, lic: &str) -> Result<(), String> { + writeln!(out, "char LICENSE[] SEC(\"license\") = \"{}\";", lic).map_err(err)?; + writeln!(out).map_err(err)?; + Ok(()) +} + +fn err(e: std::fmt::Error) -> String { + e.to_string() +} diff --git a/src/emit/ebpf_c/tracepoint.rs b/src/emit/ebpf_c/tracepoint.rs new file mode 100644 index 0000000..69aa810 --- /dev/null +++ b/src/emit/ebpf_c/tracepoint.rs @@ -0,0 +1,419 @@ +use std::collections::{HashMap, HashSet}; +use std::fmt::Write; + +use crate::ir::{BinaryOp, Opcode, Operand, UnitIr, VarId}; + +pub fn emit_tracepoint(out: &mut String, unit: &UnitIr, sec: &str) -> Result<(), String> { + emit_license(out, &unit.license)?; + + writeln!(out, "SEC(\"{}\")", sec).map_err(err)?; + 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 { + for inst in &block.instructions { + for op in &inst.operands { + if let Operand::Var(VarId(id)) = op { + used_vars.insert(*id); + } + } + } + + match &block.terminator { + crate::ir::unit::Terminator::Return(op) => { + if let Operand::Var(VarId(id)) = op { + used_vars.insert(*id); + } + } + crate::ir::unit::Terminator::Jump(_) => {} + crate::ir::unit::Terminator::Branch { condition, .. } => { + if let Operand::Var(VarId(id)) = condition { + used_vars.insert(*id); + } + } + } + } + + let mut branch_cond_vars: HashSet = HashSet::new(); + for block in &unit.blocks { + if let crate::ir::unit::Terminator::Branch { condition, .. } = &block.terminator { + if let Operand::Var(VarId(id)) = condition { + branch_cond_vars.insert(*id); + } + } + } + + 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 { + if matches!(inst.opcode, Opcode::CallMap { .. }) && used_vars.contains(&inst.result.0) { + pointer_vars.insert(inst.result.0); + } + } + } + + 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(); + + fn make_temp_u64( + temp_counter: &mut u32, + temps: &mut Vec<(String, String)>, + init: &str, + ) -> String { + let name = format!("__tmp{}", *temp_counter); + *temp_counter += 1; + 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; + for block in &unit.blocks { + for inst in &block.instructions { + match &inst.opcode { + Opcode::CallMap { .. } => { + if let Some(key_op) = inst.operands.get(0) { + if matches!(key_op, Operand::Immediate(_)) { + let key_expr = format_operand(key_op); + let tmp = make_temp_u64(&mut temp_counter, &mut temps, &key_expr); + imm_addr_cache.insert((inst_index, format!("k:{}", key_expr)), tmp); + } + } + } + Opcode::UpdateMap { .. } => { + if inst.operands.len() >= 2 { + let key_op = &inst.operands[0]; + let val_op = &inst.operands[1]; + + if matches!(key_op, Operand::Immediate(_)) { + let key_expr = format_operand(key_op); + let tmp = make_temp_u64(&mut temp_counter, &mut temps, &key_expr); + imm_addr_cache.insert((inst_index, format!("k:{}", key_expr)), tmp); + } + if matches!(val_op, Operand::Immediate(_)) { + let val_expr = format_operand(val_op); + let tmp = make_temp_u64(&mut temp_counter, &mut temps, &val_expr); + imm_addr_cache.insert((inst_index, format!("v:{}", val_expr)), tmp); + } + } + } + _ => {} + } + + 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 { + crate::ast::Type::U64 => "__u64", + crate::ast::Type::U32 => "__u32", + crate::ast::Type::I64 => "__s64", + crate::ast::Type::I32 => "__s32", + }; + + if pointer_vars.contains(&id) { + writeln!(out, " {} *v{} = 0;", c_type, id).map_err(err)?; + } else { + writeln!(out, " {} v{} = 0;", c_type, id).map_err(err)?; + } + } + + for (name, init) in &temps { + writeln!(out, " __u64 {} = {};", name, init).map_err(err)?; + } + + 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)?; + + let mut block_ids = HashSet::::new(); + 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)?; + + for inst in &block.instructions { + let res_used = used_vars.contains(&inst.result.0); + let res_name = if res_used { + Some(format!("v{}", inst.result.0)) + } else { + None + }; + + let is_side_effect = matches!( + inst.opcode, + Opcode::Store { .. } | Opcode::UpdateMap { .. } | Opcode::NullCheck + ); + + if !is_side_effect && !res_used { + inst_index += 1; + continue; + } + + match &inst.opcode { + Opcode::Binary { op } => { + if inst.operands.len() >= 2 { + let left = format_operand(&inst.operands[0]); + let right = format_operand(&inst.operands[1]); + let op_str = match op { + BinaryOp::Add => "+", + BinaryOp::Sub => "-", + BinaryOp::Mul => "*", + BinaryOp::Div => "/", + BinaryOp::Mod => "%", + BinaryOp::Shl => "<<", + BinaryOp::Shr => ">>", + }; + let res = res_name.unwrap(); + writeln!(out, " {} = {} {} {};", res, left, op_str, right) + .map_err(err)?; + } + } + + Opcode::LoadKey => { + if let Some(operand) = inst.operands.get(0) { + let ptr = format_operand(operand); + let res = res_name.unwrap(); + writeln!(out, " {} = *((__u64 *)(void *)({}));", res, ptr).map_err(err)?; + } + } + + Opcode::Store { size } => { + if inst.operands.len() >= 2 { + let ptr = format_operand(&inst.operands[0]); + let val = format_operand(&inst.operands[1]); + writeln!(out, " *((__u{} *)(void *)({})) = {};", size * 8, ptr, val).map_err(err)?; + } + } + + Opcode::LoadCtx { offset, size } => { + let res = res_name.unwrap(); + writeln!( + out, + " {} = *(__u{} *)((__u8 *)ctx + ({}));", + res, + size * 8, + offset + ) + .map_err(err)?; + } + + Opcode::LoadPacket { offset, size } => { + let res = res_name.unwrap(); + writeln!( + out, + " {} = *(__u{} *)((__u8 *)ctx + ({}));", + res, + size * 8, + offset + ) + .map_err(err)?; + } + + Opcode::HelperCall { id } => { + let call_expr = match *id { + 5 => "bpf_ktime_get_ns()", + 14 => "bpf_get_current_pid_tgid()", + 15 => "bpf_get_current_uid_gid()", + 35 => "bpf_get_current_task()", + _ => "0", + }; + let res = res_name.unwrap(); + writeln!(out, " {} = {};", res, call_expr).map_err(err)?; + } + + Opcode::CallMap { map_name } => { + if let Some(key_op) = inst.operands.get(0) { + let key_expr = format_operand(key_op); + let key_addr = match key_op { + Operand::Var(_) => format!("&{}", key_expr), + Operand::Immediate(_) => { + let tmp = imm_addr_cache + .get(&(inst_index, format!("k:{}", key_expr))) + .cloned() + .ok_or_else(|| { + "internal error: missing temp for immediate key".to_string() + })?; + format!("&{}", tmp) + } + }; + + let res = res_name.unwrap(); + writeln!( + out, + " {} = bpf_map_lookup_elem(&{}, {});", + res, map_name, key_addr + ) + .map_err(err)?; + } + } + + Opcode::UpdateMap { map_name } => { + if inst.operands.len() >= 2 { + let key_op = &inst.operands[0]; + let val_op = &inst.operands[1]; + + let key_expr = format_operand(key_op); + let val_expr = format_operand(val_op); + + let key_addr = match key_op { + Operand::Var(_) => format!("&{}", key_expr), + Operand::Immediate(_) => { + let tmp = imm_addr_cache + .get(&(inst_index, format!("k:{}", key_expr))) + .cloned() + .ok_or_else(|| { + "internal error: missing temp for immediate key".to_string() + })?; + format!("&{}", tmp) + } + }; + + let val_addr = match val_op { + Operand::Var(_) => format!("&{}", val_expr), + Operand::Immediate(_) => { + let tmp = imm_addr_cache + .get(&(inst_index, format!("v:{}", val_expr))) + .cloned() + .ok_or_else(|| { + "internal error: missing temp for immediate value" + .to_string() + })?; + format!("&{}", tmp) + } + }; + + if let Some(res) = res_name { + writeln!( + out, + " {} = bpf_map_update_elem(&{}, {}, {}, 0);", + res, map_name, key_addr, val_addr + ) + .map_err(err)?; + } else { + writeln!( + out, + " (void)bpf_map_update_elem(&{}, {}, {}, 0);", + map_name, key_addr, val_addr + ) + .map_err(err)?; + } + } + } + + 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)?; + } else { + writeln!(out, " if (!({})) goto __solnix_null_fail;", ptr_expr) + .map_err(err)?; + + if let Some(res) = res_name { + writeln!(out, " {} = 1;", res).map_err(err)?; + } + } + } + } + + 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; + } + + match &block.terminator { + crate::ir::unit::Terminator::Return(op) => { + writeln!(out, " return {};", format_operand(op)).map_err(err)?; + } + crate::ir::unit::Terminator::Jump(target) => { + if !block_ids.contains(&target.0) { + return Err(format!("Invalid jump target: {:?}", target)); + } + writeln!(out, " goto __block_{};", target.0).map_err(err)?; + } + crate::ir::unit::Terminator::Branch { + condition, + true_block, + false_block, + } => { + if !block_ids.contains(&true_block.0) || !block_ids.contains(&false_block.0) { + return Err("Invalid branch targets".to_string()); + } + writeln!( + out, + " if ({}) goto __block_{}; else goto __block_{};", + format_operand(condition), + true_block.0, + false_block.0 + ) + .map_err(err)?; + } + } + + writeln!(out).map_err(err)?; + } + + writeln!(out, "__solnix_null_fail:").map_err(err)?; + writeln!(out, " return 0;").map_err(err)?; + writeln!(out, "}}").map_err(err)?; + Ok(()) +} + +fn format_operand(op: &Operand) -> String { + match op { + Operand::Var(VarId(id)) => format!("v{}", id), + Operand::Immediate(val) => val.to_string(), + } +} + +fn emit_license(out: &mut String, lic: &str) -> Result<(), String> { + writeln!(out, "char LICENSE[] SEC(\"license\") = \"{}\";", lic).map_err(err)?; + writeln!(out).map_err(err)?; + Ok(()) +} + +fn err(e: std::fmt::Error) -> String { + e.to_string() +} diff --git a/src/emit/ebpf_c/write.rs b/src/emit/ebpf_c/write.rs new file mode 100644 index 0000000..631f9ce --- /dev/null +++ b/src/emit/ebpf_c/write.rs @@ -0,0 +1,34 @@ +use std::{fs, path::Path, process::Command}; + +pub fn compile_to_object(code: &str, out: &Path) -> Result<(), String> { + let build_dir = out.parent().unwrap(); + let c_file = out.with_extension("c"); + + fs::write(&c_file, code).map_err(|e| format!("failed to write C file: {e}"))?; + + let status = Command::new("clang") + .args([ + "-O2", + "-g", + "-Wall", + "-Wno-c23-extensions", + "-std=gnu11", + "-target", + "bpf", + "-D__TARGET_ARCH_x86", + "-I", + build_dir.to_str().unwrap(), + "-c", + c_file.to_str().unwrap(), + "-o", + out.to_str().unwrap(), + ]) + .status() + .map_err(|e| format!("failed to run clang: {e}"))?; + + if !status.success() { + return Err("clang compilation failed".into()); + } + + Ok(()) +} \ No newline at end of file diff --git a/src/emit/ebpf_c/xdp.rs b/src/emit/ebpf_c/xdp.rs new file mode 100644 index 0000000..e5a9591 --- /dev/null +++ b/src/emit/ebpf_c/xdp.rs @@ -0,0 +1,40 @@ +use std::fmt::Write; +use crate::ir::UnitIr; + +pub fn emit_xdp(out: &mut String, unit: &UnitIr) -> Result<(), String> { + emit_license(out, &unit.license)?; + + writeln!(out, "SEC(\"xdp\")").map_err(err)?; + writeln!(out, "int {}(struct xdp_md *ctx) {{", unit.name).map_err(err)?; + + writeln!(out, " void *data = (void *)(long)ctx->data;").map_err(err)?; + writeln!(out, " void *data_end = (void *)(long)ctx->data_end;").map_err(err)?; + writeln!(out).map_err(err)?; + + writeln!(out, " if (data + 26 + 4 > data_end) return XDP_PASS;").map_err(err)?; + writeln!(out, " __u32 src_ip = *(__u32 *)(data + 26);").map_err(err)?; + writeln!(out).map_err(err)?; + + writeln!(out, " __u32 key = src_ip;").map_err(err)?; + writeln!(out, " __u64 *count_ptr = bpf_map_lookup_elem(&connection_counter, &key);").map_err(err)?; + writeln!(out, " if (count_ptr) {{").map_err(err)?; + writeln!(out, " __sync_fetch_and_add(count_ptr, 1);").map_err(err)?; + writeln!(out, " }}").map_err(err)?; + writeln!(out).map_err(err)?; + + writeln!(out, " return 1;").map_err(err)?; + writeln!(out, "}}").map_err(err)?; + writeln!(out).map_err(err)?; + + Ok(()) +} + +fn emit_license(out: &mut String, lic: &str) -> Result<(), String> { + writeln!(out, "char LICENSE[] SEC(\"license\") = \"{}\";", lic).map_err(err)?; + writeln!(out).map_err(err)?; + Ok(()) +} + +fn err(e: std::fmt::Error) -> String { + e.to_string() +} diff --git a/src/emit/ebpf_obj/elf.rs b/src/emit/ebpf_obj/elf.rs deleted file mode 100644 index 95a505c..0000000 --- a/src/emit/ebpf_obj/elf.rs +++ /dev/null @@ -1,480 +0,0 @@ -// src/emit/ebpf_obj/elf.rs; -use std::{collections::HashMap, fs::File, io::Write, path::Path}; - -use crate::emit::btf::{self, ext::build_btf_ext}; - -use super::{maps::MapsSection, tracepoint::ProgramSection}; - -const ET_REL: u16 = 1; -const EM_BPF: u16 = 247; -const EV_CURRENT: u32 = 1; - -const SHT_NULL: u32 = 0; -const SHT_PROGBITS: u32 = 1; -const SHT_SYMTAB: u32 = 2; -const SHT_STRTAB: u32 = 3; -const SHT_REL: u32 = 9; - -const SHF_WRITE: u64 = 0x1; -const SHF_ALLOC: u64 = 0x2; -const SHF_EXECINSTR: u64 = 0x4; - -const STB_LOCAL: u8 = 0; -const STB_GLOBAL: u8 = 1; - -const STT_NOTYPE: u8 = 0; -const STT_OBJECT: u8 = 1; -const STT_FUNC: u8 = 2; - -const R_BPF_64_64: u32 = 1; - -#[derive(Clone, Debug)] -struct Section { - name: String, - name_off: u32, - ty: u32, - flags: u64, - align: u64, - entsize: u64, - link: u32, - info: u32, - data: Vec, - file_off: u64, -} - -#[derive(Clone, Debug)] -struct SymbolSpec { - name: String, - info: u8, - shndx: u16, - value: u64, - size: u64, -} - -struct Strtab { - bytes: Vec, - seen: HashMap, -} - -impl Strtab { - fn new() -> Self { - Self { - bytes: vec![0], - seen: HashMap::new(), - } - } - - fn put(&mut self, s: &str) -> u32 { - if let Some(x) = self.seen.get(s) { - return *x; - } - let off = self.bytes.len() as u32; - self.bytes.extend_from_slice(s.as_bytes()); - self.bytes.push(0); - self.seen.insert(s.to_string(), off); - off - } -} - -pub fn write_object( - output: &Path, - maps: &MapsSection, - programs: &[ProgramSection], - license: &str, -) -> Result<(), String> { - let mut sections = Vec::
::new(); - - let mut btf_builder = btf::BtfBuilder::new(); - - for prog in programs { - btf_builder.add_string(&prog.symbol_name); - } - - let btf_data = btf_builder.emit(); - - let btf_ext_data = build_btf_ext(); - - sections.push(Section { - name: String::new(), - name_off: 0, - ty: SHT_NULL, - flags: 0, - align: 0, - entsize: 0, - link: 0, - info: 0, - data: Vec::new(), - file_off: 0, - }); - - sections.push(Section { - name: ".BTF".to_string(), - name_off: 0, - ty: SHT_PROGBITS, - flags: 0, - align: 4, - entsize: 0, - link: 0, - info: 0, - data: btf_data, - file_off: 0, - }); - - sections.push(Section { - name: ".BTF.ext".to_string(), - name_off: 0, - ty: SHT_PROGBITS, - flags: 0, - align: 4, - entsize: 0, - link: 0, - info: 0, - data: btf_ext_data, - file_off: 0, - }); - - let mut prog_sec_indices = Vec::::new(); - for p in programs { - prog_sec_indices.push(sections.len() as u16); - sections.push(Section { - name: p.section_name.clone(), - name_off: 0, - ty: SHT_PROGBITS, - flags: SHF_ALLOC | SHF_EXECINSTR, - align: 8, - entsize: 0, - link: 0, - info: 0, - data: p.code.clone(), - file_off: 0, - }); - } - - let mut rel_sec_meta = Vec::<(usize, u16)>::new(); - for (i, p) in programs.iter().enumerate() { - if !p.relocs.is_empty() { - let idx = sections.len() as u16; - rel_sec_meta.push((i, idx)); - sections.push(Section { - name: format!(".rel{}", p.section_name), - name_off: 0, - ty: SHT_REL, - flags: 0, - align: 8, - entsize: 16, - link: 0, - info: prog_sec_indices[i] as u32, - data: Vec::new(), - file_off: 0, - }); - } - } - - let maps_idx = sections.len() as u16; - sections.push(Section { - name: ".maps".to_string(), - name_off: 0, - ty: SHT_PROGBITS, - flags: SHF_ALLOC | SHF_WRITE, - align: 8, - entsize: 0, - link: 0, - info: 0, - data: maps.bytes.clone(), - file_off: 0, - }); - - let mut license_bytes = license.as_bytes().to_vec(); - if !license_bytes.ends_with(&[0]) { - license_bytes.push(0); - } - sections.push(Section { - name: "license".to_string(), - name_off: 0, - ty: SHT_PROGBITS, - flags: SHF_ALLOC, - align: 1, - entsize: 0, - link: 0, - info: 0, - data: license_bytes, - file_off: 0, - }); - - let strtab_idx = sections.len() as u16; - sections.push(Section { - name: ".strtab".to_string(), - name_off: 0, - ty: SHT_STRTAB, - flags: 0, - align: 1, - entsize: 0, - link: 0, - info: 0, - data: Vec::new(), - file_off: 0, - }); - - let symtab_idx = sections.len() as u16; - sections.push(Section { - name: ".symtab".to_string(), - name_off: 0, - ty: SHT_SYMTAB, - flags: 0, - align: 8, - entsize: 24, - link: strtab_idx as u32, - info: 1, - data: Vec::new(), - file_off: 0, - }); - - let shstrtab_idx = sections.len() as u16; - sections.push(Section { - name: ".shstrtab".to_string(), - name_off: 0, - ty: SHT_STRTAB, - flags: 0, - align: 1, - entsize: 0, - link: 0, - info: 0, - data: Vec::new(), - file_off: 0, - }); - - // build symbols - let mut strtab = Strtab::new(); - let mut symbols = Vec::::new(); - let mut map_sym_idx = HashMap::::new(); - - symbols.push(SymbolSpec { - name: String::new(), - info: bind_type(STB_LOCAL, STT_NOTYPE), - shndx: 0, - value: 0, - size: 0, - }); - - for (i, p) in programs.iter().enumerate() { - symbols.push(SymbolSpec { - name: p.symbol_name.clone(), - info: bind_type(STB_GLOBAL, STT_FUNC), - shndx: prog_sec_indices[i], - value: 0, - size: p.code.len() as u64, - }); - } - - for rec in &maps.records { - let idx = symbols.len() as u32; - map_sym_idx.insert(rec.name.clone(), idx); - symbols.push(SymbolSpec { - name: rec.name.clone(), - info: bind_type(STB_GLOBAL, STT_OBJECT), - shndx: maps_idx, - value: rec.offset, - size: rec.size, - }); - } - - let mut symtab_bytes = Vec::new(); - for sym in &symbols { - let name_off = strtab.put(&sym.name); - push_sym( - &mut symtab_bytes, - name_off, - sym.info, - 0, - sym.shndx, - sym.value, - sym.size, - ); - } - - sections[strtab_idx as usize].data = strtab.bytes; - sections[symtab_idx as usize].data = symtab_bytes; - - // build reloc sections - for (prog_i, rel_sec_idx) in &rel_sec_meta { - let p = &programs[*prog_i]; - let mut rel_bytes = Vec::new(); - - for rel in &p.relocs { - let sym_idx = *map_sym_idx - .get(&rel.map_name) - .ok_or_else(|| format!("missing relocation symbol for map '{}'", rel.map_name))?; - push_rel( - &mut rel_bytes, - rel.insn_byte_off, - elf64_r_info(sym_idx, R_BPF_64_64), - ); - } - - sections[*rel_sec_idx as usize].link = symtab_idx as u32; - sections[*rel_sec_idx as usize].info = prog_sec_indices[*prog_i] as u32; - sections[*rel_sec_idx as usize].data = rel_bytes; - } - - // build shstrtab - let mut shstr = Strtab::new(); - for sec in sections.iter_mut().skip(1) { - sec.name_off = shstr.put(&sec.name); - } - sections[shstrtab_idx as usize].data = shstr.bytes; - - // file layout - let ehdr_size = 64u64; - let shdr_size = 64u64; - - let mut cur = ehdr_size; - for sec in sections.iter_mut().skip(1) { - cur = align_up(cur, sec.align.max(1)); - sec.file_off = cur; - cur += sec.data.len() as u64; - } - - let shoff = align_up(cur, 8); - let file_size = shoff + (sections.len() as u64 * shdr_size); - - let mut out = Vec::with_capacity(file_size as usize); - - // ELF header - push_ehdr(&mut out, shoff, sections.len() as u16, shstrtab_idx); - - // section data - while out.len() < ehdr_size as usize { - out.push(0); - } - - for sec in sections.iter().skip(1) { - while out.len() < sec.file_off as usize { - out.push(0); - } - out.extend_from_slice(&sec.data); - } - - while out.len() < shoff as usize { - out.push(0); - } - - // section headers - for sec in §ions { - push_shdr( - &mut out, - sec.name_off, - sec.ty, - sec.flags, - 0, - sec.file_off, - sec.data.len() as u64, - sec.link, - sec.info, - sec.align, - sec.entsize, - ); - } - - let mut f = File::create(output).map_err(|e| e.to_string())?; - f.write_all(&out).map_err(|e| e.to_string())?; - Ok(()) -} - -fn bind_type(bind: u8, ty: u8) -> u8 { - (bind << 4) | (ty & 0x0f) -} - -fn elf64_r_info(sym: u32, ty: u32) -> u64 { - ((sym as u64) << 32) | (ty as u64) -} - -fn align_up(v: u64, a: u64) -> u64 { - if a <= 1 { - v - } else { - (v + (a - 1)) & !(a - 1) - } -} - -fn push_u16(out: &mut Vec, v: u16) { - out.extend_from_slice(&v.to_le_bytes()); -} -fn push_u32(out: &mut Vec, v: u32) { - out.extend_from_slice(&v.to_le_bytes()); -} -fn push_u64(out: &mut Vec, v: u64) { - out.extend_from_slice(&v.to_le_bytes()); -} - -fn push_ehdr(out: &mut Vec, shoff: u64, shnum: u16, shstrndx: u16) { - let mut ident = [0u8; 16]; - ident[0] = 0x7f; - ident[1] = b'E'; - ident[2] = b'L'; - ident[3] = b'F'; - ident[4] = 2; // 64-bit - ident[5] = 1; // little endian - ident[6] = 1; // version - - out.extend_from_slice(&ident); - push_u16(out, ET_REL); - push_u16(out, EM_BPF); - push_u32(out, EV_CURRENT); - push_u64(out, 0); - push_u64(out, 0); - push_u64(out, shoff); - push_u32(out, 0); - push_u16(out, 64); - push_u16(out, 0); - push_u16(out, 0); - push_u16(out, 64); - push_u16(out, shnum); - push_u16(out, shstrndx); -} - -fn push_shdr( - out: &mut Vec, - name: u32, - ty: u32, - flags: u64, - addr: u64, - off: u64, - size: u64, - link: u32, - info: u32, - addralign: u64, - entsize: u64, -) { - push_u32(out, name); - push_u32(out, ty); - push_u64(out, flags); - push_u64(out, addr); - push_u64(out, off); - push_u64(out, size); - push_u32(out, link); - push_u32(out, info); - push_u64(out, addralign); - push_u64(out, entsize); -} - -fn push_sym( - out: &mut Vec, - st_name: u32, - st_info: u8, - st_other: u8, - st_shndx: u16, - st_value: u64, - st_size: u64, -) { - push_u32(out, st_name); - out.push(st_info); - out.push(st_other); - push_u16(out, st_shndx); - push_u64(out, st_value); - push_u64(out, st_size); -} - -fn push_rel(out: &mut Vec, r_offset: u64, r_info: u64) { - push_u64(out, r_offset); - push_u64(out, r_info); -} diff --git a/src/emit/ebpf_obj/insn.rs b/src/emit/ebpf_obj/insn.rs deleted file mode 100644 index 60f3d6d..0000000 --- a/src/emit/ebpf_obj/insn.rs +++ /dev/null @@ -1,162 +0,0 @@ -// src/emit/ebpf_obj/insn.rs -#[derive(Clone, Copy, Debug)] -pub struct BpfInsn { - pub code: u8, - pub regs: u8, - pub off: i16, - pub imm: i32, -} - -impl BpfInsn { - pub fn new(code: u8, dst: u8, src: u8, off: i16, imm: i32) -> Self { - Self { - code, - regs: dst | (src << 4), - off, - imm, - } - } - - pub fn to_bytes(self) -> [u8; 8] { - let mut out = [0u8; 8]; - out[0] = self.code; - out[1] = self.regs; - out[2..4].copy_from_slice(&self.off.to_le_bytes()); - out[4..8].copy_from_slice(&self.imm.to_le_bytes()); - out - } -} - -pub fn serialize_insns(insns: &[BpfInsn]) -> Vec { - let mut out = Vec::with_capacity(insns.len() * 8); - for insn in insns { - out.extend_from_slice(&insn.to_bytes()); - } - out -} - -pub const R0: u8 = 0; -pub const R1: u8 = 1; -pub const R2: u8 = 2; -pub const R3: u8 = 3; -pub const R4: u8 = 4; -pub const R5: u8 = 5; -pub const R6: u8 = 6; -pub const R7: u8 = 7; -pub const R8: u8 = 8; -pub const R9: u8 = 9; -pub const R10: u8 = 10; - -// classes -pub const BPF_LD: u8 = 0x00; -pub const BPF_LDX: u8 = 0x01; -pub const BPF_ST: u8 = 0x02; -pub const BPF_STX: u8 = 0x03; -pub const BPF_ALU: u8 = 0x04; -pub const BPF_JMP: u8 = 0x05; -pub const BPF_ALU64: u8 = 0x07; - -// sizes / mode -pub const BPF_W: u8 = 0x00; -pub const BPF_H: u8 = 0x08; -pub const BPF_B: u8 = 0x10; -pub const BPF_DW: u8 = 0x18; -pub const BPF_IMM: u8 = 0x00; -pub const BPF_MEM: u8 = 0x60; - -// src -pub const BPF_K: u8 = 0x00; -pub const BPF_X: u8 = 0x08; - -// alu / jmp ops -pub const BPF_ADD: u8 = 0x00; -pub const BPF_SUB: u8 = 0x10; -pub const BPF_MUL: u8 = 0x20; -pub const BPF_DIV: u8 = 0x30; -pub const BPF_MOD: u8 = 0x90; -pub const BPF_LSH: u8 = 0x60; -pub const BPF_RSH: u8 = 0x70; -pub const BPF_MOV: u8 = 0xb0; -pub const BPF_ARSH: u8 = 0xc0; - -pub const BPF_JA: u8 = 0x00; -pub const BPF_JEQ: u8 = 0x10; -pub const BPF_JNE: u8 = 0x50; -pub const BPF_CALL: u8 = 0x80; -pub const BPF_EXIT: u8 = 0x90; - -#[derive(Clone, Copy)] -pub enum Size { - B, - H, - W, - Dw, -} - -impl Size { - pub fn code(self) -> u8 { - match self { - Size::B => BPF_B, - Size::H => BPF_H, - Size::W => BPF_W, - Size::Dw => BPF_DW, - } - } -} - -pub fn mov64_reg(dst: u8, src: u8) -> BpfInsn { - BpfInsn::new(BPF_ALU64 | BPF_MOV | BPF_X, dst, src, 0, 0) -} - -pub fn mov64_imm(dst: u8, imm: i32) -> BpfInsn { - BpfInsn::new(BPF_ALU64 | BPF_MOV | BPF_K, dst, 0, 0, imm) -} - -pub fn alu64_reg(op: u8, dst: u8, src: u8) -> BpfInsn { - BpfInsn::new(BPF_ALU64 | op | BPF_X, dst, src, 0, 0) -} - -pub fn alu64_imm(op: u8, dst: u8, imm: i32) -> BpfInsn { - BpfInsn::new(BPF_ALU64 | op | BPF_K, dst, 0, 0, imm) -} - -pub fn ldx_mem(size: Size, dst: u8, src: u8, off: i16) -> BpfInsn { - BpfInsn::new(BPF_LDX | size.code() | BPF_MEM, dst, src, off, 0) -} - -pub fn stx_mem(size: Size, dst: u8, off: i16, src: u8) -> BpfInsn { - BpfInsn::new(BPF_STX | size.code() | BPF_MEM, dst, src, off, 0) -} - -pub fn st_mem(size: Size, dst: u8, off: i16, imm: i32) -> BpfInsn { - BpfInsn::new(BPF_ST | size.code() | BPF_MEM, dst, 0, off, imm) -} - -pub fn jmp_imm(op: u8, dst: u8, imm: i32, off: i16) -> BpfInsn { - BpfInsn::new(BPF_JMP | op | BPF_K, dst, 0, off, imm) -} - -pub fn ja(off: i16) -> BpfInsn { - BpfInsn::new(BPF_JMP | BPF_JA, 0, 0, off, 0) -} - -pub fn call(helper_id: i32) -> BpfInsn { - BpfInsn::new(BPF_JMP | BPF_CALL, 0, 0, 0, helper_id) -} - -pub fn exit() -> BpfInsn { - BpfInsn::new(BPF_JMP | BPF_EXIT, 0, 0, 0, 0) -} - -pub fn ld_imm64(dst: u8, imm: i64) -> [BpfInsn; 2] { - let lo = imm as i32; - let hi = (imm >> 32) as i32; - [ - BpfInsn::new(BPF_LD | BPF_DW | BPF_IMM, dst, 0, 0, lo), - BpfInsn::new(0, 0, 0, 0, hi), - ] -} - -pub fn fits_i32(v: i64) -> bool { - v >= i32::MIN as i64 && v <= i32::MAX as i64 -} \ No newline at end of file diff --git a/src/emit/ebpf_obj/maps.rs b/src/emit/ebpf_obj/maps.rs deleted file mode 100644 index 7b73298..0000000 --- a/src/emit/ebpf_obj/maps.rs +++ /dev/null @@ -1,125 +0,0 @@ -// src/emit/ebpf_obj/maps.rs -use std::collections::HashMap; - -use crate::ast::{MapDecl, MapType, Type}; - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct BpfMapDef { - pub map_type: u32, - pub key_size: u32, - pub value_size: u32, - pub max_entries: u32, - pub map_flags: u32, -} - -#[derive(Clone, Debug)] -pub struct MapRecord { - pub name: String, - pub offset: u64, - pub size: u64, -} - -#[derive(Clone, Debug)] -pub struct MapsSection { - pub bytes: Vec, - pub records: Vec, - pub by_name: HashMap, -} - -pub fn build_maps(maps: &[MapDecl]) -> Result { - let mut bytes = Vec::new(); - let mut records = Vec::new(); - let mut by_name = HashMap::new(); - - for m in maps { - let name = sanitize_ident(&m.name); - if by_name.contains_key(&name) { - return Err(format!("duplicate map name '{}'", name)); - } - - let def = match m.map_type { - MapType::Ringbuf => BpfMapDef { - map_type: map_type_to_u32(m.map_type), - key_size: 0, - value_size: 0, - max_entries: m.max_entries.unwrap_or(0), - map_flags: 0, - }, - _ => BpfMapDef { - map_type: map_type_to_u32(m.map_type), - key_size: type_size( - m.key_type - .ok_or_else(|| format!("map '{}' missing key type", m.name))?, - ), - value_size: type_size( - m.value_type - .ok_or_else(|| format!("map '{}' missing value type", m.name))?, - ), - max_entries: m.max_entries.unwrap_or(0), - map_flags: 0, - }, - }; - - let offset = bytes.len() as u64; - push_map_def(&mut bytes, def); - - let rec = MapRecord { - name: name.clone(), - offset, - size: std::mem::size_of::() as u64, - }; - - by_name.insert(name, records.len()); - records.push(rec); - } - - Ok(MapsSection { - bytes, - records, - by_name, - }) -} - -fn push_map_def(out: &mut Vec, def: BpfMapDef) { - out.extend_from_slice(&def.map_type.to_le_bytes()); - out.extend_from_slice(&def.key_size.to_le_bytes()); - out.extend_from_slice(&def.value_size.to_le_bytes()); - out.extend_from_slice(&def.max_entries.to_le_bytes()); - out.extend_from_slice(&def.map_flags.to_le_bytes()); -} - -fn type_size(t: Type) -> u32 { - match t { - Type::U32 | Type::I32 => 4, - Type::U64 | Type::I64 => 8, - } -} - -fn map_type_to_u32(t: MapType) -> u32 { - match t { - MapType::Hash => 1, - MapType::Array => 2, - MapType::ProgArray => 3, - MapType::PerfEventArray => 4, - MapType::LruHash => 9, - MapType::Ringbuf => 27, - } -} - -fn sanitize_ident(name: &str) -> String { - let mut out = String::with_capacity(name.len()); - for (i, ch) in name.chars().enumerate() { - let ok = if i == 0 { - ch.is_ascii_alphabetic() || ch == '_' - } else { - ch.is_ascii_alphanumeric() || ch == '_' - }; - out.push(if ok { ch } else { '_' }); - } - if out.is_empty() { - "_map".to_string() - } else { - out - } -} \ No newline at end of file diff --git a/src/emit/ebpf_obj/mod.rs b/src/emit/ebpf_obj/mod.rs deleted file mode 100644 index b224461..0000000 --- a/src/emit/ebpf_obj/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -// src/emit/ebpf_obj/mod.rs -use std::path::Path; - -use crate::ir::ProgramIr; - -pub mod elf; -pub mod insn; -pub mod maps; -pub mod tracepoint; - -pub fn emit_program(program: &ProgramIr, output: &Path) -> Result<(), String> { - let maps = maps::build_maps(&program.maps)?; - - let mut license: Option = None; - let mut programs = Vec::new(); - - for unit in &program.units { - let sec = unit - .sections - .get(0) - .map(|s| s.as_str()) - .ok_or_else(|| format!("unit '{}' missing section", unit.name))?; - - if !sec.starts_with("tracepoint/") { - return Err(format!( - "direct object backend currently supports only tracepoint/*, got '{}'", - sec - )); - } - - match &license { - None => license = Some(unit.license.clone()), - Some(x) if x == &unit.license => {} - Some(x) => { - return Err(format!( - "all units in one object must share same license: '{}' vs '{}'", - x, unit.license - )) - } - } - - programs.push(tracepoint::emit_tracepoint(unit, sec, &maps)?); - } - - if programs.is_empty() { - return Err("no tracepoint units found".to_string()); - } - - elf::write_object( - output, - &maps, - &programs, - license.as_deref().unwrap_or("GPL"), - ) -} \ No newline at end of file diff --git a/src/emit/ebpf_obj/tracepoint.rs b/src/emit/ebpf_obj/tracepoint.rs deleted file mode 100644 index 879757c..0000000 --- a/src/emit/ebpf_obj/tracepoint.rs +++ /dev/null @@ -1,674 +0,0 @@ -// src/emit/ebpf_obj/tracepoint.rs -use std::collections::{HashMap, HashSet}; - -use crate::{ - ast::Type, - ir::{BinaryOp, Opcode, Operand, UnitIr, VarId}, -}; - -use super::{insn::*, maps::MapsSection}; - -#[derive(Clone, Debug)] -pub struct RelocRequest { - pub insn_byte_off: u64, - pub map_name: String, -} - -#[derive(Clone, Debug)] -pub struct ProgramSection { - pub section_name: String, - pub symbol_name: String, - pub code: Vec, - pub relocs: Vec, -} - -#[derive(Clone, Copy, Debug)] -enum Label { - Block(u32), - NullFail, -} - -#[derive(Clone, Debug)] -struct JumpFixup { - at_insn: usize, - target: Label, -} - -#[derive(Clone, Debug)] -struct FrameLayout { - slots: HashMap, - types: HashMap, - pointer_vars: HashSet, - scratch_a: i16, - scratch_b: i16, - frame_size: usize, -} - -struct Codegen<'a> { - unit: &'a UnitIr, - maps: &'a MapsSection, - frame: FrameLayout, - used_vars: HashSet, - insns: Vec, - relocs: Vec, - block_pos: HashMap, - fixups: Vec, - block_ids: HashSet, -} - -pub fn emit_tracepoint( - unit: &UnitIr, - sec: &str, - maps: &MapsSection, -) -> Result { - let used_vars = collect_used_vars(unit); - let (var_types, pointer_vars) = collect_var_info(unit, &used_vars); - let block_ids = unit.blocks.iter().map(|b| b.id.0).collect::>(); - - let frame = build_frame(&used_vars, var_types, pointer_vars)?; - - let mut cg = Codegen { - unit, - maps, - frame, - used_vars, - insns: Vec::new(), - relocs: Vec::new(), - block_pos: HashMap::new(), - fixups: Vec::new(), - block_ids, - }; - - // prologue: keep ctx in callee-saved r6 - cg.emit(mov64_reg(R6, R1)); - - for block in &unit.blocks { - cg.block_pos.insert(block.id.0, cg.pos()); - - for inst in &block.instructions { - let res_used = cg.used_vars.contains(&inst.result.0); - let is_side_effect = matches!( - inst.opcode, - Opcode::Store { .. } - | Opcode::UpdateMap { .. } - | Opcode::NullCheck - | Opcode::RingBufSubmit { .. } - ); - - if !res_used && !is_side_effect { - continue; - } - - match &inst.opcode { - Opcode::Binary { op } => { - cg.emit_binary(inst.result.0, inst.result_type, op, &inst.operands)?; - } - - Opcode::LoadCtx { offset, size } => { - cg.emit_load_ctx( - inst.result.0, - inst.result_type, - *offset as i16, - (*size).into(), - )?; - } - - Opcode::LoadPacket { offset, size } => { - cg.emit_load_ctx( - inst.result.0, - inst.result_type, - *offset as i16, - (*size).into(), - )?; - } - - Opcode::HelperCall { id } => { - cg.emit(call(*id as i32)); - cg.store_r0(inst.result.0)?; - } - - Opcode::CallMap { map_name } => { - cg.emit_map_lookup(inst.result.0, map_name, inst.operands.get(0))?; - } - - Opcode::UpdateMap { map_name } => { - if inst.operands.len() < 2 { - return Err("UpdateMap requires key and value operands".to_string()); - } - cg.emit_map_update( - res_used.then_some(inst.result.0), - map_name, - &inst.operands[0], - &inst.operands[1], - )?; - } - - Opcode::NullCheck => { - let ptr = inst - .operands - .get(0) - .ok_or_else(|| "NullCheck missing operand".to_string())?; - cg.emit_nullcheck_to_bool(inst.result.0, ptr)?; - } - - Opcode::LoadKey => { - let ptr = inst - .operands - .get(0) - .ok_or_else(|| "LoadKey missing operand".to_string())?; - cg.emit_load_from_ptr(inst.result.0, inst.result_type, ptr)?; - } - - Opcode::Store { size } => { - if inst.operands.len() < 2 { - return Err("Store requires ptr and value".to_string()); - } - cg.emit_store_to_ptr(*size as usize, &inst.operands[0], &inst.operands[1])?; - } - - Opcode::RingBufReserve { map_name, size } => { - cg.emit_ringbuf_reserve(inst.result.0, map_name, *size as i64)?; - } - - Opcode::RingBufSubmit { .. } => { - let ptr = inst - .operands - .get(0) - .ok_or_else(|| "RingBufSubmit missing pointer operand".to_string())?; - cg.emit_ringbuf_submit(ptr)?; - } - } - } - - match &block.terminator { - crate::ir::unit::Terminator::Return(op) => { - cg.load_operand(R0, op)?; - cg.emit(exit()); - } - crate::ir::unit::Terminator::Jump(target) => { - if !cg.block_ids.contains(&target.0) { - return Err(format!("invalid jump target {}", target.0)); - } - let at = cg.pos(); - cg.emit(ja(0)); - cg.fixups.push(JumpFixup { - at_insn: at, - target: Label::Block(target.0), - }); - } - crate::ir::unit::Terminator::Branch { - condition, - true_block, - false_block, - } => { - if !cg.block_ids.contains(&true_block.0) || !cg.block_ids.contains(&false_block.0) { - return Err("invalid branch targets".to_string()); - } - - cg.load_operand(R1, condition)?; - - let at_false = cg.pos(); - cg.emit(jmp_imm(BPF_JEQ, R1, 0, 0)); - cg.fixups.push(JumpFixup { - at_insn: at_false, - target: Label::Block(false_block.0), - }); - - let at_true = cg.pos(); - cg.emit(ja(0)); - cg.fixups.push(JumpFixup { - at_insn: at_true, - target: Label::Block(true_block.0), - }); - } - } - } - - let null_fail_pos = cg.pos(); - cg.emit(mov64_imm(R0, 0)); - cg.emit(exit()); - - cg.patch_fixups(null_fail_pos)?; - - Ok(ProgramSection { - section_name: sec.to_string(), - symbol_name: unit.name.clone(), - code: serialize_insns(&cg.insns), - relocs: cg.relocs, - }) -} - -impl<'a> Codegen<'a> { - fn pos(&self) -> usize { - self.insns.len() - } - - fn emit(&mut self, insn: BpfInsn) { - self.insns.push(insn); - } - - fn emit2(&mut self, pair: [BpfInsn; 2]) { - self.insns.push(pair[0]); - self.insns.push(pair[1]); - } - - fn patch_fixups(&mut self, null_fail_pos: usize) -> Result<(), String> { - for fix in &self.fixups { - let target = match fix.target { - Label::Block(id) => *self - .block_pos - .get(&id) - .ok_or_else(|| format!("missing block position for {}", id))?, - Label::NullFail => null_fail_pos, - }; - - let off = target as isize - fix.at_insn as isize - 1; - if off < i16::MIN as isize || off > i16::MAX as isize { - return Err("jump offset out of range".to_string()); - } - self.insns[fix.at_insn].off = off as i16; - } - Ok(()) - } - - fn slot_of(&self, var: u32) -> Result { - self.frame - .slots - .get(&var) - .copied() - .ok_or_else(|| format!("missing stack slot for v{}", var)) - } - - fn store_reg_to_var(&mut self, reg: u8, var: u32) -> Result<(), String> { - let off = self.slot_of(var)?; - self.emit(stx_mem(Size::Dw, R10, off, reg)); - Ok(()) - } - - fn store_r0(&mut self, var: u32) -> Result<(), String> { - self.store_reg_to_var(R0, var) - } - - fn load_var(&mut self, dst: u8, var: u32) -> Result<(), String> { - let off = self.slot_of(var)?; - self.emit(ldx_mem(Size::Dw, dst, R10, off)); - Ok(()) - } - - fn lea_stack(&mut self, dst: u8, off: i16) { - self.emit(mov64_reg(dst, R10)); - self.emit(alu64_imm(BPF_ADD, dst, off as i32)); - } - - fn load_imm64(&mut self, dst: u8, imm: i64) { - if fits_i32(imm) { - self.emit(mov64_imm(dst, imm as i32)); - } else { - self.emit2(ld_imm64(dst, imm)); - } - } - - fn load_operand(&mut self, dst: u8, op: &Operand) -> Result<(), String> { - match op { - Operand::Var(VarId(id)) => self.load_var(dst, *id), - Operand::Immediate(v) => { - self.load_imm64(dst, *v); - Ok(()) - } - } - } - - fn operand_addr(&mut self, dst: u8, op: &Operand, scratch_slot: i16) -> Result<(), String> { - match op { - Operand::Var(VarId(id)) => { - let off = self.slot_of(*id)?; - self.lea_stack(dst, off); - Ok(()) - } - Operand::Immediate(v) => { - self.load_imm64(R0, *v); - self.emit(stx_mem(Size::Dw, R10, scratch_slot, R0)); - self.lea_stack(dst, scratch_slot); - Ok(()) - } - } - } - - fn emit_load_ctx( - &mut self, - dst_var: u32, - result_type: Type, - offset: i16, - size: usize, - ) -> Result<(), String> { - let sz = match size { - 1 => Size::B, - 2 => Size::H, - 4 => Size::W, - 8 => Size::Dw, - _ => return Err(format!("unsupported LoadCtx size {}", size)), - }; - - self.emit(ldx_mem(sz, R0, R6, offset)); - - if matches!(result_type, Type::I32) && size == 4 { - self.emit(alu64_imm(BPF_LSH, R0, 32)); - self.emit(alu64_imm(BPF_ARSH, R0, 32)); - } - - self.store_reg_to_var(R0, dst_var) - } - - fn emit_binary( - &mut self, - dst_var: u32, - _result_type: Type, - op: &BinaryOp, - operands: &[Operand], - ) -> Result<(), String> { - if operands.len() < 2 { - return Err("Binary opcode needs 2 operands".to_string()); - } - - self.load_operand(R0, &operands[0])?; - - let op_bits = match op { - BinaryOp::Add => BPF_ADD, - BinaryOp::Sub => BPF_SUB, - BinaryOp::Mul => BPF_MUL, - BinaryOp::Div => BPF_DIV, - BinaryOp::Mod => BPF_MOD, - BinaryOp::Shl => BPF_LSH, - BinaryOp::Shr => BPF_RSH, - }; - - match &operands[1] { - Operand::Immediate(v) if fits_i32(*v) => { - if matches!(op, BinaryOp::Div | BinaryOp::Mod) && *v == 0 { - return Err("division/modulo by zero".to_string()); - } - self.emit(alu64_imm(op_bits, R0, *v as i32)); - } - rhs => { - self.load_operand(R1, rhs)?; - if matches!(op, BinaryOp::Div | BinaryOp::Mod) { - let at = self.pos(); - self.emit(jmp_imm(BPF_JEQ, R1, 0, 0)); - self.fixups.push(JumpFixup { - at_insn: at, - target: Label::NullFail, - }); - } - self.emit(alu64_reg(op_bits, R0, R1)); - } - } - - self.store_reg_to_var(R0, dst_var) - } - - fn emit_map_lookup( - &mut self, - dst_var: u32, - map_name: &str, - key: Option<&Operand>, - ) -> Result<(), String> { - self.require_map(map_name)?; - - let insn_idx = self.pos(); - self.emit2(ld_imm64(R1, 0)); - self.relocs.push(RelocRequest { - insn_byte_off: (insn_idx as u64) * 8, - map_name: map_name.to_string(), - }); - - let key_op = key.ok_or_else(|| format!("CallMap '{}' missing key operand", map_name))?; - self.operand_addr(R2, key_op, self.frame.scratch_a)?; - - self.emit(call(1)); // bpf_map_lookup_elem - self.store_reg_to_var(R0, dst_var) - } - - fn emit_map_update( - &mut self, - dst_var: Option, - map_name: &str, - key: &Operand, - value: &Operand, - ) -> Result<(), String> { - self.require_map(map_name)?; - - let insn_idx = self.pos(); - self.emit2(ld_imm64(R1, 0)); - self.relocs.push(RelocRequest { - insn_byte_off: (insn_idx as u64) * 8, - map_name: map_name.to_string(), - }); - - self.operand_addr(R2, key, self.frame.scratch_a)?; - self.operand_addr(R3, value, self.frame.scratch_b)?; - self.emit(mov64_imm(R4, 0)); // BPF_ANY - self.emit(call(2)); // bpf_map_update_elem - - if let Some(v) = dst_var { - self.store_reg_to_var(R0, v)?; - } - - Ok(()) - } - - fn emit_null_guard_reg(&mut self, reg: u8) { - let at = self.pos(); - self.emit(jmp_imm(BPF_JEQ, reg, 0, 0)); - self.fixups.push(JumpFixup { - at_insn: at, - target: Label::NullFail, - }); - } - - fn emit_nullcheck_to_bool(&mut self, dst_var: u32, ptr: &Operand) -> Result<(), String> { - self.load_operand(R1, ptr)?; - self.emit(mov64_imm(R0, 0)); - - let jne_set1 = self.pos(); - self.emit(jmp_imm(BPF_JNE, R1, 0, 0)); - - self.store_reg_to_var(R0, dst_var)?; - - let ja_done = self.pos(); - self.emit(ja(0)); - - let set1_pos = self.pos(); - self.emit(mov64_imm(R0, 1)); - self.store_reg_to_var(R0, dst_var)?; - - let done_pos = self.pos(); - - self.insns[jne_set1].off = (set1_pos as isize - jne_set1 as isize - 1) as i16; - self.insns[ja_done].off = (done_pos as isize - ja_done as isize - 1) as i16; - - Ok(()) - } - - fn emit_load_from_ptr( - &mut self, - dst_var: u32, - result_type: Type, - ptr: &Operand, - ) -> Result<(), String> { - self.load_operand(R1, ptr)?; - self.emit_null_guard_reg(R1); - - let size = match result_type { - Type::U32 | Type::I32 => Size::W, - Type::U64 | Type::I64 => Size::Dw, - }; - - self.emit(ldx_mem(size, R0, R1, 0)); - - if matches!(result_type, Type::I32) { - self.emit(alu64_imm(BPF_LSH, R0, 32)); - self.emit(alu64_imm(BPF_ARSH, R0, 32)); - } - - self.store_reg_to_var(R0, dst_var) - } - - fn emit_store_to_ptr( - &mut self, - size: usize, - ptr: &Operand, - value: &Operand, - ) -> Result<(), String> { - self.load_operand(R1, ptr)?; - self.emit_null_guard_reg(R1); - self.load_operand(R2, value)?; - - let sz = match size { - 1 => Size::B, - 2 => Size::H, - 4 => Size::W, - 8 => Size::Dw, - _ => return Err(format!("unsupported Store size {}", size)), - }; - - self.emit(stx_mem(sz, R1, 0, R2)); - Ok(()) - } - - fn emit_ringbuf_reserve( - &mut self, - dst_var: u32, - map_name: &str, - size: i64, - ) -> Result<(), String> { - self.require_map(map_name)?; - - let insn_idx = self.pos(); - self.emit2(ld_imm64(R1, 0)); - self.relocs.push(RelocRequest { - insn_byte_off: (insn_idx as u64) * 8, - map_name: map_name.to_string(), - }); - - self.load_imm64(R2, size); - self.emit(mov64_imm(R3, 0)); - self.emit(call(131)); // bpf_ringbuf_reserve - self.store_reg_to_var(R0, dst_var) - } - - fn emit_ringbuf_submit(&mut self, ptr: &Operand) -> Result<(), String> { - self.load_operand(R1, ptr)?; - self.emit_null_guard_reg(R1); - self.emit(mov64_imm(R2, 0)); - self.emit(call(132)); // bpf_ringbuf_submit - Ok(()) - } - - fn require_map(&self, name: &str) -> Result<(), String> { - if self.maps.by_name.contains_key(name) { - Ok(()) - } else { - Err(format!("unknown map '{}'", name)) - } - } -} - -fn collect_used_vars(unit: &UnitIr) -> HashSet { - let mut used = HashSet::new(); - - for block in &unit.blocks { - for inst in &block.instructions { - - // include result variables - used.insert(inst.result.0); - - for op in &inst.operands { - if let Operand::Var(VarId(id)) = op { - used.insert(*id); - } - } - } - - match &block.terminator { - crate::ir::unit::Terminator::Return(op) => { - if let Operand::Var(VarId(id)) = op { - used.insert(*id); - } - } - crate::ir::unit::Terminator::Jump(_) => {} - crate::ir::unit::Terminator::Branch { condition, .. } => { - if let Operand::Var(VarId(id)) = condition { - used.insert(*id); - } - } - } - } - - used -} - -fn collect_var_info(unit: &UnitIr, used_vars: &HashSet) -> (HashMap, HashSet) { - let mut var_types = HashMap::new(); - let mut ptrs = HashSet::new(); - - for block in &unit.blocks { - for inst in &block.instructions { - var_types.entry(inst.result.0).or_insert(inst.result_type); - - if used_vars.contains(&inst.result.0) - && matches!( - inst.opcode, - Opcode::CallMap { .. } | Opcode::RingBufReserve { .. } - ) - { - ptrs.insert(inst.result.0); - } - } - } - - (var_types, ptrs) -} - -fn build_frame( - used_vars: &HashSet, - types: HashMap, - pointer_vars: HashSet, -) -> Result { - let mut vars = used_vars.iter().copied().collect::>(); - vars.sort_unstable(); - - let mut slots = HashMap::new(); - let mut next = 8usize; - - for id in vars { - slots.insert(id, -(next as i16)); - next += 8; - } - - let scratch_a = -(next as i16); - next += 8; - - let scratch_b = -(next as i16); - next += 8; - - let frame_size = align_up(next, 16); - if frame_size > 512 { - return Err(format!( - "stack frame too large: {} bytes (max 512)", - frame_size - )); - } - - Ok(FrameLayout { - slots, - types, - pointer_vars, - scratch_a, - scratch_b, - frame_size, - }) -} - -fn align_up(v: usize, a: usize) -> usize { - (v + (a - 1)) & !(a - 1) -} diff --git a/src/emit/mod.rs b/src/emit/mod.rs index 9548547..44bc191 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -1,3 +1,2 @@ -pub mod ebpf_obj; -pub mod util; -pub mod btf; \ No newline at end of file +pub mod ebpf_c; +pub mod util; \ No newline at end of file diff --git a/src/emit/util.rs b/src/emit/util.rs index e69de29..0167745 100644 --- a/src/emit/util.rs +++ b/src/emit/util.rs @@ -0,0 +1,3 @@ +pub fn fmt_err(e: std::fmt::Error) -> String { + e.to_string() +} diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index b3de83e..88a591e 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -209,7 +209,7 @@ impl<'src> Lexer<'src> { } fn read_string(&mut self) -> Result { - let _start = self.index; + let start = self.index; let loc = self.current_loc(); if self.peek() != '"' {