From 7fe0bc34c8c2bc82baa20edad0cf5fb8e69239ca Mon Sep 17 00:00:00 2001 From: maheshbhatiya73 Date: Thu, 25 Jun 2026 20:04:22 +0530 Subject: [PATCH] feat(codegen): replace C-based code generation with native Rust backend --- .gitignore | 2 + src/ast/event.rs | 5 +- src/ast/map.rs | 5 +- src/ast/mod.rs | 13 +- src/ast/program.rs | 2 +- src/build/clang.rs | 0 src/build/layout.rs | 11 - src/build/mod.rs | 2 - src/build/vmlinux.rs | 32 -- src/compiler.rs | 73 ++-- src/diagnostics/mod.rs | 4 +- src/diagnostics/source_manager.rs | 13 +- src/emit/ebpf/elf.rs | 642 ++++++++++++++++++++++++++++++ src/emit/ebpf/insn.rs | 82 ++++ src/emit/ebpf/mod.rs | 36 ++ src/emit/ebpf/tracepoint.rs | 398 ++++++++++++++++++ src/emit/ebpf_c/cgroup.rs | 38 -- src/emit/ebpf_c/fentry.rs | 27 -- src/emit/ebpf_c/helpers.rs | 3 - src/emit/ebpf_c/kprobe.rs | 25 -- src/emit/ebpf_c/lsm.rs | 27 -- src/emit/ebpf_c/maps.rs | 82 ---- src/emit/ebpf_c/mod.rs | 13 - src/emit/ebpf_c/program.rs | 113 ------ src/emit/ebpf_c/raw_tracepoint.rs | 30 -- src/emit/ebpf_c/sk.rs | 40 -- src/emit/ebpf_c/tc.rs | 28 -- src/emit/ebpf_c/tracepoint.rs | 516 ------------------------ src/emit/ebpf_c/write.rs | 34 -- src/emit/ebpf_c/xdp.rs | 40 -- src/emit/mod.rs | 3 +- src/emit/util.rs | 3 - src/ir/mod.rs | 17 +- src/ir/program.rs | 6 +- src/ir/unit.rs | 9 +- src/kernel_check.rs | 125 ------ src/lexer/mod.rs | 19 +- src/main.rs | 26 +- src/parser/event.rs | 9 +- src/parser/map.rs | 15 +- src/parser/mod.rs | 21 +- src/parser/parser.rs | 67 ++-- src/parser/program.rs | 6 +- src/parser/unit.rs | 2 +- 44 files changed, 1322 insertions(+), 1342 deletions(-) delete mode 100644 src/build/clang.rs delete mode 100644 src/build/layout.rs delete mode 100644 src/build/mod.rs delete mode 100644 src/build/vmlinux.rs create mode 100644 src/emit/ebpf/elf.rs create mode 100644 src/emit/ebpf/insn.rs create mode 100644 src/emit/ebpf/mod.rs create mode 100644 src/emit/ebpf/tracepoint.rs delete mode 100644 src/emit/ebpf_c/cgroup.rs delete mode 100644 src/emit/ebpf_c/fentry.rs delete mode 100644 src/emit/ebpf_c/helpers.rs delete mode 100644 src/emit/ebpf_c/kprobe.rs delete mode 100644 src/emit/ebpf_c/lsm.rs delete mode 100644 src/emit/ebpf_c/maps.rs delete mode 100644 src/emit/ebpf_c/mod.rs delete mode 100644 src/emit/ebpf_c/program.rs delete mode 100644 src/emit/ebpf_c/raw_tracepoint.rs delete mode 100644 src/emit/ebpf_c/sk.rs delete mode 100644 src/emit/ebpf_c/tc.rs delete mode 100644 src/emit/ebpf_c/tracepoint.rs delete mode 100644 src/emit/ebpf_c/write.rs delete mode 100644 src/emit/ebpf_c/xdp.rs delete mode 100644 src/emit/util.rs delete mode 100644 src/kernel_check.rs diff --git a/.gitignore b/.gitignore index 82c5ce6..45b2d40 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,7 @@ vmlinux.h build.sh +/examples + /test .snx \ No newline at end of file diff --git a/src/ast/event.rs b/src/ast/event.rs index 1e00297..f3c5ad5 100644 --- a/src/ast/event.rs +++ b/src/ast/event.rs @@ -32,10 +32,7 @@ pub enum EventType { Bytes(u32), /// Fixed-size integer array (e.g. u32[16]) - Array { - elem: PrimitiveType, - len: u32, - }, + Array { elem: PrimitiveType, len: u32 }, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src/ast/map.rs b/src/ast/map.rs index 3805759..0479d22 100644 --- a/src/ast/map.rs +++ b/src/ast/map.rs @@ -1,4 +1,3 @@ - use crate::parser::SourceLoc; #[derive(Debug, Clone)] @@ -16,7 +15,7 @@ pub enum MapType { Hash, Array, Ringbuf, - LruHash, + LruHash, ProgArray, } @@ -26,4 +25,4 @@ pub enum Type { U64, I32, I64, -} \ No newline at end of file +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 5f09d73..4fd900c 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1,13 +1,12 @@ - -pub mod program; +pub mod event; pub mod map; +pub mod program; pub mod unit; -pub mod event; -pub use program::Program; +pub use event::{EventDecl, EventField, EventType, PrimitiveType}; pub use map::{MapDecl, MapType, Type}; +pub use program::Program; pub use unit::{ - Assignment, AssignmentOp, Expr, ExprKind, HeapVarDecl, - IfGuard, MethodCall, Stmt, StmtKind, Unit, VarDecl, VarType,BinaryExpr, BinOp + Assignment, AssignmentOp, BinOp, BinaryExpr, Expr, ExprKind, HeapVarDecl, IfGuard, MethodCall, + Stmt, StmtKind, Unit, VarDecl, VarType, }; -pub use event::{EventDecl, EventField, EventType, PrimitiveType}; \ No newline at end of file diff --git a/src/ast/program.rs b/src/ast/program.rs index 273e0c5..071ea88 100644 --- a/src/ast/program.rs +++ b/src/ast/program.rs @@ -6,4 +6,4 @@ pub struct Program { pub maps: Vec, pub units: Vec, pub events: Vec, -} \ No newline at end of file +} diff --git a/src/build/clang.rs b/src/build/clang.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/build/layout.rs b/src/build/layout.rs deleted file mode 100644 index f3d3dde..0000000 --- a/src/build/layout.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::fs; -use std::path::PathBuf; - -pub fn prepare_build_dir() -> Result { - let build_dir = PathBuf::from(".snx/build"); - - fs::create_dir_all(&build_dir) - .map_err(|e| format!("failed to create build directory: {e}"))?; - - Ok(build_dir) -} diff --git a/src/build/mod.rs b/src/build/mod.rs deleted file mode 100644 index 4c1f390..0000000 --- a/src/build/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod layout; -pub mod vmlinux; \ No newline at end of file diff --git a/src/build/vmlinux.rs b/src/build/vmlinux.rs deleted file mode 100644 index e526030..0000000 --- a/src/build/vmlinux.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::{fs, path::Path, process::Command}; - -pub fn ensure_vmlinux(build_dir: &Path) -> Result<(), String> { - let header = build_dir.join("vmlinux.h"); - - if header.exists() { - return Ok(()); - } - - println!("Generating vmlinux.h..."); - - let output = Command::new("bpftool") - .args([ - "btf", - "dump", - "file", - "/sys/kernel/btf/vmlinux", - "format", - "c", - ]) - .output() - .map_err(|e| format!("failed to run bpftool: {e}"))?; - - if !output.status.success() { - return Err("bpftool failed to generate vmlinux.h".into()); - } - - fs::write(&header, output.stdout) - .map_err(|e| format!("failed to write vmlinux.h: {e}"))?; - - Ok(()) -} diff --git a/src/compiler.rs b/src/compiler.rs index 2a86ce2..5eabf61 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,6 +1,7 @@ use crate::diagnostics::{ CompileDiagnostic, DiagnosticBuilder, ErrorCategory, ErrorCode, SourceManager, Span, }; +use crate::lexer::Lexer; use crate::parser; use miette::{IntoDiagnostic, Report, WrapErr}; use std::fs; @@ -70,7 +71,7 @@ impl ErrorHandler { } } -pub fn compile(input_path: &Path, _output_path: &Path) -> Result<(), miette::Report> { +pub fn compile(input_path: &Path, output_path: &Path) -> Result<(), miette::Report> { match input_path.extension().and_then(|e| e.to_str()) { Some("snx") => {} _ => { @@ -101,29 +102,33 @@ pub fn compile(input_path: &Path, _output_path: &Path) -> Result<(), miette::Rep let mut error_handler = ErrorHandler::new(); let _file_id = error_handler.add_file(input_path.display().to_string(), src.clone()); - let program = match parser::parse(&src) { - Ok(prog) => prog, + + let tokens = match Lexer::new(&src).tokenize() { + Ok(tokens) => tokens, Err(e) => { let span = Span::new(_file_id, e.0.offset..e.0.offset + 1); + let code = lexical_error_code(&e.1); + + match error_handler.lexical_error(code, e.1.clone(), &span) { + Ok(diag) => { + let report = error_handler.report_error(diag); + eprintln!("{}", report); + return Err(report); + } + Err(_) => { + let report = miette::miette!("{}", e.1); + eprintln!("{}", report); + return Err(report); + } + } + } + }; - 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) - }; + let program = match parser::parse_tokens(tokens) { + Ok(prog) => prog, + Err(e) => { + let span = Span::new(_file_id, e.0.offset..e.0.offset + 1); + let error_result = error_handler.parse_error(e.1.clone(), &span); match error_result { Ok(diag) => { @@ -297,25 +302,12 @@ pub fn compile(input_path: &Path, _output_path: &Path) -> Result<(), miette::Rep } } - // Build steps: prepare build dir, ensure vmlinux.h, lower and emit - let build_dir = crate::build::layout::prepare_build_dir() - .map_err(|e| miette::miette!("{}", e)) - .wrap_err("Build failed")?; - - crate::build::vmlinux::ensure_vmlinux(&build_dir) - .map_err(|e| miette::miette!("{}", e)) - .wrap_err("Build failed")?; - - let file_stem = input_path.file_stem().unwrap().to_string_lossy(); - - let output = build_dir.join(format!("{file_stem}.o")); - let program_ir = crate::ir::lower_program(&program) .map_err(|e| miette::miette!("{:?}", e)) .wrap_err("Lowering failed")?; - // Emit eBPF code with stage-aware error handling - if let Err(e) = crate::emit::ebpf_c::program::emit_program(&program_ir, &output) { + // Emit native eBPF ELF object with stage-aware error handling. + if let Err(e) = crate::emit::ebpf::emit_program(&program_ir, output_path) { let span = Span::new(_file_id, 0..1); let error_msg = format!("Code generation failed: {}", e); @@ -335,3 +327,12 @@ pub fn compile(input_path: &Path, _output_path: &Path) -> Result<(), miette::Rep Ok(()) } + +fn lexical_error_code(message: &str) -> ErrorCode { + match message { + 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, + } +} diff --git a/src/diagnostics/mod.rs b/src/diagnostics/mod.rs index 9be2d57..f8c700e 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; pub use diagnostic::{CompileDiagnostic, DiagnosticBuilder}; pub use error_code::ErrorCode; -pub use source_manager::{FileId, Span, SourceManager}; +pub use source_manager::{FileId, SourceManager, Span}; diff --git a/src/diagnostics/source_manager.rs b/src/diagnostics/source_manager.rs index ace4ceb..53f1b0e 100644 --- a/src/diagnostics/source_manager.rs +++ b/src/diagnostics/source_manager.rs @@ -34,11 +34,20 @@ impl SourceManager { 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: Arc::new(content) }); + self.files.insert( + id, + SourceFile { + name, + content: Arc::new(content), + }, + ); id } - pub fn get_named_source(&self, id: FileId) -> Option>> { + pub fn get_named_source( + &self, + id: FileId, + ) -> Option>> { self.files .get(&id) .map(|f| miette::NamedSource::new(f.name.clone(), f.content.clone())) diff --git a/src/emit/ebpf/elf.rs b/src/emit/ebpf/elf.rs new file mode 100644 index 0000000..eefb145 --- /dev/null +++ b/src/emit/ebpf/elf.rs @@ -0,0 +1,642 @@ +use std::collections::HashMap; +use std::fs; +use std::path::Path; + +use crate::ast::{MapDecl, MapType, Type}; +use crate::ir::ProgramIr; + +use super::tracepoint::CompiledProgram; + +const ET_REL: u16 = 1; +const EM_BPF: u16 = 247; +const SHT_PROGBITS: u32 = 1; +const SHT_SYMTAB: u32 = 2; +const SHT_STRTAB: u32 = 3; +const SHT_REL: u32 = 9; +const SHF_WRITE: u64 = 1; +const SHF_ALLOC: u64 = 2; +const SHF_EXECINSTR: u64 = 4; +const STB_LOCAL: u8 = 0; +const STB_GLOBAL: u8 = 1; +const STT_OBJECT: u8 = 1; +const STT_FUNC: u8 = 2; +const R_BPF_64_64: u32 = 1; +const BTF_KIND_INT: u32 = 1; +const BTF_KIND_PTR: u32 = 2; +const BTF_KIND_ARRAY: u32 = 3; +const BTF_KIND_STRUCT: u32 = 4; +const BTF_KIND_VAR: u32 = 14; +const BTF_KIND_DATASEC: u32 = 15; +const BTF_VAR_GLOBAL_ALLOCATED: u32 = 1; +const BTF_INT_SIGNED: u32 = 1; + +#[derive(Debug, Clone)] +pub struct ProgramReloc { + pub offset: u64, + pub symbol: String, +} + +pub struct BpfObject { + sections: Vec
, + symbols: Vec, + map_symbols: HashMap, + btf_maps: Vec, +} + +#[derive(Clone)] +struct Section { + name: String, + sh_type: u32, + flags: u64, + align: u64, + entsize: u64, + link: u32, + info: u32, + data: Vec, +} + +struct Symbol { + name: String, + bind: u8, + typ: u8, + section: u16, + value: u64, + size: u64, +} + +impl BpfObject { + pub fn new() -> Self { + Self { + sections: vec![Section::null()], + symbols: vec![Symbol::null()], + map_symbols: HashMap::new(), + btf_maps: Vec::new(), + } + } + + pub fn add_license(&mut self, program: &ProgramIr) -> Result<(), String> { + let license = program + .units + .first() + .map(|u| u.license.as_str()) + .unwrap_or("GPL"); + let mut data = license.as_bytes().to_vec(); + data.push(0); + let section = self.add_section("license", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE, 1, data); + self.symbols.push(Symbol { + name: "LICENSE".to_string(), + bind: STB_GLOBAL, + typ: STT_OBJECT, + section, + value: 0, + size: license.len() as u64 + 1, + }); + Ok(()) + } + + pub fn add_maps(&mut self, maps: &[MapDecl]) -> Result<(), String> { + if maps.is_empty() { + return Ok(()); + } + + let maps_section = self.add_empty_section(".maps", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE, 8); + for map in maps { + let offset = align_to(self.sections[maps_section as usize].data.len() as u64, 8); + while self.sections[maps_section as usize].data.len() < offset as usize { + self.sections[maps_section as usize].data.push(0); + } + let map_size = btf_map_size(map); + let bytes = vec![0; map_size as usize]; + self.sections[maps_section as usize] + .data + .extend_from_slice(&bytes); + + let sym_idx = self.symbols.len(); + self.symbols.push(Symbol { + name: sanitize_ident(&map.name), + bind: STB_GLOBAL, + typ: STT_OBJECT, + section: maps_section, + value: offset, + size: bytes.len() as u64, + }); + self.map_symbols.insert(map.name.clone(), sym_idx); + self.btf_maps.push(BtfMap { + name: sanitize_ident(&map.name), + map_type: map.map_type, + key_type: map.key_type, + value_type: map.value_type, + max_entries: map.max_entries, + offset: offset as u32, + size: map_size, + }); + } + + let btf = build_btf(&self.btf_maps, self.sections[maps_section as usize].data.len() as u32)?; + self.add_section(".BTF", SHT_PROGBITS, 0, 4, btf); + + Ok(()) + } + + pub fn add_program( + &mut self, + section_name: &str, + symbol_name: &str, + program: CompiledProgram, + ) -> Result<(), String> { + let text_section = self.add_section( + section_name, + SHT_PROGBITS, + SHF_ALLOC | SHF_EXECINSTR, + 8, + program.code, + ); + self.symbols.push(Symbol { + name: sanitize_ident(symbol_name), + bind: STB_GLOBAL, + typ: STT_FUNC, + section: text_section, + value: 0, + size: self.sections[text_section as usize].data.len() as u64, + }); + + if !program.relocs.is_empty() { + let mut rel_data = Vec::with_capacity(program.relocs.len() * 16); + for reloc in program.relocs { + let sym = *self.map_symbols.get(&reloc.symbol).ok_or_else(|| { + format!("unknown map referenced by codegen: {}", reloc.symbol) + })?; + rel_data.extend_from_slice(&reloc.offset.to_le_bytes()); + let info = ((sym as u64) << 32) | R_BPF_64_64 as u64; + rel_data.extend_from_slice(&info.to_le_bytes()); + } + + let rel_section_name = format!(".rel{}", section_name); + let rel_section = self.add_section(&rel_section_name, SHT_REL, 0, 8, rel_data); + self.sections[rel_section as usize].entsize = 16; + self.sections[rel_section as usize].info = text_section as u32; + } + + Ok(()) + } + + pub fn write(mut self, path: &Path) -> Result<(), String> { + let symtab_index = self.sections.len() as u16; + let (symtab, strtab) = self.build_symtab(); + let strtab_index = symtab_index + 1; + + for section in &mut self.sections { + if section.sh_type == SHT_REL { + section.link = symtab_index as u32; + } + } + + self.sections.push(Section { + name: ".symtab".to_string(), + sh_type: SHT_SYMTAB, + flags: 0, + align: 8, + entsize: 24, + link: strtab_index as u32, + info: 1, + data: symtab, + }); + self.sections.push(Section { + name: ".strtab".to_string(), + sh_type: SHT_STRTAB, + flags: 0, + align: 1, + entsize: 0, + link: 0, + info: 0, + data: strtab, + }); + + self.sections.push(Section { + name: ".shstrtab".to_string(), + sh_type: SHT_STRTAB, + flags: 0, + align: 1, + entsize: 0, + link: 0, + info: 0, + data: Vec::new(), + }); + let shstrtab_index = (self.sections.len() - 1) as u16; + let shstrtab = build_shstrtab(&self.sections); + self.sections[shstrtab_index as usize].data = shstrtab.data; + + let bytes = self.encode(shstrtab_index, &shstrtab.name_offsets)?; + if let Some(parent) = path.parent() { + if !parent.as_os_str().is_empty() { + fs::create_dir_all(parent) + .map_err(|e| format!("failed to create output directory: {e}"))?; + } + } + fs::write(path, bytes).map_err(|e| format!("failed to write object file: {e}")) + } + + fn add_empty_section(&mut self, name: &str, sh_type: u32, flags: u64, align: u64) -> u16 { + self.add_section(name, sh_type, flags, align, Vec::new()) + } + + fn add_section( + &mut self, + name: &str, + sh_type: u32, + flags: u64, + align: u64, + data: Vec, + ) -> u16 { + let index = self.sections.len() as u16; + self.sections.push(Section { + name: name.to_string(), + sh_type, + flags, + align, + entsize: 0, + link: 0, + info: 0, + data, + }); + index + } + + fn build_symtab(&self) -> (Vec, Vec) { + let mut strtab = vec![0_u8]; + let mut data = Vec::with_capacity(self.symbols.len() * 24); + for sym in &self.symbols { + let name_off = add_string(&mut strtab, &sym.name); + data.extend_from_slice(&name_off.to_le_bytes()); + data.push((sym.bind << 4) | (sym.typ & 0x0f)); + data.push(0); + data.extend_from_slice(&sym.section.to_le_bytes()); + data.extend_from_slice(&sym.value.to_le_bytes()); + data.extend_from_slice(&sym.size.to_le_bytes()); + } + (data, strtab) + } + + fn encode(&self, shstrtab_index: u16, name_offsets: &[u32]) -> Result, String> { + let mut out = vec![0_u8; 64]; + let mut section_offsets = Vec::with_capacity(self.sections.len()); + + for section in &self.sections { + if section.name.is_empty() { + section_offsets.push(0); + continue; + } + let offset = align_to(out.len() as u64, section.align.max(1)); + while out.len() < offset as usize { + out.push(0); + } + section_offsets.push(offset); + out.extend_from_slice(§ion.data); + } + + let shoff = align_to(out.len() as u64, 8); + while out.len() < shoff as usize { + out.push(0); + } + + for (idx, section) in self.sections.iter().enumerate() { + let name_offset = *name_offsets + .get(idx) + .ok_or_else(|| format!("missing section name offset for index {}", idx))?; + out.extend_from_slice(&name_offset.to_le_bytes()); + out.extend_from_slice(§ion.sh_type.to_le_bytes()); + out.extend_from_slice(§ion.flags.to_le_bytes()); + out.extend_from_slice(&0_u64.to_le_bytes()); + out.extend_from_slice(§ion_offsets[idx].to_le_bytes()); + out.extend_from_slice(&(section.data.len() as u64).to_le_bytes()); + out.extend_from_slice(§ion.link.to_le_bytes()); + out.extend_from_slice(§ion.info.to_le_bytes()); + out.extend_from_slice(§ion.align.max(1).to_le_bytes()); + out.extend_from_slice(§ion.entsize.to_le_bytes()); + } + + out[0..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']); + out[4] = 2; + out[5] = 1; + out[6] = 1; + out[16..18].copy_from_slice(&ET_REL.to_le_bytes()); + out[18..20].copy_from_slice(&EM_BPF.to_le_bytes()); + out[20..24].copy_from_slice(&1_u32.to_le_bytes()); + out[32..40].copy_from_slice(&0_u64.to_le_bytes()); + out[40..48].copy_from_slice(&shoff.to_le_bytes()); + out[52..54].copy_from_slice(&64_u16.to_le_bytes()); + out[54..56].copy_from_slice(&0_u16.to_le_bytes()); + out[56..58].copy_from_slice(&0_u16.to_le_bytes()); + out[58..60].copy_from_slice(&64_u16.to_le_bytes()); + out[60..62].copy_from_slice(&(self.sections.len() as u16).to_le_bytes()); + out[62..64].copy_from_slice(&shstrtab_index.to_le_bytes()); + + Ok(out) + } +} + +impl Section { + fn null() -> Self { + Self { + name: String::new(), + sh_type: 0, + flags: 0, + align: 0, + entsize: 0, + link: 0, + info: 0, + data: Vec::new(), + } + } +} + +impl Symbol { + fn null() -> Self { + Self { + name: String::new(), + bind: STB_LOCAL, + typ: 0, + section: 0, + value: 0, + size: 0, + } + } +} + +#[derive(Clone)] +struct BtfMap { + name: String, + map_type: MapType, + key_type: Option, + value_type: Option, + max_entries: Option, + offset: u32, + size: u32, +} + +#[derive(Clone, Copy)] +struct BtfIntIds { + int_id: u32, + u32_id: u32, + u64_id: u32, + i32_id: u32, + i64_id: u32, +} + +struct BtfBuilder { + types: Vec, + strings: Vec, + next_type_id: u32, +} + +impl BtfBuilder { + fn new() -> Self { + Self { + types: Vec::new(), + strings: vec![0], + next_type_id: 1, + } + } + + fn string(&mut self, value: &str) -> u32 { + add_string(&mut self.strings, value) + } + + fn type_id(&mut self) -> u32 { + let id = self.next_type_id; + self.next_type_id += 1; + id + } + + fn add_int(&mut self, name: &str, size: u32, signed: bool) -> u32 { + let id = self.type_id(); + let name_off = self.string(name); + self.types.extend_from_slice(&name_off.to_le_bytes()); + self.types + .extend_from_slice(&btf_info(BTF_KIND_INT, 0).to_le_bytes()); + self.types.extend_from_slice(&size.to_le_bytes()); + let encoding = if signed { BTF_INT_SIGNED } else { 0 }; + let int_data = (encoding << 24) | (size * 8); + self.types.extend_from_slice(&int_data.to_le_bytes()); + id + } + + fn add_ptr(&mut self, pointee_type: u32) -> u32 { + let id = self.type_id(); + self.types.extend_from_slice(&0_u32.to_le_bytes()); + self.types + .extend_from_slice(&btf_info(BTF_KIND_PTR, 0).to_le_bytes()); + self.types.extend_from_slice(&pointee_type.to_le_bytes()); + id + } + + fn add_array(&mut self, elem_type: u32, index_type: u32, nelems: u32) -> u32 { + let id = self.type_id(); + self.types.extend_from_slice(&0_u32.to_le_bytes()); + self.types + .extend_from_slice(&btf_info(BTF_KIND_ARRAY, 0).to_le_bytes()); + self.types.extend_from_slice(&0_u32.to_le_bytes()); + self.types.extend_from_slice(&elem_type.to_le_bytes()); + self.types.extend_from_slice(&index_type.to_le_bytes()); + self.types.extend_from_slice(&nelems.to_le_bytes()); + id + } + + fn add_struct(&mut self, members: &[(String, u32)]) -> u32 { + let id = self.type_id(); + self.types.extend_from_slice(&0_u32.to_le_bytes()); + self.types + .extend_from_slice(&btf_info(BTF_KIND_STRUCT, members.len() as u32).to_le_bytes()); + self.types + .extend_from_slice(&((members.len() as u32) * 8).to_le_bytes()); + + for (idx, (name, typ)) in members.iter().enumerate() { + let name_off = self.string(name); + self.types.extend_from_slice(&name_off.to_le_bytes()); + self.types.extend_from_slice(&typ.to_le_bytes()); + self.types + .extend_from_slice(&((idx as u32) * 64).to_le_bytes()); + } + id + } + + fn add_var(&mut self, name: &str, typ: u32) -> u32 { + let id = self.type_id(); + let name_off = self.string(name); + self.types.extend_from_slice(&name_off.to_le_bytes()); + self.types + .extend_from_slice(&btf_info(BTF_KIND_VAR, 0).to_le_bytes()); + self.types.extend_from_slice(&typ.to_le_bytes()); + self.types + .extend_from_slice(&BTF_VAR_GLOBAL_ALLOCATED.to_le_bytes()); + id + } + + fn add_datasec(&mut self, name: &str, size: u32, vars: &[(u32, u32, u32)]) -> u32 { + let id = self.type_id(); + let name_off = self.string(name); + self.types.extend_from_slice(&name_off.to_le_bytes()); + self.types + .extend_from_slice(&btf_info(BTF_KIND_DATASEC, vars.len() as u32).to_le_bytes()); + self.types.extend_from_slice(&size.to_le_bytes()); + for (typ, offset, size) in vars { + self.types.extend_from_slice(&typ.to_le_bytes()); + self.types.extend_from_slice(&offset.to_le_bytes()); + self.types.extend_from_slice(&size.to_le_bytes()); + } + id + } + + fn finish(self) -> Vec { + let hdr_len = 24_u32; + let type_len = self.types.len() as u32; + let str_len = self.strings.len() as u32; + let mut out = Vec::with_capacity(hdr_len as usize + self.types.len() + self.strings.len()); + out.extend_from_slice(&0xeb9f_u16.to_le_bytes()); + out.push(1); + out.push(0); + out.extend_from_slice(&hdr_len.to_le_bytes()); + out.extend_from_slice(&0_u32.to_le_bytes()); + out.extend_from_slice(&type_len.to_le_bytes()); + out.extend_from_slice(&type_len.to_le_bytes()); + out.extend_from_slice(&str_len.to_le_bytes()); + out.extend_from_slice(&self.types); + out.extend_from_slice(&self.strings); + out + } +} + +fn build_btf(maps: &[BtfMap], maps_sec_size: u32) -> Result, String> { + let mut btf = BtfBuilder::new(); + let ints = BtfIntIds { + int_id: btf.add_int("int", 4, true), + u32_id: btf.add_int("u32", 4, false), + u64_id: btf.add_int("u64", 8, false), + i32_id: btf.add_int("i32", 4, true), + i64_id: btf.add_int("i64", 8, true), + }; + + let mut datasec_vars = Vec::with_capacity(maps.len()); + for map in maps { + let struct_id = add_btf_map_struct(&mut btf, ints, map)?; + let var_id = btf.add_var(&map.name, struct_id); + datasec_vars.push((var_id, map.offset, map.size)); + } + btf.add_datasec(".maps", maps_sec_size, &datasec_vars); + Ok(btf.finish()) +} + +fn add_btf_map_struct( + btf: &mut BtfBuilder, + ints: BtfIntIds, + map: &BtfMap, +) -> Result { + let mut members = Vec::new(); + let type_array = btf.add_array(ints.int_id, ints.int_id, map_type_num(map.map_type)); + let type_ptr = btf.add_ptr(type_array); + members.push(("type".to_string(), type_ptr)); + + if let Some(key_type) = map.key_type { + let typ = btf_type_for_source_type(ints, key_type); + members.push(("key".to_string(), btf.add_ptr(typ))); + } + if let Some(value_type) = map.value_type { + let typ = btf_type_for_source_type(ints, value_type); + members.push(("value".to_string(), btf.add_ptr(typ))); + } + if let Some(max_entries) = map.max_entries { + let max_entries_array = btf.add_array(ints.int_id, ints.int_id, max_entries); + let max_entries_ptr = btf.add_ptr(max_entries_array); + members.push(("max_entries".to_string(), max_entries_ptr)); + } + + if members.is_empty() { + return Err(format!("map '{}' has no BTF attributes", map.name)); + } + + Ok(btf.add_struct(&members)) +} + +fn btf_type_for_source_type(ints: BtfIntIds, ty: Type) -> u32 { + match ty { + Type::U32 => ints.u32_id, + Type::U64 => ints.u64_id, + Type::I32 => ints.i32_id, + Type::I64 => ints.i64_id, + } +} + +fn btf_map_size(map: &MapDecl) -> u32 { + let mut fields = 1; + fields += u32::from(map.key_type.is_some()); + fields += u32::from(map.value_type.is_some()); + fields += u32::from(map.max_entries.is_some()); + fields * 8 +} + +fn btf_info(kind: u32, vlen: u32) -> u32 { + (kind << 24) | vlen +} + +fn map_type_num(map_type: MapType) -> u32 { + match map_type { + MapType::Hash => 1, + MapType::Array => 2, + MapType::ProgArray => 3, + MapType::LruHash => 9, + MapType::Ringbuf => 27, + } +} + +fn sanitize_ident(name: &str) -> String { + let mut out = String::with_capacity(name.len()); + for (idx, ch) in name.chars().enumerate() { + let ok = if idx == 0 { + ch.is_ascii_alphabetic() || ch == '_' + } else { + ch.is_ascii_alphanumeric() || ch == '_' + }; + out.push(if ok { ch } else { '_' }); + } + if out.is_empty() { + "_solnix_symbol".to_string() + } else { + out + } +} + +fn add_string(buf: &mut Vec, s: &str) -> u32 { + let off = buf.len() as u32; + buf.extend_from_slice(s.as_bytes()); + buf.push(0); + off +} + +struct ShStrTab { + data: Vec, + name_offsets: Vec, +} + +fn build_shstrtab(sections: &[Section]) -> ShStrTab { + let mut data = vec![0_u8]; + let mut name_offsets = Vec::with_capacity(sections.len()); + for section in sections { + if section.name.is_empty() { + name_offsets.push(0); + } else { + let off = data.len() as u32; + name_offsets.push(off); + data.extend_from_slice(section.name.as_bytes()); + data.push(0); + } + } + ShStrTab { data, name_offsets } +} + +fn align_to(value: u64, align: u64) -> u64 { + if align <= 1 { + value + } else { + (value + align - 1) & !(align - 1) + } +} diff --git a/src/emit/ebpf/insn.rs b/src/emit/ebpf/insn.rs new file mode 100644 index 0000000..1dc5680 --- /dev/null +++ b/src/emit/ebpf/insn.rs @@ -0,0 +1,82 @@ +#[derive(Debug, Clone)] +pub struct Insn { + pub code: u8, + pub dst: u8, + pub src: u8, + pub off: i16, + pub imm: i32, +} + +impl Insn { + pub fn new(code: u8, dst: u8, src: u8, off: i16, imm: i32) -> Self { + Self { + code, + dst, + src, + off, + imm, + } + } + + pub fn to_le_bytes(&self) -> [u8; 8] { + let mut out = [0_u8; 8]; + out[0] = self.code; + out[1] = (self.dst & 0x0f) | ((self.src & 0x0f) << 4); + out[2..4].copy_from_slice(&self.off.to_le_bytes()); + out[4..8].copy_from_slice(&self.imm.to_le_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 R6: u8 = 6; +pub const R10: u8 = 10; + +pub const BPF_LD_DW_IMM: u8 = 0x18; +pub const BPF_CALL: u8 = 0x85; +pub const BPF_EXIT: u8 = 0x95; +pub const BPF_JA: u8 = 0x05; +pub const BPF_JEQ_IMM: u8 = 0x15; +pub const BPF_JNE_IMM: u8 = 0x55; +pub const BPF_MOV64_IMM: u8 = 0xb7; +pub const BPF_MOV64_REG: u8 = 0xbf; + +pub fn alu64_imm(op: crate::ir::BinaryOp) -> u8 { + match op { + crate::ir::BinaryOp::Add => 0x07, + crate::ir::BinaryOp::Sub => 0x17, + crate::ir::BinaryOp::Mul => 0x27, + crate::ir::BinaryOp::Div => 0x37, + crate::ir::BinaryOp::Shl => 0x67, + crate::ir::BinaryOp::Shr => 0x77, + crate::ir::BinaryOp::Mod => 0x97, + } +} + +pub fn alu64_reg(op: crate::ir::BinaryOp) -> u8 { + alu64_imm(op) | 0x08 +} + +pub fn ldx_mem(size: u8) -> Result { + match size { + 1 => Ok(0x71), + 2 => Ok(0x69), + 4 => Ok(0x61), + 8 => Ok(0x79), + _ => Err(format!("unsupported load size: {}", size)), + } +} + +pub fn stx_mem(size: u8) -> Result { + match size { + 1 => Ok(0x73), + 2 => Ok(0x6b), + 4 => Ok(0x63), + 8 => Ok(0x7b), + _ => Err(format!("unsupported store size: {}", size)), + } +} diff --git a/src/emit/ebpf/mod.rs b/src/emit/ebpf/mod.rs new file mode 100644 index 0000000..b56f2c0 --- /dev/null +++ b/src/emit/ebpf/mod.rs @@ -0,0 +1,36 @@ +mod elf; +mod insn; +mod tracepoint; + +use std::path::Path; + +use crate::ast::unit::ProgramKind; +use crate::ir::ProgramIr; + +pub fn emit_program(program: &ProgramIr, output: &Path) -> Result<(), String> { + for unit in &program.units { + let section = unit + .sections + .first() + .ok_or_else(|| format!("unit '{}' has no section", unit.name))?; + + if unit.program_type != ProgramKind::Tracepoint || !section.starts_with("tracepoint/") { + return Err(format!( + "native Rust backend currently supports only tracepoint sections, got '{}'", + section + )); + } + } + + let mut object = elf::BpfObject::new(); + object.add_license(program)?; + object.add_maps(&program.maps)?; + + for unit in &program.units { + let section = unit.sections.first().expect("validated above"); + let compiled = tracepoint::compile_tracepoint(unit)?; + object.add_program(section, &unit.name, compiled)?; + } + + object.write(output) +} diff --git a/src/emit/ebpf/tracepoint.rs b/src/emit/ebpf/tracepoint.rs new file mode 100644 index 0000000..c691c25 --- /dev/null +++ b/src/emit/ebpf/tracepoint.rs @@ -0,0 +1,398 @@ +use std::collections::{HashMap, HashSet}; + +use crate::ir::unit::Terminator; +use crate::ir::{BinaryOp, Opcode, Operand, UnitIr, VarId}; + +use super::elf::ProgramReloc; +use super::insn::*; + +#[derive(Debug)] +pub struct CompiledProgram { + pub code: Vec, + pub relocs: Vec, +} + +struct Builder { + insns: Vec, + relocs: Vec, + stack_slots: HashMap, + scratch_slots: Vec, + fixups: Vec<(usize, u32)>, + block_offsets: HashMap, + next_scratch: usize, +} + +impl Builder { + fn new(unit: &UnitIr) -> Result { + let mut ids = HashSet::new(); + for block in &unit.blocks { + for inst in &block.instructions { + ids.insert(inst.result.0); + for op in &inst.operands { + if let Operand::Var(VarId(id)) = op { + ids.insert(*id); + } + } + } + match &block.terminator { + Terminator::Return(Operand::Var(VarId(id))) + | Terminator::Branch { + condition: Operand::Var(VarId(id)), + .. + } => { + ids.insert(*id); + } + _ => {} + } + } + + let mut ids: Vec = ids.into_iter().collect(); + ids.sort_unstable(); + + let scratch_count = 8_usize; + let total_slots = ids.len() + scratch_count; + let frame_bytes = total_slots * 8; + if frame_bytes > 512 { + return Err(format!( + "tracepoint '{}' needs {} bytes of eBPF stack, maximum is 512", + unit.name, frame_bytes + )); + } + + let mut stack_slots = HashMap::new(); + for (idx, id) in ids.iter().enumerate() { + stack_slots.insert(*id, -8 * (idx as i16 + 1)); + } + + let scratch_base = ids.len() as i16; + let scratch_slots = (0..scratch_count) + .map(|i| -8 * (scratch_base + i as i16 + 1)) + .collect(); + + Ok(Self { + insns: Vec::new(), + relocs: Vec::new(), + stack_slots, + scratch_slots, + fixups: Vec::new(), + block_offsets: HashMap::new(), + next_scratch: 0, + }) + } + + fn push(&mut self, insn: Insn) { + self.insns.push(insn); + } + + fn bytes_len(&self) -> u64 { + (self.insns.len() * 8) as u64 + } + + fn emit_load_operand(&mut self, dst: u8, op: &Operand) -> Result<(), String> { + match op { + Operand::Immediate(value) => { + let imm = imm32(*value)?; + self.push(Insn::new(BPF_MOV64_IMM, dst, 0, 0, imm)); + } + Operand::Var(VarId(id)) => { + let off = *self + .stack_slots + .get(id) + .ok_or_else(|| format!("missing stack slot for v{}", id))?; + self.push(Insn::new(ldx_mem(8)?, dst, R10, off, 0)); + } + } + Ok(()) + } + + fn emit_store_var(&mut self, id: VarId, src: u8) -> Result<(), String> { + let off = *self + .stack_slots + .get(&id.0) + .ok_or_else(|| format!("missing stack slot for v{}", id.0))?; + self.push(Insn::new(stx_mem(8)?, R10, src, off, 0)); + Ok(()) + } + + fn emit_operand_addr(&mut self, dst: u8, op: &Operand) -> Result<(), String> { + match op { + Operand::Var(VarId(id)) => { + let off = *self + .stack_slots + .get(id) + .ok_or_else(|| format!("missing stack slot for v{}", id))?; + self.push(Insn::new(BPF_MOV64_REG, dst, R10, 0, 0)); + self.push(Insn::new(0x07, dst, 0, 0, off as i32)); + } + Operand::Immediate(value) => { + let slot = self.take_scratch()?; + self.push(Insn::new(BPF_MOV64_IMM, dst, 0, 0, imm32(*value)?)); + self.push(Insn::new(stx_mem(8)?, R10, dst, slot, 0)); + self.push(Insn::new(BPF_MOV64_REG, dst, R10, 0, 0)); + self.push(Insn::new(0x07, dst, 0, 0, slot as i32)); + } + } + Ok(()) + } + + fn take_scratch(&mut self) -> Result { + let slot = self + .scratch_slots + .get(self.next_scratch % self.scratch_slots.len()) + .copied() + .ok_or_else(|| "no scratch stack slots available".to_string())?; + self.next_scratch += 1; + Ok(slot) + } + + fn emit_map_ldimm(&mut self, dst: u8, map_name: &str) { + let offset = self.bytes_len(); + self.push(Insn::new(BPF_LD_DW_IMM, dst, 1, 0, 0)); + self.push(Insn::new(0, 0, 0, 0, 0)); + self.relocs.push(ProgramReloc { + offset, + symbol: map_name.to_string(), + }); + } + + fn emit_jump_to_block(&mut self, target: u32) { + let idx = self.insns.len(); + self.push(Insn::new(BPF_JA, 0, 0, 0, 0)); + self.fixups.push((idx, target)); + } + + fn patch_jumps(&mut self) -> Result<(), String> { + for (idx, block_id) in &self.fixups { + let target = *self + .block_offsets + .get(block_id) + .ok_or_else(|| format!("invalid jump target block {}", block_id))?; + let rel = target as isize - *idx as isize - 1; + if rel < i16::MIN as isize || rel > i16::MAX as isize { + return Err(format!("jump to block {} exceeds eBPF range", block_id)); + } + self.insns[*idx].off = rel as i16; + } + Ok(()) + } +} + +pub fn compile_tracepoint(unit: &UnitIr) -> Result { + let mut b = Builder::new(unit)?; + let branch_cond_vars = branch_condition_vars(unit); + b.push(Insn::new(BPF_MOV64_REG, R6, R1, 0, 0)); + + for block in &unit.blocks { + b.block_offsets.insert(block.id.0, b.insns.len()); + + for inst in &block.instructions { + let _result_type = inst.result_type; + match &inst.opcode { + Opcode::Binary { op } => emit_binary(&mut b, inst.result, *op, &inst.operands)?, + Opcode::LoadKey => { + let ptr = inst + .operands + .first() + .ok_or_else(|| "LoadKey expects one operand".to_string())?; + b.emit_load_operand(R1, ptr)?; + b.push(Insn::new(ldx_mem(8)?, R0, R1, 0, 0)); + b.emit_store_var(inst.result, R0)?; + } + Opcode::Store { size } => { + if inst.operands.len() != 2 { + return Err("Store expects pointer and value operands".to_string()); + } + b.emit_load_operand(R1, &inst.operands[0])?; + b.emit_load_operand(R2, &inst.operands[1])?; + b.push(Insn::new(stx_mem(*size)?, R1, R2, 0, 0)); + } + Opcode::LoadCtx { offset, size } | Opcode::LoadPacket { offset, size } => { + b.push(Insn::new(ldx_mem(*size)?, R0, R6, checked_i16(*offset)?, 0)); + b.emit_store_var(inst.result, R0)?; + } + Opcode::HelperCall { id } => { + emit_helper_call(&mut b, inst.result, *id, &inst.operands)? + } + Opcode::CallMap { map_name } => { + let key = inst + .operands + .first() + .ok_or_else(|| "map lookup expects a key operand".to_string())?; + b.emit_map_ldimm(R1, map_name); + b.emit_operand_addr(R2, key)?; + b.push(Insn::new(BPF_CALL, 0, 0, 0, 1)); + b.emit_store_var(inst.result, R0)?; + } + Opcode::UpdateMap { map_name } => { + if inst.operands.len() != 2 { + return Err("map update expects key and value operands".to_string()); + } + b.emit_map_ldimm(R1, map_name); + b.emit_operand_addr(R2, &inst.operands[0])?; + b.emit_operand_addr(R3, &inst.operands[1])?; + b.push(Insn::new(BPF_MOV64_IMM, R4, 0, 0, 0)); + b.push(Insn::new(BPF_CALL, 0, 0, 0, 2)); + b.emit_store_var(inst.result, R0)?; + } + Opcode::NullCheck => { + let ptr = inst + .operands + .first() + .ok_or_else(|| "NullCheck expects pointer operand".to_string())?; + b.emit_load_operand(R0, ptr)?; + if branch_cond_vars.contains(&inst.result.0) { + b.push(Insn::new(BPF_JEQ_IMM, R0, 0, 2, 0)); + b.push(Insn::new(BPF_MOV64_IMM, R0, 0, 0, 1)); + b.push(Insn::new(BPF_JA, 0, 0, 1, 0)); + b.push(Insn::new(BPF_MOV64_IMM, R0, 0, 0, 0)); + b.emit_store_var(inst.result, R0)?; + } else { + b.push(Insn::new(BPF_JEQ_IMM, R0, 0, 1, 0)); + b.push(Insn::new(BPF_JA, 0, 0, 2, 0)); + b.push(Insn::new(BPF_MOV64_IMM, R0, 0, 0, 0)); + b.push(Insn::new(BPF_EXIT, 0, 0, 0, 0)); + b.push(Insn::new(BPF_MOV64_IMM, R0, 0, 0, 1)); + b.emit_store_var(inst.result, R0)?; + } + } + Opcode::RingBufReserve { map_name, size } => { + b.emit_map_ldimm(R1, map_name); + b.push(Insn::new(BPF_MOV64_IMM, R2, 0, 0, imm32(*size as i64)?)); + b.push(Insn::new(BPF_MOV64_IMM, R3, 0, 0, 0)); + b.push(Insn::new(BPF_CALL, 0, 0, 0, 131)); + b.emit_store_var(inst.result, R0)?; + } + Opcode::RingBufSubmit { map_name } => { + let _map_name = map_name; + let ptr = inst + .operands + .first() + .ok_or_else(|| "ringbuf submit expects pointer operand".to_string())?; + b.emit_load_operand(R1, ptr)?; + b.push(Insn::new(BPF_MOV64_IMM, R2, 0, 0, 0)); + b.push(Insn::new(BPF_CALL, 0, 0, 0, 132)); + b.push(Insn::new(BPF_MOV64_IMM, R0, 0, 0, 0)); + b.emit_store_var(inst.result, R0)?; + } + Opcode::CopyCtxToMem { offset, size } => { + let dest = inst + .operands + .first() + .ok_or_else(|| "CopyCtxToMem expects destination operand".to_string())?; + b.emit_load_operand(R1, dest)?; + b.push(Insn::new(BPF_MOV64_IMM, R2, 0, 0, imm32(*size as i64)?)); + b.push(Insn::new(BPF_MOV64_REG, R3, R6, 0, 0)); + b.push(Insn::new(0x07, R3, 0, 0, *offset)); + b.push(Insn::new(BPF_CALL, 0, 0, 0, 113)); + b.emit_store_var(inst.result, R0)?; + } + } + } + + match &block.terminator { + Terminator::Return(op) => { + b.emit_load_operand(R0, op)?; + b.push(Insn::new(BPF_EXIT, 0, 0, 0, 0)); + } + Terminator::Jump(target) => b.emit_jump_to_block(target.0), + Terminator::Branch { + condition, + true_block, + false_block, + } => { + b.emit_load_operand(R0, condition)?; + let jne_idx = b.insns.len(); + b.push(Insn::new(BPF_JNE_IMM, R0, 0, 0, 0)); + b.emit_jump_to_block(false_block.0); + b.fixups.push((jne_idx, true_block.0)); + } + } + } + + b.patch_jumps()?; + + let mut code = Vec::with_capacity(b.insns.len() * 8); + for insn in &b.insns { + code.extend_from_slice(&insn.to_le_bytes()); + } + + Ok(CompiledProgram { + code, + relocs: b.relocs, + }) +} + +fn branch_condition_vars(unit: &UnitIr) -> HashSet { + let mut out = HashSet::new(); + for block in &unit.blocks { + if let Terminator::Branch { + condition: Operand::Var(VarId(id)), + .. + } = &block.terminator + { + out.insert(*id); + } + } + out +} + +fn emit_binary( + b: &mut Builder, + result: VarId, + op: BinaryOp, + operands: &[Operand], +) -> Result<(), String> { + if operands.len() != 2 { + return Err("binary instruction expects two operands".to_string()); + } + b.emit_load_operand(R0, &operands[0])?; + match &operands[1] { + Operand::Immediate(value) => b.push(Insn::new(alu64_imm(op), R0, 0, 0, imm32(*value)?)), + Operand::Var(_) => { + b.emit_load_operand(R1, &operands[1])?; + b.push(Insn::new(alu64_reg(op), R0, R1, 0, 0)); + } + } + b.emit_store_var(result, R0) +} + +fn emit_helper_call( + b: &mut Builder, + result: VarId, + id: u32, + operands: &[Operand], +) -> Result<(), String> { + match id { + 5 | 14 | 15 | 35 => { + if !operands.is_empty() { + return Err(format!("helper {} expects no operands", id)); + } + } + 16 => { + return Err( + "ctx.get_current_comm requires a destination buffer; IR does not carry one yet" + .to_string(), + ); + } + 202 | 204 => { + if operands.len() != 3 { + return Err(format!("helper {} expects dest, size, src operands", id)); + } + b.emit_load_operand(R1, &operands[0])?; + b.emit_load_operand(R2, &operands[1])?; + b.emit_load_operand(R3, &operands[2])?; + } + other => return Err(format!("unsupported helper id {}", other)), + } + + b.push(Insn::new(BPF_CALL, 0, 0, 0, id as i32)); + b.emit_store_var(result, R0) +} + +fn imm32(value: i64) -> Result { + i32::try_from(value).map_err(|_| format!("immediate {} does not fit in eBPF imm32", value)) +} + +fn checked_i16(value: i32) -> Result { + i16::try_from(value).map_err(|_| format!("offset {} does not fit in eBPF off16", value)) +} diff --git a/src/emit/ebpf_c/cgroup.rs b/src/emit/ebpf_c/cgroup.rs deleted file mode 100644 index 6d906ce..0000000 --- a/src/emit/ebpf_c/cgroup.rs +++ /dev/null @@ -1,38 +0,0 @@ -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 deleted file mode 100644 index f9c4714..0000000 --- a/src/emit/ebpf_c/fentry.rs +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index f516127..0000000 --- a/src/emit/ebpf_c/helpers.rs +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index c2fd5fa..0000000 --- a/src/emit/ebpf_c/kprobe.rs +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index 20bc4a6..0000000 --- a/src/emit/ebpf_c/lsm.rs +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index e0a8195..0000000 --- a/src/emit/ebpf_c/maps.rs +++ /dev/null @@ -1,82 +0,0 @@ -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", - } -} - -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 deleted file mode 100644 index 0d2ca4e..0000000 --- a/src/emit/ebpf_c/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 49dd1d5..0000000 --- a/src/emit/ebpf_c/program.rs +++ /dev/null @@ -1,113 +0,0 @@ -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 deleted file mode 100644 index c22bce9..0000000 --- a/src/emit/ebpf_c/raw_tracepoint.rs +++ /dev/null @@ -1,30 +0,0 @@ -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 deleted file mode 100644 index ae7bd36..0000000 --- a/src/emit/ebpf_c/sk.rs +++ /dev/null @@ -1,40 +0,0 @@ -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 deleted file mode 100644 index 08a5654..0000000 --- a/src/emit/ebpf_c/tc.rs +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index 2b55bcd..0000000 --- a/src/emit/ebpf_c/tracepoint.rs +++ /dev/null @@ -1,516 +0,0 @@ -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)?; - } - - if unit.blocks.len() > 1 { - 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 - | Opcode::HelperCall { .. } - | Opcode::RingBufSubmit { .. } - ); - - 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 } => { - match *id { - 5 => { - let res = res_name.unwrap(); - writeln!(out, " {} = bpf_ktime_get_ns();", res).map_err(err)?; - } - 14 => { - let res = res_name.unwrap(); - writeln!(out, " {} = bpf_get_current_pid_tgid();", res) - .map_err(err)?; - } - 15 => { - let res = res_name.unwrap(); - writeln!(out, " {} = bpf_get_current_uid_gid();", res) - .map_err(err)?; - } - 35 => { - let res = res_name.unwrap(); - writeln!(out, " {} = bpf_get_current_task();", res).map_err(err)?; - } - 202 => { - // bpf_probe_read_user_str(void *dst, u32 size, const void *unsafe_ptr) - if inst.operands.len() != 3 { - return Err( - "internal error: helper 202 expects 3 operands".to_string() - ); - } - let dst = format_operand(&inst.operands[0]); - let size = format_operand(&inst.operands[1]); - let src = format_operand(&inst.operands[2]); - if let Some(res) = res_name { - writeln!( - out, - " {} = bpf_probe_read_user_str((void *){}, {}, (void *){});", - res, dst, size, src - ) - .map_err(err)?; - } else { - writeln!( - out, - " (void)bpf_probe_read_user_str((void *){}, {}, (void *){});", - dst, size, src - ) - .map_err(err)?; - } - } - 204 => { - // bpf_probe_read_kernel_str(void *dst, u32 size, const void *unsafe_ptr) - if inst.operands.len() != 3 { - return Err( - "internal error: helper 204 expects 3 operands".to_string() - ); - } - let dst = format_operand(&inst.operands[0]); - let size = format_operand(&inst.operands[1]); - let src = format_operand(&inst.operands[2]); - if let Some(res) = res_name { - writeln!( - out, - " {} = bpf_probe_read_kernel_str((void *){}, {}, (void *){});", - res, dst, size, src - ) - .map_err(err)?; - } else { - writeln!( - out, - " (void)bpf_probe_read_kernel_str((void *){}, {}, (void *){});", - dst, size, src - ) - .map_err(err)?; - } - } - other => { - return Err(format!( - "unsupported helper id {} in C emitter (needs explicit lowering)", - other - )); - } - } - } - - 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(&{}, (void *){}, 0);", - map_name, ptr - ) - .map_err(err)?; - } - } - Opcode::CopyCtxToMem { offset, size } => { - let dest = match &inst.operands[0] { - Operand::Var(v) => format!("v{}", v.0), - _ => return Err("CopyCtxToMem expects dest var".into()), - }; - let res = res_name.unwrap(); - writeln!( - out, - " {} = bpf_probe_read_kernel((void *)&{}, {}, (void *)((__u8 *)ctx + {}));", - res, dest, size, offset - ).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, "}}").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 deleted file mode 100644 index 631f9ce..0000000 --- a/src/emit/ebpf_c/write.rs +++ /dev/null @@ -1,34 +0,0 @@ -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 deleted file mode 100644 index e5a9591..0000000 --- a/src/emit/ebpf_c/xdp.rs +++ /dev/null @@ -1,40 +0,0 @@ -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/mod.rs b/src/emit/mod.rs index 44bc191..d90fdb3 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -1,2 +1 @@ -pub mod ebpf_c; -pub mod util; \ No newline at end of file +pub mod ebpf; diff --git a/src/emit/util.rs b/src/emit/util.rs deleted file mode 100644 index 0167745..0000000 --- a/src/emit/util.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub fn fmt_err(e: std::fmt::Error) -> String { - e.to_string() -} diff --git a/src/ir/mod.rs b/src/ir/mod.rs index f52e2d1..c8b2c15 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -4,22 +4,11 @@ pub mod unit; pub use program::lower_program; pub mod ctx; +pub use instruction::{BinaryOp, Instruction, Opcode, Operand, VarId}; -pub use instruction::{ - Instruction, - VarId, - Opcode, - BinaryOp, - Operand, -}; +pub use program::ProgramIr; -pub use program::{ - ProgramIr, -}; - -pub use unit::{ - UnitIr, -}; +pub use unit::UnitIr; #[derive(Debug, thiserror::Error)] pub enum LoweringError { diff --git a/src/ir/program.rs b/src/ir/program.rs index 03359b5..9b345df 100644 --- a/src/ir/program.rs +++ b/src/ir/program.rs @@ -1,4 +1,4 @@ -use super::{UnitIr, LoweringError}; +use super::{LoweringError, UnitIr}; use crate::ast::{MapDecl, Program}; #[derive(Debug, Clone)] @@ -9,11 +9,11 @@ pub struct ProgramIr { 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); diff --git a/src/ir/unit.rs b/src/ir/unit.rs index 2744179..fd7fd87 100644 --- a/src/ir/unit.rs +++ b/src/ir/unit.rs @@ -431,7 +431,10 @@ fn lower_statement( block.instructions.push(Instruction { result: ptr, opcode: Opcode::Binary { op: BinaryOp::Add }, - operands: vec![Operand::Var(base_var), Operand::Immediate(offset as i64)], + operands: vec![ + Operand::Var(base_var), + Operand::Immediate(offset as i64), + ], result_type: crate::ast::Type::U64, }); ptr @@ -449,9 +452,7 @@ fn lower_statement( let result = ir.alloc_var(crate::ast::Type::U64); block.instructions.push(Instruction { result, - opcode: Opcode::Store { - size: (size as u8), - }, + opcode: Opcode::Store { size: (size as u8) }, operands: vec![Operand::Var(field_ptr_var), value.clone()], result_type: crate::ast::Type::U64, }); diff --git a/src/kernel_check.rs b/src/kernel_check.rs deleted file mode 100644 index ae5c1f0..0000000 --- a/src/kernel_check.rs +++ /dev/null @@ -1,125 +0,0 @@ -use std::process::Command; -use std::fmt; - -/// Minimum kernel version for CO-RE: 5.2.0 -const MIN_KERNEL_MAJOR: u32 = 5; -const MIN_KERNEL_MINOR: u32 = 2; -const MIN_KERNEL_PATCH: u32 = 0; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct KernelVersion { - pub major: u32, - pub minor: u32, - pub patch: u32, -} - -impl KernelVersion { - pub fn new(major: u32, minor: u32, patch: u32) -> Self { - Self { major, minor, patch } - } - - /// Parse from uname -r output like "5.10.134-18.0.9.lifsea8.x86_64" - pub fn from_uname(release: &str) -> Option { - let mut parts = release.split(|c: char| !c.is_ascii_digit()); - let major = parts.next()?.parse().ok()?; - let minor = parts.next()?.parse().ok()?; - let patch = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0); - Some(Self::new(major, minor, patch)) - } - - pub fn is_core_supported(&self) -> bool { - *self >= Self::new(MIN_KERNEL_MAJOR, MIN_KERNEL_MINOR, MIN_KERNEL_PATCH) - } - - pub fn min_required() -> Self { - Self::new(MIN_KERNEL_MAJOR, MIN_KERNEL_MINOR, MIN_KERNEL_PATCH) - } -} - -impl fmt::Display for KernelVersion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}.{}.{}", self.major, self.minor, self.patch) - } -} - -/// Detect kernel version from `uname -r` -pub fn detect_kernel_version() -> Result { - let output = Command::new("uname") - .arg("-r") - .output() - .map_err(|e| format!("Failed to run `uname -r`: {e}"))?; - - if !output.status.success() { - return Err("`uname -r` returned non-zero".into()); - } - - let release = String::from_utf8_lossy(&output.stdout).trim().to_string(); - - KernelVersion::from_uname(&release) - .ok_or_else(|| format!("Cannot parse kernel version from: '{release}'")) -} - -/// Check BTF availability (CO-RE requires kernel BTF) -pub fn check_btf_available() -> bool { - std::path::Path::new("/sys/kernel/btf/vmlinux").exists() -} - -/// Full CO-RE compatibility check -pub fn check_core_support() -> Result<(), CoreCheckError> { - let version = detect_kernel_version() - .map_err(CoreCheckError::VersionDetectionFailed)?; - - if !version.is_core_supported() { - return Err(CoreCheckError::KernelTooOld { - current: version, - required: KernelVersion::min_required(), - }); - } - - if !check_btf_available() { - return Err(CoreCheckError::BtfMissing); - } - - Ok(()) -} - -#[derive(Debug)] -pub enum CoreCheckError { - VersionDetectionFailed(String), - KernelTooOld { - current: KernelVersion, - required: KernelVersion, - }, - BtfMissing, -} - -impl fmt::Display for CoreCheckError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - CoreCheckError::VersionDetectionFailed(e) => { - write!(f, "Cannot detect kernel version: {e}") - } - CoreCheckError::KernelTooOld { current, required } => { - write!(f, - "Kernel {current} does not support CO-RE.\n\ - Minimum required: Linux {required}\n\ - \n\ - CO-RE requires kernel BTF which is available from Linux 5.2+.\n\ - Upgrade your kernel or use a distribution with BTF-enabled builds." - ) - } - CoreCheckError::BtfMissing => { - write!(f, - "Kernel BTF not found at /sys/kernel/btf/vmlinux.\n\ - \n\ - Your kernel may be too old, or BTF was disabled at compile time.\n\ - To fix:\n\ - - Upgrade to Linux 5.2+ with CONFIG_DEBUG_INFO_BTF=y\n\ - - Or install BTF files via: https://github.com/aquasecurity/btfhub" - ) - } - } - } -} - -impl std::error::Error for CoreCheckError {} \ No newline at end of file diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 75a12ed..54ab565 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -1,5 +1,4 @@ #![allow(unused_assignments)] -#![allow(unused_assignments)] use crate::parser::TokenKind; @@ -132,7 +131,7 @@ impl<'src> Lexer<'src> { "guard" => TokenKind::KeywordGuard, "heap" => TokenKind::KeywordHeap, "event" => TokenKind::KeywordEvent, - "bytes" => TokenKind::KeywordBytes, + "bytes" => TokenKind::KeywordBytes, "u32" => TokenKind::TypeU32, "u64" => TokenKind::TypeU64, "i32" => TokenKind::TypeI32, @@ -431,4 +430,20 @@ impl<'src> Lexer<'src> { _ => Err((loc, "Invalid character".to_string())), } } + + pub fn tokenize(mut self) -> Result, LexError> { + let mut tokens = Vec::new(); + + loop { + let token = self.next_token()?; + let is_eof = token.kind == TokenKind::Eof; + tokens.push(token); + + if is_eof { + break; + } + } + + Ok(tokens) + } } diff --git a/src/main.rs b/src/main.rs index 21b62c5..f93ed1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,18 @@ +mod ast; mod compiler; mod diagnostics; -mod parser; -mod ast; +mod emit; +mod ir; mod lexer; +mod parser; mod sema; -mod ir; -mod emit; -mod build; - +use clap::{Arg, ArgMatches, Command}; use compiler::compile; use std::path::PathBuf; -use clap::{Arg, Command, ArgMatches}; -use kernel_check::check_core_support; -mod kernel_check; fn main() { - if let Err(e) = check_core_support() { - eprintln!("\n❌ CO-RE Check Failed"); - eprintln!("{}", "=".repeat(50)); - eprintln!("{e}"); - eprintln!("{}\n", "=".repeat(50)); - std::process::exit(2); - } - let mut app = Command::new("solnixc") + let mut app = Command::new("solnixc") .version("0.2.0") .about("Experimental eBPF compiler for tracepoint programs") .subcommand( @@ -50,12 +39,11 @@ fn main() { _ => { println!("Solnix Compiler v0.2.0"); app.print_help().unwrap(); - println!(); + println!(); } } } - fn compile_cmd(matches: &ArgMatches) { let input_path = PathBuf::from(matches.get_one::("input").unwrap()); let output_path = PathBuf::from(matches.get_one::("output").unwrap()); diff --git a/src/parser/event.rs b/src/parser/event.rs index 5af1f70..659f1fd 100644 --- a/src/parser/event.rs +++ b/src/parser/event.rs @@ -70,7 +70,8 @@ fn parse_event_type(parser: &mut Parser) -> Result { if parser.r#match(TokenKind::LBracket) { let len_tok = parser.expect(TokenKind::Number)?; - let len = len_tok.int_value + let len = len_tok + .int_value .ok_or((len_tok.loc, "Invalid array length".to_string()))? as u32; @@ -92,9 +93,9 @@ fn parse_event_type(parser: &mut Parser) -> Result { parser.expect(TokenKind::LBracket)?; let len_tok = parser.expect(TokenKind::Number)?; - let len = len_tok.int_value - .ok_or((len_tok.loc, "Invalid bytes length".to_string()))? - as u32; + let len = len_tok + .int_value + .ok_or((len_tok.loc, "Invalid bytes length".to_string()))? as u32; parser.expect(TokenKind::RBracket)?; diff --git a/src/parser/map.rs b/src/parser/map.rs index 8b06ba8..12a9cd3 100644 --- a/src/parser/map.rs +++ b/src/parser/map.rs @@ -95,14 +95,13 @@ pub fn parse_map(parser: &mut Parser) -> Result { } Ok(MapDecl { - name: map_name_tok.lexeme, - map_type, - key_type, - value_type, - max_entries, - loc: map_loc, -}) - + name: map_name_tok.lexeme, + map_type, + key_type, + value_type, + max_entries, + loc: map_loc, + }) } pub fn parse_type(parser: &mut Parser) -> Result { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0633047..554667e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,16 +1,23 @@ -pub mod token; +pub mod event; +pub mod map; pub mod parser; pub mod program; -pub mod map; +pub mod token; pub mod unit; -pub mod event; -pub use token::{Token, TokenKind, SourceLoc}; -pub use parser::{Parser, ParseError}; +pub use parser::{ParseError, Parser}; +pub use token::{SourceLoc, Token, TokenKind}; use crate::ast::Program; +use crate::lexer::Lexer; +#[allow(dead_code)] pub fn parse(src: &str) -> Result { - let mut parser = Parser::new(src)?; + let tokens = Lexer::new(src).tokenize()?; + parse_tokens(tokens) +} + +pub fn parse_tokens(tokens: Vec) -> Result { + let mut parser = Parser::new(tokens)?; program::parse_program(&mut parser) -} \ No newline at end of file +} diff --git a/src/parser/parser.rs b/src/parser/parser.rs index f3d2c7c..d64edfa 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -1,41 +1,43 @@ -#![allow(unused_assignments)] - use super::{Token, TokenKind}; -use crate::lexer::Lexer; -use anyhow::Result; pub type ParseError = (crate::parser::SourceLoc, String); -pub struct Parser<'src> { - _src: &'src str, - lexer: Lexer<'src>, - current: Token, +pub struct Parser { + tokens: Vec, + current: usize, } -impl<'src> Parser<'src> { - pub fn new(src: &'src str) -> Result { - let mut lexer = Lexer::new(src); - let current = lexer.next_token().map_err(|_e| { - (super::SourceLoc::new(0, 0, 0), "Failed to lex first token".to_string()) - })?; +impl Parser { + pub fn new(tokens: Vec) -> Result { + if tokens.is_empty() { + return Err(( + super::SourceLoc::new(0, 0, 0), + "Parser received an empty token stream".to_string(), + )); + } - Ok(Self { - _src: src, - lexer, - current, - }) + if tokens.last().map(|token| token.kind) != Some(TokenKind::Eof) { + return Err(( + tokens + .last() + .map(|token| token.loc) + .unwrap_or_else(|| super::SourceLoc::new(0, 0, 0)), + "Token stream must end with EOF".to_string(), + )); + } + + Ok(Self { tokens, current: 0 }) } pub fn advance(&mut self) -> Result<(), ParseError> { - self.current = self - .lexer - .next_token() - .map_err(|_e| (self.current.loc, "Lexer error".to_string()))?; + if !self.check(TokenKind::Eof) { + self.current += 1; + } Ok(()) } pub fn check(&self, kind: TokenKind) -> bool { - self.current.kind == kind + self.current().kind == kind } pub fn r#match(&mut self, kind: TokenKind) -> bool { @@ -49,35 +51,34 @@ impl<'src> Parser<'src> { pub fn expect(&mut self, kind: TokenKind) -> Result { if self.check(kind) { - let tok = self.current.clone(); + let tok = self.current().clone(); self.advance()?; Ok(tok) } else { Err(( - self.current.loc, - format!("Expected {}, found {}", kind, self.current.kind), + self.current().loc, + format!("Expected {}, found {}", kind, self.current().kind), )) } } pub fn error(&self, message: impl Into) -> ParseError { - (self.current.loc, message.into()) + (self.current().loc, message.into()) } pub fn current(&self) -> &Token { - &self.current + &self.tokens[self.current] } pub fn current_loc(&self) -> crate::parser::SourceLoc { - self.current.loc + self.current().loc } pub fn current_kind(&self) -> TokenKind { - self.current.kind + self.current().kind } pub fn _current_lexeme(&self) -> &str { - &self.current.lexeme + &self.current().lexeme } - } diff --git a/src/parser/program.rs b/src/parser/program.rs index 4c6c334..717c99b 100644 --- a/src/parser/program.rs +++ b/src/parser/program.rs @@ -26,5 +26,9 @@ pub fn parse_program(parser: &mut Parser) -> Result { } } - Ok(Program { maps, units, events }) + Ok(Program { + maps, + units, + events, + }) } diff --git a/src/parser/unit.rs b/src/parser/unit.rs index ebdabe8..51bf19a 100644 --- a/src/parser/unit.rs +++ b/src/parser/unit.rs @@ -56,7 +56,7 @@ pub fn parse_unit(parser: &mut Parser) -> Result { sections, kind, license, - events: Vec::new(), + events: Vec::new(), body, }) }