1use {
2 crate::{
3 CompileError, SbpfArch,
4 astnode::{ASTNode, ROData},
5 dynsym::{DynamicSymbolMap, RelDynMap, RelocationType},
6 header::ProgramHeader,
7 optimizer,
8 parser::{ProgramLayout, Token},
9 section::{CodeSection, DataSection},
10 },
11 either::Either,
12 sbpf_common::{
13 inst_param::{Number, Register},
14 instruction::Instruction,
15 opcode::Opcode,
16 },
17 std::{
18 collections::{HashMap, HashSet},
19 path::PathBuf,
20 },
21 syscall_map::murmur3_32,
22};
23
24type LabelOffsetMap = HashMap<String, u64>;
25type NumericLabel = (String, u64, usize);
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct RodataRelocation {
29 pub offset: u64,
31 pub target: String,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum OptimizationConfig {
37 Disabled,
38 Enabled { cfg_dump_dir: Option<PathBuf> },
39}
40
41impl Default for OptimizationConfig {
42 fn default() -> Self {
43 Self::disabled()
44 }
45}
46
47impl OptimizationConfig {
48 pub fn disabled() -> Self {
49 Self::Disabled
50 }
51
52 pub fn enabled() -> Self {
53 Self::Enabled { cfg_dump_dir: None }
54 }
55
56 pub fn with_cfg_dump_dir(self, path: impl Into<PathBuf>) -> Self {
57 match self {
58 Self::Enabled { .. } => Self::Enabled {
59 cfg_dump_dir: Some(path.into()),
60 },
61 Self::Disabled => Self::Disabled,
62 }
63 }
64}
65
66#[derive(Default, Debug)]
67pub struct AST {
68 pub nodes: Vec<ASTNode>,
69 pub rodata_nodes: Vec<ASTNode>,
70
71 function_entries: HashSet<String>,
72 text_size: u64,
73 rodata_size: u64,
74 rodata_relocations: Vec<RodataRelocation>,
75}
76
77impl AST {
78 pub fn new() -> Self {
79 Self::default()
80 }
81
82 pub fn add_function_entry(&mut self, name: String) {
83 self.function_entries.insert(name);
84 }
85
86 pub fn add_rodata_relocation(&mut self, offset: u64, target: String) {
87 self.rodata_relocations
88 .push(RodataRelocation { offset, target });
89 }
90
91 pub(crate) fn function_entries(&self) -> &HashSet<String> {
92 &self.function_entries
93 }
94
95 pub(crate) fn rodata_relocations(&self) -> &[RodataRelocation] {
96 &self.rodata_relocations
97 }
98
99 pub fn set_text_size(&mut self, text_size: u64) {
101 self.text_size = text_size;
102 }
103
104 pub fn set_rodata_size(&mut self, rodata_size: u64) {
106 self.rodata_size = rodata_size;
107 }
108
109 pub fn get_instruction_at_offset(&mut self, offset: u64) -> Option<&mut Instruction> {
111 self.nodes
112 .iter_mut()
113 .find(|node| match node {
114 ASTNode::Instruction {
115 instruction: _,
116 offset: inst_offset,
117 ..
118 } => offset == *inst_offset,
119 _ => false,
120 })
121 .map(|node| match node {
122 ASTNode::Instruction { instruction, .. } => instruction,
123 _ => panic!("Expected Instruction node"),
124 })
125 }
126
127 pub fn get_rodata_at_offset(&self, offset: u64) -> Option<&ROData> {
129 self.rodata_nodes
130 .iter()
131 .find(|node| match node {
132 ASTNode::ROData {
133 rodata: _,
134 offset: rodata_offset,
135 ..
136 } => offset == *rodata_offset,
137 _ => false,
138 })
139 .map(|node| match node {
140 ASTNode::ROData { rodata, .. } => rodata,
141 _ => panic!("Expected ROData node"),
142 })
143 }
144
145 pub(crate) fn resolve_numeric_label(
147 label_ref: &str,
148 current_idx: usize,
149 numeric_labels: &[NumericLabel],
150 ) -> Option<u64> {
151 if let Some(direction) = label_ref.chars().last()
152 && (direction == 'f' || direction == 'b')
153 {
154 let label_num = &label_ref[..label_ref.len() - 1];
155
156 if direction == 'f' {
157 for (name, offset, node_idx) in numeric_labels {
159 if name == label_num && *node_idx > current_idx {
160 return Some(*offset);
161 }
162 }
163 } else {
164 for (name, offset, node_idx) in numeric_labels.iter().rev() {
166 if name == label_num && *node_idx < current_idx {
167 return Some(*offset);
168 }
169 }
170 }
171 }
172 None
173 }
174}
175
176pub fn build_program(
177 mut ast: AST,
178 arch: SbpfArch,
179 optimization: OptimizationConfig,
180) -> Result<ProgramLayout, Vec<CompileError>> {
181 let optimization = run_optimizations(&mut ast, &optimization);
182 let mut errors = optimization.errors;
183
184 let (label_offset_map, numeric_labels) = label_offset_map(&ast);
185 let program_is_static = arch.is_v3()
186 || (ast.rodata_relocations.is_empty()
187 && !ast.nodes.iter().any(|node| {
188 matches!(node, ASTNode::Instruction { instruction: inst, .. }
189 if inst.is_syscall()
190 || (inst.opcode == Opcode::Lddw && matches!(&inst.imm, Some(Either::Left(_)))))
191 }));
192
193 let label_resolution = resolve_label_references(
194 &mut ast,
195 arch,
196 program_is_static,
197 &label_offset_map,
198 &numeric_labels,
199 );
200 errors.extend(label_resolution.errors);
201
202 optimizer::remove_temp_control_flow_target_labels(
203 &mut ast.nodes,
204 &optimization.labels_to_remove,
205 );
206
207 if !errors.is_empty() {
208 Err(errors)
209 } else {
210 Ok(ProgramLayout {
211 code_section: CodeSection::new(std::mem::take(&mut ast.nodes), ast.text_size)
212 .with_arch(arch),
213 data_section: DataSection::new(std::mem::take(&mut ast.rodata_nodes), ast.rodata_size),
214 dynamic_symbols: label_resolution.dynamic_symbols,
215 relocation_data: label_resolution.relocations,
216 prog_is_static: program_is_static,
217 arch,
218 debug_sections: Vec::default(),
219 })
220 }
221}
222
223#[derive(Default)]
224struct OptimizationOutcome {
225 labels_to_remove: HashSet<String>,
226 errors: Vec<CompileError>,
227}
228
229fn run_optimizations(ast: &mut AST, config: &OptimizationConfig) -> OptimizationOutcome {
230 let OptimizationConfig::Enabled { cfg_dump_dir } = config else {
231 return OptimizationOutcome::default();
232 };
233
234 let canonicalized_targets = optimizer::canonicalize_control_flow_targets(&mut ast.nodes);
238 let labels_to_remove = canonicalized_targets.labels_to_remove;
239 let mut errors = Vec::new();
240
241 if canonicalized_targets.errors.is_empty() {
242 if let Some(dump_dir) = cfg_dump_dir.as_deref() {
243 let mut dump_errors = Vec::new();
244 if let Err(error) = std::fs::create_dir_all(dump_dir) {
245 dump_errors.push((dump_dir.to_path_buf(), error));
246 optimizer::eliminate_unreachable_functions(ast);
247 } else {
248 optimizer::eliminate_unreachable_functions_with_observer(ast, |stage, cfg| {
249 let path = dump_dir.join(stage.file_name());
250 if let Err(error) = std::fs::write(&path, sbpf_analyze::dump_cfg(cfg)) {
251 dump_errors.push((path, error));
252 }
253 });
254 }
255 for (path, error) in dump_errors {
256 errors.push(CompileError::BytecodeError {
257 error: format!("failed to write CFG dump '{}': {error}", path.display()),
258 span: 0..0,
259 custom_label: None,
260 });
261 }
262 } else {
263 optimizer::eliminate_unreachable_functions(ast);
264 }
265 }
266
267 OptimizationOutcome {
268 labels_to_remove,
269 errors,
270 }
271}
272
273#[derive(Default)]
274struct LabelResolution {
275 dynamic_symbols: DynamicSymbolMap,
276 relocations: RelDynMap,
277 errors: Vec<CompileError>,
278}
279
280fn resolve_label_references(
281 ast: &mut AST,
282 arch: SbpfArch,
283 program_is_static: bool,
284 label_offset_map: &LabelOffsetMap,
285 numeric_labels: &[NumericLabel],
286) -> LabelResolution {
287 let mut relocations = RelDynMap::new();
288 let mut dynamic_symbols = DynamicSymbolMap::new();
289 let mut errors = Vec::new();
290
291 for node in ast.nodes.iter_mut() {
293 if let ASTNode::Instruction {
294 instruction: inst,
295 offset,
296 } = node
297 && inst.is_syscall()
298 && let Some(Either::Left(syscall_name)) = &inst.imm
299 {
300 let syscall_name = syscall_name.clone();
301 if arch.is_v3() {
302 inst.src = Some(Register { n: 0 });
304 inst.imm = Some(Either::Right(Number::Int(murmur3_32(&syscall_name) as i64)));
305 } else {
306 inst.src = Some(Register { n: 1 });
308 inst.imm = Some(Either::Right(Number::Int(-1)));
309
310 relocations.add_rel_dyn(*offset, RelocationType::RSbfSyscall, syscall_name.clone());
312 dynamic_symbols.add_call_target(syscall_name.clone(), *offset);
313 }
314 }
315 }
316
317 for (idx, node) in ast.nodes.iter_mut().enumerate() {
318 if let ASTNode::Instruction {
319 instruction: inst,
320 offset,
321 ..
322 } = node
323 {
324 if inst.is_jump()
326 && let Some(Either::Left(label)) = &inst.off
327 {
328 let target_offset = if let Some(offset) = label_offset_map.get(label) {
329 Some(*offset)
330 } else {
331 AST::resolve_numeric_label(label, idx, numeric_labels)
333 };
334
335 if let Some(target_offset) = target_offset {
336 let rel_offset = (target_offset as i64 - *offset as i64) / 8 - 1;
337 inst.off = Some(Either::Right(rel_offset as i16));
338 }
339 } else if inst.opcode == Opcode::Call
340 && let Some(Either::Left(label)) = &inst.imm
341 && let Some(target_offset) = label_offset_map.get(label)
342 {
343 let rel_offset = (*target_offset as i64 - *offset as i64) / 8 - 1;
344 inst.src = Some(Register { n: 1 });
345 inst.imm = Some(Either::Right(Number::Int(rel_offset)));
346 }
347
348 if inst.opcode == Opcode::Lddw
349 && let Some(Either::Left(name)) = &inst.imm
350 {
351 let label = name.clone();
352 if !arch.is_v3() {
354 relocations.add_rel_dyn(*offset, RelocationType::RSbf64Relative, label.clone());
355 }
356
357 if let Some(target_offset) = label_offset_map.get(&label) {
358 let abs_offset = if arch.is_v3() {
359 if *target_offset >= ast.text_size {
360 (ProgramHeader::V3_RODATA_VADDR + *target_offset - ast.text_size) as i64
361 } else {
362 (ProgramHeader::V3_BYTECODE_VADDR + *target_offset) as i64
363 }
364 } else {
365 let ph_count = if program_is_static { 1 } else { 3 };
366 let ph_offset = 64 + (ph_count as u64 * 56) as i64;
367 *target_offset as i64 + ph_offset
368 };
369 inst.imm = Some(Either::Right(Number::Addr(abs_offset)));
371 }
372 }
373
374 if let Some(Either::Left(label)) = &inst.off {
376 errors.push(CompileError::UndefinedLabel {
377 label: label.clone(),
378 span: inst.span.clone(),
379 custom_label: None,
380 });
381 }
382 if let Some(Either::Left(label)) = &inst.imm {
383 errors.push(CompileError::UndefinedLabel {
384 label: label.clone(),
385 span: inst.span.clone(),
386 custom_label: None,
387 });
388 }
389 }
390 }
391
392 let entry_label = ast.nodes.iter().find_map(|node| {
394 if let ASTNode::GlobalDecl { global_decl } = node {
395 Some(global_decl.entry_label.clone())
396 } else {
397 None
398 }
399 });
400 if let Some(entry_label) = entry_label
401 && let Some(offset) = label_offset_map.get(&entry_label)
402 {
403 dynamic_symbols.add_entry_point(entry_label, *offset);
404 }
405
406 for relocation in &ast.rodata_relocations {
408 let Some(&target_offset) = label_offset_map.get(&relocation.target) else {
409 errors.push(CompileError::UndefinedLabel {
410 label: relocation.target.clone(),
411 span: 0..0,
412 custom_label: None,
413 });
414 continue;
415 };
416 let value = if arch.is_v3() {
417 if target_offset >= ast.text_size {
418 ProgramHeader::V3_RODATA_VADDR + target_offset - ast.text_size
419 } else {
420 ProgramHeader::V3_BYTECODE_VADDR + target_offset
421 }
422 } else {
423 let ph_count = if program_is_static { 1 } else { 3 };
424 (64 + ph_count * 56 + target_offset) << 32
425 };
426
427 let mut written = false;
428 for node in &mut ast.rodata_nodes {
429 let ASTNode::ROData { rodata, offset } = node else {
430 continue;
431 };
432 let is_quad = match rodata.args.first() {
433 Some(Token::Directive(directive, _)) => directive == "quad",
434 _ => continue,
435 };
436 let Some(Token::VectorLiteral(bytes, _)) = rodata.args.get_mut(1) else {
437 continue;
438 };
439 let Some(relocation_offset_in_node) = relocation.offset.checked_sub(*offset) else {
440 continue;
441 };
442
443 if is_quad {
445 if relocation_offset_in_node % 8 == 0
447 && let Some(relocation_value) =
448 bytes.get_mut((relocation_offset_in_node / 8) as usize)
449 {
450 *relocation_value = Number::Int(value as i64);
451 written = true;
452 break;
453 }
454 } else {
455 if let Some(relocation_bytes) = bytes.get_mut(
457 relocation_offset_in_node as usize..relocation_offset_in_node as usize + 8,
458 ) {
459 for (byte, value) in relocation_bytes.iter_mut().zip(value.to_le_bytes()) {
460 *byte = Number::Int(i64::from(value));
461 }
462 written = true;
463 break;
464 }
465 }
466 }
467
468 if !written {
469 errors.push(CompileError::BytecodeError {
470 error: format!(
471 "could not apply rodata relocation at offset {}",
472 relocation.offset
473 ),
474 span: 0..0,
475 custom_label: None,
476 });
477 continue;
478 }
479
480 if !arch.is_v3() {
481 relocations.add_rel_dyn(
482 ast.text_size + relocation.offset,
483 RelocationType::RSbf64Relative,
484 String::new(),
485 );
486 }
487 }
488
489 LabelResolution {
490 dynamic_symbols,
491 relocations,
492 errors,
493 }
494}
495
496fn label_offset_map(ast: &AST) -> (LabelOffsetMap, Vec<NumericLabel>) {
497 let mut label_offset_map = HashMap::new();
498 let mut numeric_labels = Vec::new();
499
500 for (idx, node) in ast.nodes.iter().enumerate() {
501 if let ASTNode::Label { label, offset } = node {
502 label_offset_map.insert(label.name.clone(), *offset);
503 numeric_labels.push((label.name.clone(), *offset, idx));
504 }
505 }
506
507 for node in &ast.rodata_nodes {
508 if let ASTNode::ROData { rodata, offset } = node {
509 label_offset_map.insert(rodata.name.clone(), *offset + ast.text_size);
510 }
511 }
512
513 (label_offset_map, numeric_labels)
514}
515
516#[cfg(test)]
517mod tests {
518 use {
519 super::*,
520 crate::{astnode::Label, parser::Token, section::Section},
521 };
522
523 #[test]
524 fn test_ast_new() {
525 let ast = AST::new();
526 assert!(ast.nodes.is_empty());
527 assert!(ast.rodata_nodes.is_empty());
528 assert_eq!(ast.text_size, 0);
529 assert_eq!(ast.rodata_size, 0);
530 }
531
532 #[test]
533 fn test_ast_set_sizes() {
534 let mut ast = AST::new();
535 ast.set_text_size(100);
536 ast.set_rodata_size(50);
537 assert_eq!(ast.text_size, 100);
538 assert_eq!(ast.rodata_size, 50);
539 }
540
541 #[test]
542 fn test_get_instruction_at_offset() {
543 let mut ast = AST::new();
544 ast.nodes
545 .push(instruction_node(Opcode::Exit, 0, None, None));
546
547 let found = ast.get_instruction_at_offset(0);
548 assert!(found.is_some());
549 assert_eq!(found.unwrap().opcode, Opcode::Exit);
550
551 let not_found = ast.get_instruction_at_offset(8);
552 assert!(not_found.is_none());
553 }
554
555 #[test]
556 fn test_get_rodata_at_offset() {
557 let mut ast = AST::new();
558 let rodata = ROData {
559 name: "data".to_string(),
560 args: vec![
561 Token::Directive("ascii".to_string(), 0..5),
562 Token::StringLiteral("test".to_string(), 6..12),
563 ],
564 span: 0..12,
565 };
566 ast.rodata_nodes.push(ASTNode::ROData {
567 rodata: rodata.clone(),
568 offset: 0,
569 });
570
571 let found = ast.get_rodata_at_offset(0);
572 assert!(found.is_some());
573 assert_eq!(found.unwrap().name, "data");
574 }
575
576 #[test]
577 fn test_resolve_numeric_label_forward() {
578 let numeric_labels = vec![("1".to_string(), 16, 2), ("2".to_string(), 32, 4)];
579
580 let result = AST::resolve_numeric_label("1f", 0, &numeric_labels);
581 assert_eq!(result, Some(16));
582
583 let result = AST::resolve_numeric_label("2f", 3, &numeric_labels);
584 assert_eq!(result, Some(32));
585 }
586
587 #[test]
588 fn test_resolve_numeric_label_backward() {
589 let numeric_labels = vec![("1".to_string(), 16, 2), ("2".to_string(), 32, 4)];
590
591 let result = AST::resolve_numeric_label("1b", 3, &numeric_labels);
592 assert_eq!(result, Some(16));
593
594 let result = AST::resolve_numeric_label("2b", 5, &numeric_labels);
595 assert_eq!(result, Some(32));
596 }
597
598 #[test]
599 fn test_canonicalize_numeric_jump_target_to_label() {
600 let mut nodes = vec![
601 instruction_node(Opcode::Ja, 0, Some(Either::Left("1f".to_string())), None),
602 label_node("1", 8),
603 instruction_node(Opcode::Exit, 8, None, None),
604 ];
605
606 let canonicalized = optimizer::canonicalize_control_flow_targets(&mut nodes);
607
608 assert!(canonicalized.errors.is_empty());
609 assert!(canonicalized.labels_to_remove.is_empty());
610 let ASTNode::Instruction { instruction, .. } = &nodes[0] else {
611 panic!("expected instruction");
612 };
613 assert_eq!(instruction.off, Some(Either::Left("1".to_string())));
614 }
615
616 #[test]
617 fn test_canonicalize_relative_jump_target_to_synthetic_label() {
618 let mut nodes = vec![
619 instruction_node(Opcode::Ja, 0, Some(Either::Right(1)), None),
620 instruction_node(Opcode::Exit, 8, None, None),
621 instruction_node(Opcode::Exit, 16, None, None),
622 ];
623
624 let canonicalized = optimizer::canonicalize_control_flow_targets(&mut nodes);
625
626 assert!(canonicalized.errors.is_empty());
627 assert_eq!(canonicalized.labels_to_remove.len(), 1);
628
629 let ASTNode::Instruction { instruction, .. } = &nodes[0] else {
630 panic!("expected instruction");
631 };
632 let Some(Either::Left(label)) = &instruction.off else {
633 panic!("expected canonical label target");
634 };
635 assert!(label.starts_with("temp_"));
636 assert!(canonicalized.labels_to_remove.contains(label));
637 assert!(
638 matches!(&nodes[2], ASTNode::Label { label: target_label, offset }
639 if target_label.name == *label && *offset == 16)
640 );
641 }
642
643 #[test]
644 fn test_canonicalize_rejects_invalid_relative_jump_target() {
645 let mut nodes = vec![
646 instruction_node(Opcode::Ja, 0, Some(Either::Right(1)), None),
647 instruction_node(Opcode::Exit, 8, None, None),
648 ];
649
650 let canonicalized = optimizer::canonicalize_control_flow_targets(&mut nodes);
651
652 assert_eq!(canonicalized.errors.len(), 1);
653 assert!(canonicalized.labels_to_remove.is_empty());
654 assert_eq!(nodes.len(), 2);
655 let ASTNode::Instruction { instruction, .. } = &nodes[0] else {
656 panic!("expected instruction");
657 };
658 assert_eq!(instruction.off, Some(Either::Right(1)));
659 }
660
661 #[test]
662 fn test_dce_recomputes_relative_call_target_after_removing_code() {
663 let mut ast = AST::new();
664 ast.add_function_entry("entrypoint".to_string());
665 ast.add_function_entry("dead".to_string());
666 ast.add_function_entry("target".to_string());
667 ast.nodes = vec![
668 label_node("entrypoint", 0),
669 internal_call_node(0, 2),
670 instruction_node(Opcode::Exit, 8, None, None),
671 label_node("dead", 16),
672 instruction_node(Opcode::Exit, 16, None, None),
673 label_node("target", 24),
674 instruction_node(Opcode::Exit, 24, None, None),
675 ];
676 ast.set_text_size(32);
677
678 let program_layout =
679 build_program(ast, SbpfArch::V0, OptimizationConfig::enabled()).unwrap();
680 let nodes = program_layout.code_section.get_nodes();
681
682 assert_eq!(
683 nodes
684 .iter()
685 .filter(|node| matches!(node, ASTNode::Instruction { .. }))
686 .count(),
687 3
688 );
689 assert!(matches!(
690 &nodes[1],
691 ASTNode::Instruction { instruction, offset }
692 if instruction.opcode == Opcode::Call
693 && instruction.src == Some(Register { n: 1 })
694 && instruction.imm == Some(Either::Right(Number::Int(1)))
695 && *offset == 0
696 ));
697 assert!(matches!(
698 &nodes[3],
699 ASTNode::Label { label, offset } if label.name == "target" && *offset == 16
700 ));
701 }
702
703 #[test]
704 fn test_optimize_ast_removes_temp_jump_target_labels() {
705 let mut ast = AST::new();
706 ast.nodes = vec![
707 instruction_node(Opcode::Ja, 0, Some(Either::Right(1)), None),
708 instruction_node(Opcode::Exit, 8, None, None),
709 instruction_node(Opcode::Exit, 16, None, None),
710 ];
711 ast.set_text_size(24);
712 ast.set_rodata_size(0);
713
714 let result = build_program(ast, SbpfArch::V0, OptimizationConfig::enabled());
715
716 assert!(result.is_ok());
717 let program_layout = result.unwrap();
718 assert!(!program_layout.code_section.get_nodes().iter().any(|node| {
719 matches!(node, ASTNode::Label { label, .. }
720 if label
721 .name
722 .starts_with("temp_"))
723 }));
724 let ASTNode::Instruction { instruction, .. } = &program_layout.code_section.get_nodes()[0]
725 else {
726 panic!("expected instruction");
727 };
728 assert_eq!(instruction.off, Some(Either::Right(1)));
729 }
730
731 #[test]
732 fn test_build_program_simple() {
733 for arch in [SbpfArch::V0, SbpfArch::V3] {
734 let mut ast = AST::new();
735 ast.nodes
736 .push(instruction_node(Opcode::Exit, 0, None, None));
737 ast.set_text_size(8);
738 ast.set_rodata_size(0);
739
740 let result = build_program(ast, arch, OptimizationConfig::default());
741 assert!(result.is_ok());
742 let parse_result = result.unwrap();
743 assert!(parse_result.prog_is_static);
744 }
745 }
746
747 #[test]
748 fn test_build_program_undefined_label_error() {
749 for arch in [SbpfArch::V0, SbpfArch::V3] {
750 let mut ast = AST::new();
751 ast.nodes.push(instruction_node(
752 Opcode::Ja,
753 0,
754 Some(Either::Left("undefined_label".to_string())),
755 None,
756 ));
757 ast.set_text_size(8);
758
759 let result = build_program(ast, arch, OptimizationConfig::default());
760 assert!(result.is_err());
761 }
762 }
763
764 #[test]
765 fn test_build_program_static_syscalls_no_relocation() {
766 let mut ast = AST::new();
767
768 ast.nodes.push(instruction_node(
769 Opcode::Call,
770 0,
771 None,
772 Some(Either::Left("sol_log_".to_string())),
773 ));
774 ast.nodes
775 .push(instruction_node(Opcode::Exit, 8, None, None));
776
777 ast.set_text_size(16);
778 ast.set_rodata_size(0);
779
780 let result = build_program(ast, SbpfArch::V3, OptimizationConfig::default());
781 assert!(result.is_ok());
782 let parse_result = result.unwrap();
783
784 assert!(parse_result.prog_is_static);
785 assert!(parse_result.relocation_data.get_rel_dyns().is_empty());
786 }
787
788 #[test]
789 fn test_build_program_dynamic_syscalls_with_relocation() {
790 let mut ast = AST::new();
791
792 ast.nodes.push(instruction_node(
793 Opcode::Call,
794 0,
795 None,
796 Some(Either::Left("sol_log_".to_string())),
797 ));
798 ast.nodes
799 .push(instruction_node(Opcode::Exit, 8, None, None));
800
801 ast.set_text_size(16);
802 ast.set_rodata_size(0);
803
804 let result = build_program(ast, SbpfArch::V0, OptimizationConfig::default());
805 assert!(result.is_ok());
806 let parse_result = result.unwrap();
807
808 assert!(!parse_result.prog_is_static);
809 assert!(!parse_result.relocation_data.get_rel_dyns().is_empty());
810 }
811
812 #[test]
813 fn test_build_program_rodata_data_relocation_v0() {
814 let result = build_program(
815 rodata_data_relocation_ast(),
816 SbpfArch::V0,
817 OptimizationConfig::default(),
818 );
819 assert!(result.is_ok());
820 let layout = result.unwrap();
821 assert!(!layout.prog_is_static);
822 let target_address: u64 = 64 + 3 * 56 + 8 + 8;
823 assert_eq!(
824 &layout.data_section.bytecode()[..8],
825 &(target_address << 32).to_le_bytes()
826 );
827 }
828
829 #[test]
830 fn test_build_program_rodata_data_relocation_v3() {
831 let result = build_program(
832 rodata_data_relocation_ast(),
833 SbpfArch::V3,
834 OptimizationConfig::default(),
835 );
836 assert!(result.is_ok());
837 let layout = result.unwrap();
838 assert!(layout.prog_is_static);
839 assert_eq!(
840 &layout.data_section.bytecode()[..8],
841 &(ProgramHeader::V3_RODATA_VADDR + 8).to_le_bytes()
842 );
843 }
844
845 #[test]
846 fn test_build_program_rodata_fn_relocation_v0() {
847 let result = build_program(
848 rodata_fn_relocation_ast(),
849 SbpfArch::V0,
850 OptimizationConfig::default(),
851 );
852 assert!(result.is_ok());
853 let layout = result.unwrap();
854 assert!(!layout.prog_is_static);
855 let target_address: u64 = 64 + 3 * 56;
856 assert_eq!(
857 &layout.data_section.bytecode()[..8],
858 &(target_address << 32).to_le_bytes()
859 );
860 }
861
862 #[test]
863 fn test_build_program_rodata_fn_relocation_v3() {
864 let result = build_program(
865 rodata_fn_relocation_ast(),
866 SbpfArch::V3,
867 OptimizationConfig::default(),
868 );
869 assert!(result.is_ok());
870 let layout = result.unwrap();
871 assert!(layout.prog_is_static);
872 assert_eq!(
873 &layout.data_section.bytecode()[..8],
874 &ProgramHeader::V3_BYTECODE_VADDR.to_le_bytes()
875 );
876 }
877
878 #[test]
879 fn test_build_program_rodata_relocation_undefined_label() {
880 let mut ast = AST::new();
881 ast.nodes
882 .push(instruction_node(Opcode::Exit, 0, None, None));
883 ast.add_rodata_relocation(8, "invalid".to_string());
884 ast.set_text_size(8);
885 let result = build_program(ast, SbpfArch::V0, OptimizationConfig::default());
886 let Err(errors) = result else {
887 panic!("expected undefined label");
888 };
889 assert!(matches!(
890 &errors[..],
891 [CompileError::UndefinedLabel { label, .. }] if label == "invalid"
892 ));
893 }
894
895 fn label_node(name: &str, offset: u64) -> ASTNode {
896 ASTNode::Label {
897 label: Label {
898 name: name.to_string(),
899 span: 0..0,
900 },
901 offset,
902 }
903 }
904
905 fn instruction_node(
906 opcode: Opcode,
907 offset: u64,
908 off: Option<Either<String, i16>>,
909 imm: Option<Either<String, Number>>,
910 ) -> ASTNode {
911 ASTNode::Instruction {
912 instruction: Instruction {
913 opcode,
914 dst: None,
915 src: None,
916 off,
917 imm,
918 span: 0..0,
919 },
920 offset,
921 }
922 }
923
924 fn internal_call_node(offset: u64, relative_offset: i64) -> ASTNode {
925 ASTNode::Instruction {
926 instruction: Instruction {
927 opcode: Opcode::Call,
928 dst: None,
929 src: Some(Register { n: 1 }),
930 off: None,
931 imm: Some(Either::Right(Number::Int(relative_offset))),
932 span: 0..0,
933 },
934 offset,
935 }
936 }
937
938 fn rodata_data_relocation_ast() -> AST {
940 let mut ast = AST::new();
941 ast.nodes
942 .push(instruction_node(Opcode::Exit, 0, None, None));
943 ast.rodata_nodes.push(ASTNode::ROData {
944 rodata: ROData {
945 name: "ptr".to_string(),
946 args: vec![
947 Token::Directive("byte".to_string(), 0..1),
948 Token::VectorLiteral(vec![Number::Int(0); 8], 0..1),
949 ],
950 span: 0..1,
951 },
952 offset: 0,
953 });
954 ast.rodata_nodes.push(ASTNode::ROData {
955 rodata: ROData {
956 name: "data".to_string(),
957 args: vec![
958 Token::Directive("byte".to_string(), 0..1),
959 Token::VectorLiteral(vec![Number::Int(0); 8], 0..1),
960 ],
961 span: 0..1,
962 },
963 offset: 8,
964 });
965 ast.add_rodata_relocation(0, "data".to_string());
966 ast.set_text_size(8);
967 ast.set_rodata_size(16);
968 ast
969 }
970
971 fn rodata_fn_relocation_ast() -> AST {
973 let mut ast = AST::new();
974 ast.nodes.push(label_node("fn", 0));
975 ast.nodes
976 .push(instruction_node(Opcode::Exit, 0, None, None));
977 ast.rodata_nodes.push(ASTNode::ROData {
978 rodata: ROData {
979 name: "ptr".to_string(),
980 args: vec![
981 Token::Directive("byte".to_string(), 0..1),
982 Token::VectorLiteral(vec![Number::Int(0); 8], 0..1),
983 ],
984 span: 0..1,
985 },
986 offset: 0,
987 });
988 ast.add_rodata_relocation(0, "fn".to_string());
989 ast.set_text_size(8);
990 ast.set_rodata_size(8);
991 ast
992 }
993}