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