Skip to main content

sbpf_assembler/parser/
mod.rs

1pub mod common;
2mod default;
3mod directive;
4mod llvm;
5
6use {
7    crate::{
8        SbpfArch,
9        ast::{AST, OptimizationConfig, build_program},
10        astnode::{ASTNode, Label},
11        dynsym::{DynamicSymbolMap, RelDynMap},
12        errors::CompileError,
13        section::{CodeSection, DataSection, DebugSection},
14    },
15    directive::{process_directive_statement, process_rodata_directive},
16    pest::{
17        Parser,
18        error::{ErrorVariant, InputLocation},
19        iterators::Pair,
20    },
21    pest_derive::Parser,
22    sbpf_common::{inst_param::Number, instruction::Instruction},
23    std::collections::HashMap,
24};
25
26#[derive(Parser)]
27#[grammar = "sbpf.pest"]
28pub struct SbpfParser;
29
30/// Which section a label belongs to.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub(crate) enum Section {
33    Text,
34    Rodata,
35}
36
37/// Context containing all mutable state during parsing
38pub(crate) struct ParseContext<'a> {
39    pub arch: SbpfArch,
40    pub ast: &'a mut AST,
41    pub const_map: &'a mut HashMap<String, Number>,
42    pub label_spans: &'a mut HashMap<String, std::ops::Range<usize>>,
43    pub label_offset_map: &'a mut HashMap<String, (Number, Section)>,
44    pub errors: Vec<CompileError>,
45    pub rodata_phase: bool,
46    pub text_offset: u64,
47    pub rodata_offset: u64,
48    pub missing_text_directive: bool,
49    /// A rodata label on its own line, waiting for the next data directive.
50    pub pending_rodata_label: Option<(String, std::ops::Range<usize>)>,
51}
52
53/// BPF_X flag: Converts immediate variant opcodes to register variant opcodes
54const BPF_X: u8 = 0x08;
55
56/// Token types used in the AST
57#[derive(Debug, Clone)]
58pub enum Token {
59    Directive(String, std::ops::Range<usize>),
60    Identifier(String, std::ops::Range<usize>),
61    ImmediateValue(Number, std::ops::Range<usize>),
62    StringLiteral(String, std::ops::Range<usize>),
63    VectorLiteral(Vec<Number>, std::ops::Range<usize>),
64}
65
66pub struct ProgramLayout {
67    // TODO: parse result is basically 1. static part 2. dynamic part of the program
68    pub code_section: CodeSection,
69
70    pub data_section: DataSection,
71
72    pub dynamic_symbols: DynamicSymbolMap,
73
74    pub relocation_data: RelDynMap,
75
76    // TODO: this can be removed and dynamic-ness should just be
77    // determined by if there's any dynamic symbol
78    pub prog_is_static: bool,
79
80    pub arch: SbpfArch,
81
82    // Debug sections we came across while byteparsing
83    pub debug_sections: Vec<DebugSection>,
84}
85
86pub fn parse(source: &str, arch: SbpfArch) -> Result<ProgramLayout, Vec<CompileError>> {
87    parse_with_optimization(source, arch, OptimizationConfig::default())
88}
89
90pub fn parse_with_optimization(
91    source: &str,
92    arch: SbpfArch,
93    optimization: OptimizationConfig,
94) -> Result<ProgramLayout, Vec<CompileError>> {
95    let pairs = SbpfParser::parse(Rule::program, source).map_err(|e| {
96        // Extract the actual byte position from the pest error so the source
97        // map can resolve it back to the original file/line.
98        let span = match e.location {
99            InputLocation::Pos(pos) => pos..pos + 1,
100            InputLocation::Span((start, end)) => start..end,
101        };
102
103        // Build a clean message without pest's embedded source context,
104        // which would show expanded-source line numbers.
105        let message = match &e.variant {
106            ErrorVariant::ParsingError {
107                positives,
108                negatives,
109            } => {
110                let pos: Vec<String> = positives.iter().filter_map(rule_display_name).collect();
111                let neg: Vec<String> = negatives.iter().filter_map(rule_display_name).collect();
112                let mut parts = Vec::new();
113                if !pos.is_empty() {
114                    parts.push(format!("expected {}", pos.join(", ")));
115                }
116                if !neg.is_empty() {
117                    parts.push(format!("unexpected {}", neg.join(", ")));
118                }
119                if parts.is_empty() {
120                    "Parse error".to_string()
121                } else {
122                    parts.join("; ")
123                }
124            }
125            ErrorVariant::CustomError { message } => message.clone(),
126        };
127
128        vec![CompileError::ParseError {
129            error: message,
130            span,
131            custom_label: None,
132        }]
133    })?;
134
135    let mut ast = AST::new();
136    let mut const_map = HashMap::<String, Number>::new();
137    let mut label_spans = HashMap::<String, std::ops::Range<usize>>::new();
138
139    // Pass 1: collect all label offsets so forward references work in expressions.
140    let pairs_clone = pairs.clone();
141    let mut label_offset_map = collect_label_offsets(pairs_clone);
142
143    // Pass 2: full processing with label_offset_map already populated.
144    let (text_offset, rodata_offset, errors) = {
145        let mut ctx = ParseContext {
146            arch,
147            ast: &mut ast,
148            const_map: &mut const_map,
149            label_spans: &mut label_spans,
150            label_offset_map: &mut label_offset_map,
151            errors: Vec::new(),
152            rodata_phase: false,
153            text_offset: 0,
154            rodata_offset: 0,
155            missing_text_directive: false,
156            pending_rodata_label: None,
157        };
158
159        for pair in pairs {
160            match pair.as_rule() {
161                Rule::program_default | Rule::program_llvm => {
162                    for statement in pair.into_inner() {
163                        if statement.as_rule() == Rule::EOI {
164                            continue;
165                        }
166                        process_statement(statement, &mut ctx);
167                    }
168                }
169                _ => {}
170            }
171        }
172
173        (ctx.text_offset, ctx.rodata_offset, ctx.errors)
174    };
175
176    if !errors.is_empty() {
177        return Err(errors);
178    }
179
180    ast.set_text_size(text_offset);
181    ast.set_rodata_size(rodata_offset);
182
183    build_program(ast, arch, optimization)
184}
185
186/// Pass 1: lightweight scan of the parse tree to collect all label offsets.
187/// This enables forward references in operand expressions (e.g. rodata labels
188/// referenced from the text section that appears earlier in the source).
189fn collect_label_offsets(
190    pairs: pest::iterators::Pairs<Rule>,
191) -> HashMap<String, (Number, Section)> {
192    let mut map = HashMap::new();
193    let mut rodata_phase = false;
194    let mut text_offset: u64 = 0;
195    let mut rodata_offset: u64 = 0;
196
197    for pair in pairs {
198        match pair.as_rule() {
199            Rule::program_default | Rule::program_llvm => {
200                for statement in pair.into_inner() {
201                    if statement.as_rule() == Rule::EOI {
202                        continue;
203                    }
204                    scan_statement_for_labels(
205                        statement,
206                        &mut map,
207                        &mut rodata_phase,
208                        &mut text_offset,
209                        &mut rodata_offset,
210                    );
211                }
212            }
213            _ => {}
214        }
215    }
216    map
217}
218
219/// Scan a single statement to find labels and track offsets.
220fn scan_statement_for_labels(
221    pair: Pair<Rule>,
222    map: &mut HashMap<String, (Number, Section)>,
223    rodata_phase: &mut bool,
224    text_offset: &mut u64,
225    rodata_offset: &mut u64,
226) {
227    for inner in pair.into_inner() {
228        match inner.as_rule() {
229            Rule::label_default | Rule::label_llvm => {
230                scan_label(inner, map, rodata_phase, text_offset, rodata_offset);
231            }
232            Rule::directive => {
233                // Track section switches and standalone data directive sizes
234                for dir_inner in inner.into_inner() {
235                    let dir_inner_clone = dir_inner.clone();
236                    for dir_item in dir_inner.into_inner() {
237                        if dir_item.as_rule() == Rule::directive_section {
238                            let section_name = dir_item.as_str().trim_start_matches('.');
239                            match section_name {
240                                "text" => *rodata_phase = false,
241                                "rodata" => *rodata_phase = true,
242                                _ => {}
243                            }
244                        } else if *rodata_phase {
245                            // Standalone data directive in rodata — account for its size
246                            match dir_item.as_rule() {
247                                Rule::directive_ascii
248                                | Rule::directive_byte
249                                | Rule::directive_short
250                                | Rule::directive_word
251                                | Rule::directive_int
252                                | Rule::directive_long
253                                | Rule::directive_quad => {
254                                    *rodata_offset += rodata_directive_size(&dir_inner_clone);
255                                }
256                                _ => {}
257                            }
258                        }
259                    }
260                }
261            }
262            Rule::instr_default | Rule::instr_llvm if !*rodata_phase => {
263                let size = instr_size(&inner);
264                *text_offset += size;
265            }
266            _ => {}
267        }
268    }
269}
270
271/// Scan a label node: record its offset and account for any attached
272/// instruction/directive size.
273fn scan_label(
274    pair: Pair<Rule>,
275    map: &mut HashMap<String, (Number, Section)>,
276    rodata_phase: &mut bool,
277    text_offset: &mut u64,
278    rodata_offset: &mut u64,
279) {
280    let mut label_name = None;
281
282    for item in pair.into_inner() {
283        match item.as_rule() {
284            Rule::identifier | Rule::numeric_label => {
285                label_name = Some(item.as_str().to_string());
286            }
287            Rule::directive_inner => {
288                // Rodata directive attached to label — compute data size
289                if *rodata_phase {
290                    if let Some(ref name) = label_name {
291                        map.insert(
292                            name.clone(),
293                            (Number::Int(*rodata_offset as i64), Section::Rodata),
294                        );
295                    }
296                    let size = rodata_directive_size(&item);
297                    *rodata_offset += size;
298                }
299                return;
300            }
301            Rule::instr_default | Rule::instr_llvm => {
302                if !*rodata_phase {
303                    if let Some(ref name) = label_name {
304                        map.insert(
305                            name.clone(),
306                            (Number::Int(*text_offset as i64), Section::Text),
307                        );
308                    }
309                    let size = instr_size(&item);
310                    *text_offset += size;
311                }
312                return;
313            }
314            _ => {}
315        }
316    }
317
318    // Bare label (no directive or instruction attached)
319    if let Some(name) = label_name {
320        if *rodata_phase {
321            map.insert(name, (Number::Int(*rodata_offset as i64), Section::Rodata));
322        } else {
323            map.insert(name, (Number::Int(*text_offset as i64), Section::Text));
324        }
325    }
326}
327
328/// Determine instruction size from the parse tree (lddw = 16 bytes, all others = 8).
329fn instr_size(pair: &Pair<Rule>) -> u64 {
330    for inner in pair.clone().into_inner() {
331        match inner.as_rule() {
332            Rule::instr_lddw | Rule::instr_llvm_lddw => return 16,
333            _ => {}
334        }
335    }
336    8
337}
338
339/// Determine the byte size of a rodata directive from the parse tree.
340fn rodata_directive_size(pair: &Pair<Rule>) -> u64 {
341    for inner in pair.clone().into_inner() {
342        match inner.as_rule() {
343            Rule::directive_ascii => {
344                for ascii_inner in inner.into_inner() {
345                    if ascii_inner.as_rule() == Rule::string_literal {
346                        for content in ascii_inner.into_inner() {
347                            if content.as_rule() == Rule::string_content {
348                                return content.as_str().len() as u64;
349                            }
350                        }
351                    }
352                }
353            }
354            Rule::directive_byte => {
355                return inner
356                    .into_inner()
357                    .filter(|p| p.as_rule() == Rule::number)
358                    .count() as u64;
359            }
360            Rule::directive_short | Rule::directive_word => {
361                return inner
362                    .into_inner()
363                    .filter(|p| p.as_rule() == Rule::number)
364                    .count() as u64
365                    * 2;
366            }
367            Rule::directive_int | Rule::directive_long => {
368                return inner
369                    .into_inner()
370                    .filter(|p| p.as_rule() == Rule::number)
371                    .count() as u64
372                    * 4;
373            }
374            Rule::directive_quad => {
375                return inner
376                    .into_inner()
377                    .filter(|p| p.as_rule() == Rule::number)
378                    .count() as u64
379                    * 8;
380            }
381            _ => {}
382        }
383    }
384    0
385}
386
387/// Map internal pest rule names to human-readable descriptions for error messages.
388fn rule_display_name(rule: &Rule) -> Option<String> {
389    let name = match rule {
390        // Top-level
391        Rule::program_default | Rule::program_llvm => return None,
392        Rule::statement_default | Rule::statement_llvm => "statement",
393        Rule::label_default | Rule::label_llvm => "label",
394
395        // Directives
396        Rule::directive | Rule::directive_inner => "directive",
397        Rule::directive_globl => ".globl",
398        Rule::directive_extern => ".extern",
399        Rule::directive_equ => ".equ",
400        Rule::directive_section => "section (.text, .rodata)",
401        Rule::directive_ascii => ".ascii",
402        Rule::directive_byte => ".byte",
403        Rule::directive_short => ".short",
404        Rule::directive_word => ".word",
405        Rule::directive_int => ".int",
406        Rule::directive_long => ".long",
407        Rule::directive_quad => ".quad",
408
409        // Instructions
410        Rule::instr_default | Rule::instr_llvm => "instruction",
411        Rule::instr_lddw | Rule::instr_llvm_lddw => "lddw",
412        Rule::instr_call => "call",
413        Rule::instr_callx => "callx",
414        Rule::instr_exit => "exit",
415
416        // Operands
417        Rule::register => "register",
418        Rule::operand => "operand",
419        Rule::number => "number",
420        Rule::symbol => "symbol",
421        Rule::identifier => "identifier",
422        Rule::expression => "expression",
423        Rule::string_literal => "string literal",
424
425        // Memory
426        Rule::memory_ref | Rule::llvm_memory_ref => "memory reference",
427        Rule::jump_target => "jump target",
428
429        // Whitespace / structure
430        Rule::EOI => "end of input",
431        _ => return Some(format!("{:?}", rule)),
432    };
433    Some(name.to_string())
434}
435
436fn process_statement(pair: Pair<Rule>, ctx: &mut ParseContext) {
437    for inner in pair.into_inner() {
438        match inner.as_rule() {
439            Rule::label_default | Rule::label_llvm => {
440                process_label(inner, ctx);
441            }
442            Rule::directive => {
443                process_directive_statement(inner, ctx);
444            }
445            Rule::instr_default | Rule::instr_llvm => {
446                let span = inner.as_span();
447                let span_range = span.start()..span.end();
448                let is_llvm = inner.as_rule() == Rule::instr_llvm;
449
450                match process_instruction(
451                    inner,
452                    ctx.const_map,
453                    ctx.label_offset_map,
454                    ctx.arch,
455                    is_llvm,
456                ) {
457                    Ok(instruction) => {
458                        if !ctx.rodata_phase {
459                            let size = instruction.get_size();
460                            ctx.ast.nodes.push(ASTNode::Instruction {
461                                instruction,
462                                offset: ctx.text_offset,
463                            });
464                            ctx.text_offset += size;
465                        }
466                    }
467                    Err(e) => ctx.errors.push(e),
468                }
469
470                if ctx.rodata_phase && !ctx.missing_text_directive {
471                    ctx.missing_text_directive = true;
472                    ctx.errors.push(CompileError::MissingTextDirective {
473                        span: span_range,
474                        custom_label: None,
475                    });
476                }
477            }
478            _ => {}
479        }
480    }
481}
482
483fn process_label(pair: Pair<Rule>, ctx: &mut ParseContext) {
484    let is_llvm = pair.as_rule() == Rule::label_llvm;
485    let mut label_opt = None;
486    let mut directive_opt = None;
487    let mut instruction_opt = None;
488
489    for item in pair.into_inner() {
490        match item.as_rule() {
491            Rule::identifier | Rule::numeric_label => match extract_label_from_pair(item) {
492                Ok(label) => label_opt = Some(label),
493                Err(e) => ctx.errors.push(e),
494            },
495            Rule::directive_inner => {
496                directive_opt = Some(item);
497            }
498            Rule::instr_default | Rule::instr_llvm => {
499                instruction_opt = Some(item);
500            }
501            _ => {}
502        }
503    }
504
505    if let Some((label_name, label_span)) = label_opt {
506        // Check for duplicate labels
507        if let Some(original_span) = ctx.label_spans.get(&label_name) {
508            ctx.errors.push(CompileError::DuplicateLabel {
509                label: label_name,
510                span: label_span,
511                original_span: original_span.clone(),
512                custom_label: Some("Label already defined".to_string()),
513            });
514            return;
515        }
516        ctx.label_spans
517            .insert(label_name.clone(), label_span.clone());
518
519        if ctx.rodata_phase {
520            // Record label offset for expression evaluation
521            ctx.label_offset_map.insert(
522                label_name.clone(),
523                (Number::Int(ctx.rodata_offset as i64), Section::Rodata),
524            );
525
526            // Handle rodata label with directive
527            if let Some(dir_pair) = directive_opt {
528                match process_rodata_directive(label_name.clone(), label_span.clone(), dir_pair) {
529                    Ok(rodata) => {
530                        let size = rodata.get_size();
531                        ctx.ast.rodata_nodes.push(ASTNode::ROData {
532                            rodata,
533                            offset: ctx.rodata_offset,
534                        });
535                        ctx.rodata_offset += size;
536                    }
537                    Err(e) => ctx.errors.push(e),
538                }
539            } else if let Some(inst_pair) = instruction_opt {
540                if let Err(e) = process_instruction(
541                    inst_pair,
542                    ctx.const_map,
543                    ctx.label_offset_map,
544                    ctx.arch,
545                    is_llvm,
546                ) {
547                    ctx.errors.push(e);
548                }
549                if !ctx.missing_text_directive {
550                    ctx.missing_text_directive = true;
551                    ctx.errors.push(CompileError::MissingTextDirective {
552                        span: label_span,
553                        custom_label: None,
554                    });
555                }
556            } else {
557                // Bare rodata label (no directive on same line) — store it
558                // so the next data directive can pick it up.
559                ctx.pending_rodata_label = Some((label_name, label_span));
560            }
561        } else {
562            // Record label offset for expression evaluation
563            ctx.label_offset_map.insert(
564                label_name.clone(),
565                (Number::Int(ctx.text_offset as i64), Section::Text),
566            );
567
568            ctx.ast.nodes.push(ASTNode::Label {
569                label: Label {
570                    name: label_name,
571                    span: label_span,
572                },
573                offset: ctx.text_offset,
574            });
575
576            if let Some(inst_pair) = instruction_opt {
577                match process_instruction(
578                    inst_pair,
579                    ctx.const_map,
580                    ctx.label_offset_map,
581                    ctx.arch,
582                    is_llvm,
583                ) {
584                    Ok(instruction) => {
585                        let size = instruction.get_size();
586                        ctx.ast.nodes.push(ASTNode::Instruction {
587                            instruction,
588                            offset: ctx.text_offset,
589                        });
590                        ctx.text_offset += size;
591                    }
592                    Err(e) => ctx.errors.push(e),
593                }
594            }
595        }
596    }
597}
598
599fn process_instruction(
600    pair: Pair<Rule>,
601    const_map: &HashMap<String, Number>,
602    label_offset_map: &HashMap<String, (Number, Section)>,
603    arch: SbpfArch,
604    is_llvm: bool,
605) -> Result<Instruction, CompileError> {
606    if is_llvm {
607        llvm::process_instruction(pair, const_map, label_offset_map, arch)
608    } else {
609        default::process_instruction(pair, const_map, label_offset_map, arch)
610    }
611}
612
613fn extract_label_from_pair(
614    pair: Pair<Rule>,
615) -> Result<(String, std::ops::Range<usize>), CompileError> {
616    let span = pair.as_span();
617    Ok((pair.as_str().to_string(), span.start()..span.end()))
618}