1use crate::LinkError;
36use crate::elf::parse_linked_elf;
37use nub_program::abi::{CODE_BASE, DATA_BASE, PAGE_SIZE, SP_REG};
38use nub_program::{Endpoint, ProgramBlob, Regions};
39use std::collections::BTreeMap;
40
41const OP_AUIPC: u32 = 0b001_0111;
43const OP_LUI: u32 = 0b011_0111;
45const OP_SYSTEM: u32 = 0b111_0011;
47#[cfg(test)]
49const OP_OP_IMM: u32 = 0b001_0011;
50const OP_CUSTOM_0: u32 = 0b000_1011;
52const OP_CUSTOM_1: u32 = 0b010_1011;
54const OP_JAL: u32 = 0b110_1111;
56const OP_JALR: u32 = 0b110_0111;
58
59const NOP_BYTES: [u8; 4] = [0x13, 0x00, 0x00, 0x00];
61
62const CSR_ECALL_JAR: u32 = 0x800;
64const CSR_ECALLI: u32 = 0x801;
65
66pub fn link_elf(elf_data: &[u8]) -> Result<ProgramBlob, LinkError> {
70 let elf = parse_linked_elf(elf_data)?;
71
72 if elf.code_sections.is_empty() {
84 return Err(LinkError::InvalidSection(
85 "link_elf: ELF has no code sections".into(),
86 ));
87 }
88 let mut sections_by_vaddr: Vec<&(u64, u64, Vec<u8>)> = elf.code_sections.iter().collect();
89 sections_by_vaddr.sort_by_key(|(_, v, _)| *v);
90 let base_vaddr = sections_by_vaddr[0].1;
91 let mut code_end_vaddr = base_vaddr;
92 for (_, v, d) in §ions_by_vaddr {
93 let end = v.saturating_add(d.len() as u64);
94 if end > code_end_vaddr {
95 code_end_vaddr = end;
96 }
97 }
98 let span = (code_end_vaddr - base_vaddr) as usize;
99 let mut code: Vec<u8> = vec![0u8; span];
100 for (_, v, d) in §ions_by_vaddr {
101 let off = (v - base_vaddr) as usize;
102 code[off..off + d.len()].copy_from_slice(d);
103 }
104 let code_len = code.len();
105
106 let vaddr_to_offset = |v: u64| -> Option<usize> {
107 if v < base_vaddr {
108 return None;
109 }
110 let o = (v - base_vaddr) as usize;
111 if o >= code_len { None } else { Some(o) }
112 };
113
114 let is_code_addr = |addr: u64| -> bool {
115 elf.code_ranges
116 .iter()
117 .any(|(start, end)| addr >= *start && addr < *end)
118 };
119
120 let mut code_auipc: BTreeMap<usize, usize> = BTreeMap::new();
141 let mut code_lo12: Vec<(usize, usize, usize)> = Vec::new();
142
143 for (&call_v, &target) in &elf.call_targets {
145 let auipc_off = vaddr_to_offset(call_v).ok_or_else(|| {
146 LinkError::InvalidSection(format!(
147 "link_elf: CALL_PLT AUIPC at vaddr {call_v:#x} outside code section"
148 ))
149 })?;
150 let target_off = vaddr_to_offset(target).ok_or_else(|| {
151 LinkError::InvalidSection(format!(
152 "link_elf: CALL_PLT target {target:#x} (from {call_v:#x}) outside code section"
153 ))
154 })?;
155 expect_auipc(&code, auipc_off, call_v)?;
156 code_auipc.insert(auipc_off, target_off);
157 if let Some(jalr_off) = vaddr_to_offset(call_v + 4) {
158 code_lo12.push((jalr_off, auipc_off, target_off));
159 }
160 }
161
162 for (&hi20_v, &target) in &elf.hi20_targets {
164 let auipc_off = vaddr_to_offset(hi20_v).ok_or_else(|| {
165 LinkError::InvalidSection(format!(
166 "link_elf: PCREL_HI20 AUIPC at vaddr {hi20_v:#x} outside code section"
167 ))
168 })?;
169 if is_code_addr(target) {
170 let target_off = vaddr_to_offset(target).ok_or_else(|| {
171 LinkError::InvalidSection(format!(
172 "link_elf: PCREL_HI20 code target {target:#x} (from {hi20_v:#x}) out of range"
173 ))
174 })?;
175 expect_auipc(&code, auipc_off, hi20_v)?;
176 code_auipc.insert(auipc_off, target_off);
177 } else {
178 let data_target = target.wrapping_add(u64::from(DATA_BASE));
181 fold_auipc_to_lui(
182 &mut code,
183 auipc_off,
184 hi20_v,
185 (data_target & 0xFFFF_FFFF) as u32,
186 )?;
187 }
188 }
189
190 for (&lo_v, &target) in &elf.lo12_targets {
193 let Some(lo_off) = vaddr_to_offset(lo_v) else {
194 continue;
195 };
196 if lo_off + 4 > code.len() {
197 continue;
198 }
199 if is_code_addr(target) {
200 if let Some(&hi20_v) = elf.lo12_to_hi20.get(&lo_v)
201 && let (Some(auipc_off), Some(target_off)) =
202 (vaddr_to_offset(hi20_v), vaddr_to_offset(target))
203 {
204 code_lo12.push((lo_off, auipc_off, target_off));
205 }
206 } else {
207 let data_target = target.wrapping_add(u64::from(DATA_BASE));
211 patch_lo12_abs(&mut code, lo_off, (data_target & 0xFFFF_FFFF) as u32);
212 }
213 }
214
215 rewrite_ecall_markers(&mut code)?;
228
229 let endpoint_entries_pre: Vec<usize> = {
249 match crate::elf::find_all_section_bytes(elf_data, ".nub.endpoints") {
250 Ok(sections) => sections
251 .iter()
252 .flat_map(|s| s.chunks(16))
253 .filter_map(|chunk| {
254 if chunk.len() < 8 {
255 return None;
256 }
257 let fn_ptr = u64::from_le_bytes(chunk[0..8].try_into().unwrap());
258 if fn_ptr < base_vaddr {
259 return None;
260 }
261 Some((fn_ptr - base_vaddr) as usize)
262 })
263 .collect(),
264 Err(_) => Vec::new(),
265 }
266 };
267 let rodata_targets_pre: Vec<usize> = elf
268 .abs_code_ptrs
269 .iter()
270 .filter_map(|&(_, rv_target, _)| {
271 if is_code_addr(rv_target) {
272 Some(rv_target.wrapping_sub(base_vaddr) as usize)
273 } else {
274 None
275 }
276 })
277 .collect();
278 let mut extra_targets: Vec<usize> = endpoint_entries_pre;
279 extra_targets.extend_from_slice(&rodata_targets_pre);
280 extra_targets.extend(code_auipc.values().copied());
281 let offset_map = align_branch_targets(&mut code, &extra_targets)?;
282
283 fixup_code_pcrel(&mut code, &offset_map, &code_auipc, &code_lo12)?;
289
290 validate_pvm2(&code)?;
295
296 let mut ro_data_rewritten = elf.ro_data.clone();
312 let ro_base = elf.stack_size as u64;
313 {
314 let sub32_data_vaddrs: std::collections::HashSet<u64> =
317 elf.sub32_relocs.iter().map(|(v, _)| *v).collect();
318
319 let translate_code_addr = |rv_target: u64| -> u32 {
322 let pre = rv_target.wrapping_sub(base_vaddr) as usize;
323 let off = offset_map.get(&pre).copied().unwrap_or(pre);
324 CODE_BASE.wrapping_add(off as u32)
325 };
326
327 for &(data_vaddr, rv_target, size) in &elf.abs_code_ptrs {
328 if sub32_data_vaddrs.contains(&data_vaddr) {
329 continue;
331 }
332 if !is_code_addr(rv_target) {
333 continue;
334 }
335 if data_vaddr < ro_base {
336 continue;
337 }
338 let off = (data_vaddr - ro_base) as usize;
339 let new_val = translate_code_addr(rv_target);
340 match size {
341 4 if off + 4 <= ro_data_rewritten.len() => {
342 ro_data_rewritten[off..off + 4].copy_from_slice(&new_val.to_le_bytes());
343 }
344 8 if off + 8 <= ro_data_rewritten.len() => {
345 ro_data_rewritten[off..off + 8]
346 .copy_from_slice(&(new_val as u64).to_le_bytes());
347 }
348 _ => {}
349 }
350 }
351
352 let mut off = 0;
355 let already_covered: std::collections::HashSet<u64> =
356 elf.abs_code_ptrs.iter().map(|&(v, _, _)| v).collect();
357 while off + 8 <= ro_data_rewritten.len() {
358 let val = u64::from_le_bytes(ro_data_rewritten[off..off + 8].try_into().unwrap());
359 if is_code_addr(val) {
360 let vaddr = ro_base + off as u64;
361 if !already_covered.contains(&vaddr) {
362 let new_val = translate_code_addr(val);
363 ro_data_rewritten[off..off + 8]
364 .copy_from_slice(&(new_val as u64).to_le_bytes());
365 }
366 }
367 off += 8;
368 }
369 }
370
371 let mut rw_data_rewritten = elf.rw_data.clone();
381 {
382 let ro_base = elf.stack_size as u64;
383 let rw_base = elf.rw_base;
384 for &(data_vaddr, target, size) in &elf.abs_data_ptrs {
385 let new_val = target.wrapping_add(u64::from(DATA_BASE));
386 let n = size as usize;
387 let bytes = new_val.to_le_bytes();
388 if data_vaddr >= ro_base
389 && (data_vaddr - ro_base) as usize + n <= ro_data_rewritten.len()
390 {
391 let off = (data_vaddr - ro_base) as usize;
392 ro_data_rewritten[off..off + n].copy_from_slice(&bytes[..n]);
393 } else if data_vaddr >= rw_base
394 && (data_vaddr - rw_base) as usize + n <= rw_data_rewritten.len()
395 {
396 let off = (data_vaddr - rw_base) as usize;
397 rw_data_rewritten[off..off + n].copy_from_slice(&bytes[..n]);
398 } else {
399 return Err(LinkError::InvalidSection(format!(
400 "link_elf: absolute data pointer at vaddr {data_vaddr:#x} (→ {target:#x}) \
401 falls outside the RO/RW data blobs; cannot relocate to DATA_BASE"
402 )));
403 }
404 }
405 }
406
407 let mut endpoints = read_endpoint_descriptors(elf_data, base_vaddr, code.len())?;
413 for def in endpoints.values_mut() {
414 let pre = def.entry_pc as usize;
415 if let Some(&new) = offset_map.get(&pre) {
416 def.entry_pc = new as u64;
417 }
418 }
419
420 let regions = Regions {
432 stack_pages: elf.stack_size / PAGE_SIZE,
433 ro_pages: (ro_data_rewritten.len() as u32).div_ceil(PAGE_SIZE),
434 rw_pages: (rw_data_rewritten.len() as u32).div_ceil(PAGE_SIZE),
435 heap_pages: elf.heap_pages,
436 };
437
438 let stack_top = regions.stack_top();
439 for def in endpoints.values_mut() {
440 def.initial_regs.insert(SP_REG, stack_top);
441 }
442
443 Ok(ProgramBlob::new(
444 code,
445 regions,
446 ro_data_rewritten,
447 rw_data_rewritten,
448 endpoints,
449 )?)
450}
451
452fn expect_auipc(code: &[u8], off: usize, v: u64) -> Result<(), LinkError> {
455 if off + 4 > code.len() {
456 return Err(LinkError::InvalidSection(format!(
457 "link_elf: AUIPC reloc at vaddr {v:#x} truncated by section end"
458 )));
459 }
460 let word = u32::from_le_bytes([code[off], code[off + 1], code[off + 2], code[off + 3]]);
461 if word & 0x7F != OP_AUIPC {
462 return Err(LinkError::InvalidSection(format!(
463 "link_elf: reloc at vaddr {v:#x} not an AUIPC (opcode {:#x})",
464 word & 0x7F
465 )));
466 }
467 Ok(())
468}
469
470fn fold_auipc_to_lui(code: &mut [u8], off: usize, v: u64, eff: u32) -> Result<(), LinkError> {
474 if off + 4 > code.len() {
475 return Err(LinkError::InvalidSection(format!(
476 "link_elf: AUIPC reloc at vaddr {v:#x} truncated by section end"
477 )));
478 }
479 let word = u32::from_le_bytes([code[off], code[off + 1], code[off + 2], code[off + 3]]);
480 if word & 0x7F != OP_AUIPC {
481 return Err(LinkError::InvalidSection(format!(
482 "link_elf: reloc at vaddr {v:#x} not an AUIPC (opcode {:#x})",
483 word & 0x7F
484 )));
485 }
486 let rd = (word >> 7) & 0x1F;
487 let new_word = (eff.wrapping_add(0x800) & 0xFFFF_F000) | (rd << 7) | OP_LUI;
488 code[off..off + 4].copy_from_slice(&new_word.to_le_bytes());
489 Ok(())
490}
491
492fn patch_lo12_abs(code: &mut [u8], off: usize, eff: u32) {
495 let new_lo12 = ((eff as i32) << 20) >> 20;
496 match code[off] & 0x7F {
497 0b0000011 | 0b0010011 | 0b1100111 => patch_imm_i(&mut code[off..off + 4], new_lo12),
499 0b0100011 => patch_imm_s(&mut code[off..off + 4], new_lo12),
501 _ => {}
502 }
503}
504
505fn fixup_code_pcrel(
510 code: &mut [u8],
511 offset_map: &BTreeMap<usize, usize>,
512 code_auipc: &BTreeMap<usize, usize>,
513 code_lo12: &[(usize, usize, usize)],
514) -> Result<(), LinkError> {
515 let remap = |o: usize| -> Result<usize, LinkError> {
516 offset_map.get(&o).copied().ok_or_else(|| {
517 LinkError::InvalidSection(format!("fixup_code_pcrel: offset {o:#x} not in offset_map"))
518 })
519 };
520 for (&auipc_off, &target_off) in code_auipc {
521 let na = remap(auipc_off)?;
522 let nt = remap(target_off)?;
523 if na + 4 > code.len() {
524 continue;
525 }
526 let word = u32::from_le_bytes([code[na], code[na + 1], code[na + 2], code[na + 3]]);
527 if word & 0x7F != OP_AUIPC {
528 return Err(LinkError::InvalidSection(format!(
529 "fixup_code_pcrel: expected AUIPC at offset {na:#x} (opcode {:#x})",
530 word & 0x7F
531 )));
532 }
533 let disp = nt as i64 - na as i64;
534 let rd = (word >> 7) & 0x1F;
535 let new_word = ((disp as u32).wrapping_add(0x800) & 0xFFFF_F000) | (rd << 7) | OP_AUIPC;
536 code[na..na + 4].copy_from_slice(&new_word.to_le_bytes());
537 }
538 for &(lo12_off, auipc_off, target_off) in code_lo12 {
539 let nl = remap(lo12_off)?;
540 let na = remap(auipc_off)?;
541 let nt = remap(target_off)?;
542 if nl + 4 > code.len() {
543 continue;
544 }
545 let disp = nt as i64 - na as i64;
546 let new_lo12 = ((disp as i32) << 20) >> 20;
547 match code[nl] & 0x7F {
548 0b0000011 | 0b0010011 | 0b1100111 => patch_imm_i(&mut code[nl..nl + 4], new_lo12),
549 0b0100011 => patch_imm_s(&mut code[nl..nl + 4], new_lo12),
550 _ => {}
551 }
552 }
553 Ok(())
554}
555
556fn rewrite_ecall_markers(code: &mut [u8]) -> Result<(), LinkError> {
565 let n = code.len();
566 let mut i = 0;
567 while i + 2 <= n {
568 let lo = u16::from_le_bytes([code[i], code[i + 1]]);
570 if lo & 0b11 != 0b11 {
571 i += 2;
572 continue;
573 }
574 if i + 4 > n {
575 break;
576 }
577 let word = u32::from_le_bytes([code[i], code[i + 1], code[i + 2], code[i + 3]]);
578 let opcode = word & 0x7F;
579 let funct3 = (word >> 12) & 0x7;
580 if opcode == OP_SYSTEM && funct3 == 0b001 {
581 let csr = (word >> 20) & 0xFFF;
583 if csr == CSR_ECALL_JAR || csr == CSR_ECALLI {
584 code[i..i + 4].copy_from_slice(&NOP_BYTES);
585 let j = i + 4;
586 if j + 4 <= n {
587 let nxt = u32::from_le_bytes([code[j], code[j + 1], code[j + 2], code[j + 3]]);
588 if is_full_length(nxt) && is_standard_ecall(nxt) {
589 let new_word = if csr == CSR_ECALL_JAR {
590 encode_custom0_ecall_jar()
591 } else {
592 encode_custom0_ecalli(0)
593 };
594 code[j..j + 4].copy_from_slice(&new_word.to_le_bytes());
595 i = j + 4;
596 continue;
597 }
598 }
599 i += 4;
602 continue;
603 }
604 }
605 if opcode == OP_SYSTEM && funct3 == 0 && is_standard_ecall(word) {
606 let new_word = encode_custom0_ecalli(0);
608 code[i..i + 4].copy_from_slice(&new_word.to_le_bytes());
609 }
610 i += 4;
611 }
612 Ok(())
613}
614
615#[derive(Clone, Copy)]
620struct RegFields {
621 rd: bool,
622 rs1: bool,
623 rs2: bool,
624}
625const REG_NONE: RegFields = RegFields {
626 rd: false,
627 rs1: false,
628 rs2: false,
629};
630
631fn reg_fields_for(opcode: u32) -> RegFields {
634 match opcode {
635 0b011_0011 | 0b011_1011 => RegFields {
637 rd: true,
638 rs1: true,
639 rs2: true,
640 },
641 0b000_0011 => RegFields {
643 rd: true,
644 rs1: true,
645 rs2: false,
646 },
647 0b001_0011 | 0b001_1011 | 0b110_0111 => RegFields {
650 rd: true,
651 rs1: true,
652 rs2: false,
653 },
654 0b010_0011 => RegFields {
656 rd: false,
657 rs1: true,
658 rs2: true,
659 },
660 0b110_0011 => RegFields {
662 rd: false,
663 rs1: true,
664 rs2: true,
665 },
666 0b011_0111 | 0b001_0111 => RegFields {
668 rd: true,
669 rs1: false,
670 rs2: false,
671 },
672 0b110_1111 => RegFields {
674 rd: true,
675 rs1: false,
676 rs2: false,
677 },
678 0b000_1111 => REG_NONE,
680 0b000_1011 => RegFields {
684 rd: true,
685 rs1: true,
686 rs2: false,
687 },
688 _ => REG_NONE,
689 }
690}
691
692fn validate_pvm2(code: &[u8]) -> Result<(), LinkError> {
699 let n = code.len();
700 let mut i = 0;
701 while i < n {
702 if i + 2 > n {
703 break;
704 }
705 let lo16 = u16::from_le_bytes([code[i], code[i + 1]]);
706 if lo16 & 0b11 != 0b11 {
707 if lo16 == 0x9002 {
712 return Err(LinkError::InvalidSection(format!(
713 "link_elf: c.ebreak at offset {:#x} (forbidden)",
714 i
715 )));
716 }
717 i += 2;
718 continue;
719 }
720 if i + 4 > n {
721 break;
722 }
723 let w = u32::from_le_bytes([code[i], code[i + 1], code[i + 2], code[i + 3]]);
724 let opcode = w & 0x7F;
725 match opcode {
726 OP_CUSTOM_1 => {
727 return Err(LinkError::InvalidSection(format!(
728 "link_elf: custom-1 opcode at offset {:#x} is reserved in PVM2",
729 i
730 )));
731 }
732 OP_SYSTEM => {
733 let funct3 = (w >> 12) & 0x7;
734 let csr_or_imm = (w >> 20) & 0xFFF;
735 if funct3 == 0 {
736 return Err(LinkError::InvalidSection(format!(
737 "link_elf: standard ECALL/EBREAK at offset {:#x} (imm={:#x})",
738 i, csr_or_imm
739 )));
740 }
741 return Err(LinkError::InvalidSection(format!(
742 "link_elf: CSR op at offset {:#x} (funct3={})",
743 i, funct3
744 )));
745 }
746 0b010_1111 => {
747 return Err(LinkError::InvalidSection(format!(
748 "link_elf: atomic op at offset {:#x}",
749 i
750 )));
751 }
752 0b000_0111 | 0b010_0111 => {
753 return Err(LinkError::InvalidSection(format!(
754 "link_elf: FP load/store at offset {:#x}",
755 i
756 )));
757 }
758 0b101_0011 => {
759 return Err(LinkError::InvalidSection(format!(
760 "link_elf: FP arithmetic at offset {:#x}",
761 i
762 )));
763 }
764 _ => {}
765 }
766 let rf = reg_fields_for(opcode);
768 let rd = (w >> 7) & 0x1F;
769 let rs1 = (w >> 15) & 0x1F;
770 let rs2 = (w >> 20) & 0x1F;
771 let check = |name: &str, r: u32| -> Result<(), LinkError> {
776 if r == 3 || r == 4 || r >= 16 {
777 return Err(LinkError::InvalidSection(format!(
778 "link_elf: forbidden register x{} ({}) at offset {:#x}",
779 r, name, i
780 )));
781 }
782 Ok(())
783 };
784 if rf.rd {
785 check("rd", rd)?;
786 }
787 if rf.rs1 {
788 check("rs1", rs1)?;
789 }
790 if rf.rs2 {
791 check("rs2", rs2)?;
792 }
793 i += 4;
794 }
795 Ok(())
796}
797
798fn read_endpoint_descriptors(
803 elf_data: &[u8],
804 base_vaddr: u64,
805 code_len: usize,
806) -> Result<BTreeMap<u8, Endpoint>, LinkError> {
807 let sections = crate::elf::find_all_section_bytes(elf_data, ".nub.endpoints")?;
808 const DESCRIPTOR_SIZE: usize = 16;
809 let mut endpoints: BTreeMap<u8, Endpoint> = BTreeMap::new();
810 for section_bytes in §ions {
811 if section_bytes.len() % DESCRIPTOR_SIZE != 0 {
812 return Err(LinkError::InvalidSection(format!(
813 ".nub.endpoints size {} is not a multiple of {}",
814 section_bytes.len(),
815 DESCRIPTOR_SIZE
816 )));
817 }
818 for chunk in section_bytes.chunks(DESCRIPTOR_SIZE) {
819 let fn_ptr = u64::from_le_bytes(chunk[0..8].try_into().unwrap());
820 let index = chunk[8];
821 let arg_registers = chunk[9];
822 let arg_meta = chunk[10];
825 if fn_ptr < base_vaddr || fn_ptr >= base_vaddr + code_len as u64 {
826 return Err(LinkError::InvalidSection(format!(
827 "nub_rt endpoint {} fn_ptr {:#x} outside code section",
828 index, fn_ptr
829 )));
830 }
831 let rv_pc = fn_ptr - base_vaddr;
832 if endpoints
833 .insert(
834 index,
835 Endpoint {
836 entry_pc: rv_pc,
837 arg_registers,
838 arg_meta,
839 initial_regs: BTreeMap::new(),
840 },
841 )
842 .is_some()
843 {
844 return Err(LinkError::InvalidSection(format!(
845 "duplicate #[nub_rt::endpoint({})] declaration",
846 index
847 )));
848 }
849 }
850 }
851 if endpoints.is_empty() {
852 return Err(LinkError::InvalidSection(
853 ".nub.endpoints section is absent or empty: \
854 the guest must declare at least one #[nub_rt::endpoint(N)]"
855 .into(),
856 ));
857 }
858 Ok(endpoints)
859}
860
861#[inline]
865fn is_full_length(word: u32) -> bool {
866 word & 0b11 == 0b11
867}
868
869fn patch_imm_i(slot: &mut [u8], imm: i32) {
872 let w = u32::from_le_bytes([slot[0], slot[1], slot[2], slot[3]]);
873 let cleared = w & 0x000F_FFFF;
874 let imm12 = (imm as u32) & 0xFFF;
875 let patched = cleared | (imm12 << 20);
876 slot[0..4].copy_from_slice(&patched.to_le_bytes());
877}
878
879fn patch_imm_s(slot: &mut [u8], imm: i32) {
881 let w = u32::from_le_bytes([slot[0], slot[1], slot[2], slot[3]]);
882 let cleared = w & 0x01FF_F07F;
883 let imm12 = (imm as u32) & 0xFFF;
884 let hi7 = (imm12 >> 5) & 0x7F;
885 let lo5 = imm12 & 0x1F;
886 let patched = cleared | (hi7 << 25) | (lo5 << 7);
887 slot[0..4].copy_from_slice(&patched.to_le_bytes());
888}
889
890#[inline]
892fn is_standard_ecall(word: u32) -> bool {
893 word == 0x0000_0073
894}
895
896#[inline]
898fn encode_custom0_ecall_jar() -> u32 {
899 (0b001 << 12) | OP_CUSTOM_0
901}
902
903#[inline]
906fn encode_custom0_ecalli(imm: i32) -> u32 {
907 let imm12 = (imm as u32) & 0xFFF;
908 (imm12 << 20) | (0b010 << 12) | OP_CUSTOM_0
909}
910
911#[inline]
914fn encode_custom0_fallthrough() -> u32 {
915 (0b100 << 12) | OP_CUSTOM_0
916}
917
918fn imm_j(w: u32) -> i32 {
920 let b20 = (w >> 31) & 1;
921 let b10_1 = (w >> 21) & 0x3FF;
922 let b11 = (w >> 20) & 1;
923 let b19_12 = (w >> 12) & 0xFF;
924 let raw = (b20 << 20) | (b19_12 << 12) | (b11 << 11) | (b10_1 << 1);
925 ((raw as i32) << 11) >> 11
926}
927
928fn imm_b(w: u32) -> i32 {
930 let b12 = (w >> 31) & 1;
931 let b11 = (w >> 7) & 1;
932 let b10_5 = (w >> 25) & 0x3F;
933 let b4_1 = (w >> 8) & 0xF;
934 let raw = (b12 << 12) | (b11 << 11) | (b10_5 << 5) | (b4_1 << 1);
935 ((raw as i32) << 19) >> 19
936}
937
938fn encode_b_imm(opcode_and_regs: u32, imm: i32) -> u32 {
940 let v = imm as u32;
941 let b12 = (v >> 12) & 0x1;
942 let b11 = (v >> 11) & 0x1;
943 let b10_5 = (v >> 5) & 0x3F;
944 let b4_1 = (v >> 1) & 0xF;
945 let cleared = opcode_and_regs & 0x01FF_F07F;
947 cleared | (b12 << 31) | (b10_5 << 25) | (b4_1 << 8) | (b11 << 7)
948}
949
950fn align_branch_targets(
963 code: &mut Vec<u8>,
964 extra_targets: &[usize],
965) -> Result<BTreeMap<usize, usize>, LinkError> {
966 let n = code.len();
975 let mut inst_starts: Vec<usize> = Vec::with_capacity(n / 4);
976 let mut post_terminator: std::collections::HashSet<usize> = std::collections::HashSet::new();
977 post_terminator.insert(0); let mut static_edges: Vec<(usize, usize)> = Vec::new(); let mut pc: usize = 0;
981 while pc < n {
982 inst_starts.push(pc);
983 let lo = u16::from_le_bytes([code[pc], code[pc + 1]]);
984 let inst_len: usize;
985 let is_terminator: bool;
986 let target: Option<i64>;
987 if lo & 0b11 != 0b11 {
988 inst_len = 2;
990 let op = lo & 0b11;
999 let f3 = (lo >> 13) & 0b111;
1000 if lo == 0 {
1001 is_terminator = true;
1003 target = None;
1004 } else if op == 0b01 && f3 == 0b101 {
1005 let imm = decompress_cj_imm(lo);
1007 is_terminator = true;
1008 target = Some(pc as i64 + imm as i64);
1009 } else if op == 0b01 && (f3 == 0b110 || f3 == 0b111) {
1010 let imm = decompress_cb_imm(lo);
1012 is_terminator = true;
1013 target = Some(pc as i64 + imm as i64);
1014 } else if op == 0b10 && f3 == 0b100 {
1015 let bit12 = (lo >> 12) & 1;
1022 let rdrs1 = (lo >> 7) & 0x1F;
1023 let rs2 = (lo >> 2) & 0x1F;
1024 let is_jr_like = rs2 == 0 && (bit12 == 1 || rdrs1 != 0);
1028 is_terminator = is_jr_like;
1029 target = None;
1030 } else {
1031 is_terminator = false;
1032 target = None;
1033 }
1034 } else {
1035 if pc + 4 > n {
1037 break;
1038 }
1039 inst_len = 4;
1040 let w = u32::from_le_bytes([code[pc], code[pc + 1], code[pc + 2], code[pc + 3]]);
1041 let opcode = w & 0x7F;
1042 let funct3 = (w >> 12) & 0x7;
1043 match opcode {
1044 OP_JAL => {
1045 let imm = imm_j(w);
1046 is_terminator = true;
1047 target = Some(pc as i64 + imm as i64);
1048 }
1049 OP_JALR => {
1050 is_terminator = true;
1054 target = None;
1055 }
1056 0b110_0011 => {
1057 let imm = imm_b(w);
1059 is_terminator = true;
1060 target = Some(pc as i64 + imm as i64);
1061 }
1062 OP_CUSTOM_0 => {
1063 is_terminator = true;
1066 target = None;
1067 let _ = funct3;
1068 }
1069 _ => {
1070 is_terminator = false;
1071 target = None;
1072 }
1073 }
1074 }
1075 let next_pc = pc + inst_len;
1076 if is_terminator && next_pc < n {
1077 post_terminator.insert(next_pc);
1078 }
1079 if let Some(t) = target
1080 && t >= 0
1081 && (t as usize) < n
1082 {
1083 static_edges.push((pc, t as usize));
1084 }
1085 pc = next_pc;
1086 }
1087
1088 let inst_starts_set: std::collections::HashSet<usize> = inst_starts.iter().copied().collect();
1090 let mut needs_inject: std::collections::BTreeSet<usize> = std::collections::BTreeSet::new();
1091 for &(_, target) in &static_edges {
1092 if !post_terminator.contains(&target) && inst_starts_set.contains(&target) {
1093 needs_inject.insert(target);
1094 }
1095 }
1096 for &t in extra_targets {
1100 if t < n && !post_terminator.contains(&t) && inst_starts_set.contains(&t) {
1101 needs_inject.insert(t);
1102 }
1103 }
1104
1105 if needs_inject.is_empty() {
1106 let identity: BTreeMap<usize, usize> = inst_starts.into_iter().map(|p| (p, p)).collect();
1108 return Ok(identity);
1109 }
1110
1111 let new_len = n + needs_inject.len() * 4;
1113 let mut new_code: Vec<u8> = Vec::with_capacity(new_len);
1114 let mut offset_map: BTreeMap<usize, usize> = BTreeMap::new();
1117 let fallthrough_word = encode_custom0_fallthrough();
1118 let fallthrough_bytes = fallthrough_word.to_le_bytes();
1119
1120 let mut next_inject_iter = needs_inject.iter().peekable();
1121 let mut old_idx = 0;
1122 while old_idx < inst_starts.len() {
1123 let old_pc = inst_starts[old_idx];
1124 while let Some(&&inject_pc) = next_inject_iter.peek() {
1126 if inject_pc == old_pc {
1127 new_code.extend_from_slice(&fallthrough_bytes);
1128 next_inject_iter.next();
1129 } else {
1130 break;
1131 }
1132 }
1133 offset_map.insert(old_pc, new_code.len());
1134 let next_pc = inst_starts.get(old_idx + 1).copied().unwrap_or(n);
1135 let inst_len = next_pc - old_pc;
1136 new_code.extend_from_slice(&code[old_pc..old_pc + inst_len]);
1137 old_idx += 1;
1138 }
1139
1140 for &old_pc in &inst_starts {
1144 let new_pc = offset_map[&old_pc];
1145 let lo = u16::from_le_bytes([new_code[new_pc], new_code[new_pc + 1]]);
1146 if lo & 0b11 != 0b11 {
1147 let op = lo & 0b11;
1149 let f3 = (lo >> 13) & 0b111;
1150 if op == 0b01 && f3 == 0b101 {
1151 let old_imm = decompress_cj_imm(lo);
1152 let old_target = (old_pc as i64 + old_imm as i64) as usize;
1153 let new_target = *offset_map.get(&old_target).ok_or_else(|| {
1154 LinkError::InvalidSection(format!(
1155 "align_branch_targets: c.j old target {:#x} not in offset_map",
1156 old_target
1157 ))
1158 })?;
1159 let new_imm = new_target as i64 - new_pc as i64;
1160 if new_imm != old_imm as i64 {
1161 let new_h = encode_cj_imm(lo, new_imm as i32).ok_or_else(|| {
1162 LinkError::InvalidSection(format!(
1163 "align_branch_targets: c.j at new_pc {:#x} new_imm {} \
1164 out of ±2 KiB range",
1165 new_pc, new_imm
1166 ))
1167 })?;
1168 new_code[new_pc..new_pc + 2].copy_from_slice(&new_h.to_le_bytes());
1169 }
1170 } else if op == 0b01 && (f3 == 0b110 || f3 == 0b111) {
1171 let old_imm = decompress_cb_imm(lo);
1172 let old_target = (old_pc as i64 + old_imm as i64) as usize;
1173 let new_target = *offset_map.get(&old_target).ok_or_else(|| {
1174 LinkError::InvalidSection(format!(
1175 "align_branch_targets: c.beqz/c.bnez old target {:#x} not in offset_map",
1176 old_target
1177 ))
1178 })?;
1179 let new_imm = new_target as i64 - new_pc as i64;
1180 if new_imm != old_imm as i64 {
1181 let new_h = encode_cb_imm(lo, new_imm as i32).ok_or_else(|| {
1182 LinkError::InvalidSection(format!(
1183 "align_branch_targets: c.beqz/c.bnez at new_pc {:#x} new_imm {} \
1184 out of ±256 byte range",
1185 new_pc, new_imm
1186 ))
1187 })?;
1188 new_code[new_pc..new_pc + 2].copy_from_slice(&new_h.to_le_bytes());
1189 }
1190 }
1191 } else {
1192 let w = u32::from_le_bytes([
1193 new_code[new_pc],
1194 new_code[new_pc + 1],
1195 new_code[new_pc + 2],
1196 new_code[new_pc + 3],
1197 ]);
1198 let opcode = w & 0x7F;
1199 match opcode {
1200 OP_JAL => {
1201 let old_imm = imm_j(w);
1202 let old_target = (old_pc as i64 + old_imm as i64) as usize;
1203 if let Some(&new_target) = offset_map.get(&old_target) {
1204 let new_imm = new_target as i64 - new_pc as i64;
1205 if !(-(1 << 20)..(1 << 20)).contains(&new_imm) {
1206 return Err(LinkError::InvalidSection(format!(
1207 "align_branch_targets: JAL at new_pc {:#x} out of ±1 MiB \
1208 range after injection (new_imm = {})",
1209 new_pc, new_imm
1210 )));
1211 }
1212 let rd = (w >> 7) & 0x1F;
1213 let v = new_imm as u32;
1214 let b20 = (v >> 20) & 0x1;
1215 let b10_1 = (v >> 1) & 0x3FF;
1216 let b11 = (v >> 11) & 0x1;
1217 let b19_12 = (v >> 12) & 0xFF;
1218 let imm_field = (b20 << 31) | (b10_1 << 21) | (b11 << 20) | (b19_12 << 12);
1219 let new_w = imm_field | (rd << 7) | OP_JAL;
1220 new_code[new_pc..new_pc + 4].copy_from_slice(&new_w.to_le_bytes());
1221 }
1222 }
1223 0b110_0011 => {
1224 let old_imm = imm_b(w);
1225 let old_target = (old_pc as i64 + old_imm as i64) as usize;
1226 if let Some(&new_target) = offset_map.get(&old_target) {
1227 let new_imm = new_target as i64 - new_pc as i64;
1228 if !(-(1 << 12)..(1 << 12)).contains(&new_imm) {
1229 return Err(LinkError::InvalidSection(format!(
1230 "align_branch_targets: B-type branch at new_pc {:#x} out of ±4 KiB \
1231 range after injection (new_imm = {})",
1232 new_pc, new_imm
1233 )));
1234 }
1235 let new_w = encode_b_imm(w, new_imm as i32);
1236 new_code[new_pc..new_pc + 4].copy_from_slice(&new_w.to_le_bytes());
1237 }
1238 }
1239 _ => {}
1240 }
1241 }
1242 }
1243
1244 *code = new_code;
1245 Ok(offset_map)
1246}
1247
1248fn decompress_cj_imm(h: u16) -> i32 {
1251 let h = h as u32;
1252 let b11 = (h >> 12) & 0x1;
1253 let b4 = (h >> 11) & 0x1;
1254 let b9_8 = (h >> 9) & 0x3;
1255 let b10 = (h >> 8) & 0x1;
1256 let b6 = (h >> 7) & 0x1;
1257 let b7 = (h >> 6) & 0x1;
1258 let b3_1 = (h >> 3) & 0x7;
1259 let b5 = (h >> 2) & 0x1;
1260 let raw = (b11 << 11)
1261 | (b10 << 10)
1262 | (b9_8 << 8)
1263 | (b7 << 7)
1264 | (b6 << 6)
1265 | (b5 << 5)
1266 | (b4 << 4)
1267 | (b3_1 << 1);
1268 ((raw as i32) << 20) >> 20
1269}
1270
1271fn decompress_cb_imm(h: u16) -> i32 {
1273 let h = h as u32;
1274 let b8 = (h >> 12) & 0x1;
1275 let b4_3 = (h >> 10) & 0x3;
1276 let b7_6 = (h >> 5) & 0x3;
1277 let b2_1 = (h >> 3) & 0x3;
1278 let b5 = (h >> 2) & 0x1;
1279 let raw = (b8 << 8) | (b7_6 << 6) | (b5 << 5) | (b4_3 << 3) | (b2_1 << 1);
1280 ((raw as i32) << 23) >> 23
1281}
1282
1283fn encode_cb_imm(h: u16, imm: i32) -> Option<u16> {
1287 if !(-(1 << 8)..(1 << 8)).contains(&imm) {
1288 return None;
1289 }
1290 if imm & 1 != 0 {
1291 return None;
1292 }
1293 let v = imm as u32;
1294 let b8 = (v >> 8) & 0x1;
1295 let b7_6 = (v >> 6) & 0x3;
1296 let b5 = (v >> 5) & 0x1;
1297 let b4_3 = (v >> 3) & 0x3;
1298 let b2_1 = (v >> 1) & 0x3;
1299 let preserved = (h as u32) & 0b1110_0011_1000_0011;
1301 let new_imm = (b8 << 12) | (b4_3 << 10) | (b7_6 << 5) | (b2_1 << 3) | (b5 << 2);
1302 Some((preserved | new_imm) as u16)
1303}
1304
1305fn encode_cj_imm(h: u16, imm: i32) -> Option<u16> {
1308 if !(-(1 << 11)..(1 << 11)).contains(&imm) {
1309 return None;
1310 }
1311 if imm & 1 != 0 {
1312 return None;
1313 }
1314 let v = imm as u32;
1315 let b11 = (v >> 11) & 0x1;
1316 let b10 = (v >> 10) & 0x1;
1317 let b9_8 = (v >> 8) & 0x3;
1318 let b7 = (v >> 7) & 0x1;
1319 let b6 = (v >> 6) & 0x1;
1320 let b5 = (v >> 5) & 0x1;
1321 let b4 = (v >> 4) & 0x1;
1322 let b3_1 = (v >> 1) & 0x7;
1323 let preserved = (h as u32) & 0b1110_0000_0000_0011;
1325 let new_imm = (b11 << 12)
1326 | (b4 << 11)
1327 | (b9_8 << 9)
1328 | (b10 << 8)
1329 | (b6 << 7)
1330 | (b7 << 6)
1331 | (b3_1 << 3)
1332 | (b5 << 2);
1333 Some((preserved | new_imm) as u16)
1334}
1335
1336#[cfg(test)]
1337mod tests {
1338 use super::*;
1339
1340 #[test]
1341 fn nop_encoding_matches_addi_x0_x0_0() {
1342 let w = u32::from_le_bytes(NOP_BYTES);
1343 assert_eq!(w & 0x7F, OP_OP_IMM, "opcode must be OP-IMM");
1344 assert_eq!((w >> 7) & 0x1F, 0, "rd must be x0");
1345 assert_eq!((w >> 15) & 0x1F, 0, "rs1 must be x0");
1346 assert_eq!((w >> 20) & 0xFFF, 0, "imm must be 0");
1347 assert_eq!((w >> 12) & 0x7, 0, "funct3 must be 0 (ADDI)");
1348 }
1349
1350 #[test]
1351 fn custom0_ecall_jar_decodes() {
1352 let w = encode_custom0_ecall_jar();
1353 assert_eq!(w & 0x7F, OP_CUSTOM_0);
1354 assert_eq!((w >> 12) & 0x7, 0b001);
1355 assert_eq!((w >> 7) & 0x1F, 0);
1357 assert_eq!((w >> 15) & 0x1F, 0);
1358 }
1359
1360 #[test]
1361 fn custom0_ecalli_decodes() {
1362 let w = encode_custom0_ecalli(42);
1363 assert_eq!(w & 0x7F, OP_CUSTOM_0);
1364 assert_eq!((w >> 12) & 0x7, 0b010);
1365 assert_eq!((w >> 20) & 0xFFF, 42);
1366 }
1367
1368 #[test]
1369 fn custom0_fallthrough_decodes() {
1370 let w = encode_custom0_fallthrough();
1371 assert_eq!(w & 0x7F, OP_CUSTOM_0);
1372 assert_eq!((w >> 12) & 0x7, 0b100);
1373 }
1374
1375 #[test]
1376 fn cb_imm_round_trips() {
1377 let base = (0b110u16 << 13) | (0b01u16);
1380 for &imm in &[0, 2, -2, 4, -4, 128, -128, 254, -256] {
1381 let h = encode_cb_imm(base, imm).expect("in range");
1382 assert_eq!(
1383 decompress_cb_imm(h),
1384 imm,
1385 "round-trip failed for imm={}",
1386 imm
1387 );
1388 }
1389 assert!(encode_cb_imm(base, 256).is_none());
1390 assert!(encode_cb_imm(base, -258).is_none());
1391 }
1392
1393 #[test]
1394 fn cj_imm_round_trips() {
1395 let base = (0b101u16 << 13) | (0b01u16);
1397 for &imm in &[0, 2, -2, 4, -4, 512, -512, 2046, -2048] {
1398 let h = encode_cj_imm(base, imm).expect("in range");
1399 assert_eq!(
1400 decompress_cj_imm(h),
1401 imm,
1402 "round-trip failed for imm={}",
1403 imm
1404 );
1405 }
1406 assert!(encode_cj_imm(base, 2048).is_none());
1407 }
1408
1409 #[test]
1410 fn rewrite_ecall_marker_jar() {
1411 let csrrw = (0x800u32 << 20) | (0b001 << 12) | OP_SYSTEM;
1413 let ecall: u32 = 0x0000_0073;
1414 let mut code = Vec::new();
1415 code.extend_from_slice(&csrrw.to_le_bytes());
1416 code.extend_from_slice(&ecall.to_le_bytes());
1417 rewrite_ecall_markers(&mut code).unwrap();
1418 let w0 = u32::from_le_bytes(code[0..4].try_into().unwrap());
1419 assert_eq!(w0, u32::from_le_bytes(NOP_BYTES));
1420 let w1 = u32::from_le_bytes(code[4..8].try_into().unwrap());
1421 assert_eq!(w1, encode_custom0_ecall_jar());
1422 }
1423
1424 #[test]
1425 fn rewrite_ecall_marker_ecalli() {
1426 let csrrw = (0x801u32 << 20) | (0b001 << 12) | OP_SYSTEM;
1427 let ecall: u32 = 0x0000_0073;
1428 let mut code = Vec::new();
1429 code.extend_from_slice(&csrrw.to_le_bytes());
1430 code.extend_from_slice(&ecall.to_le_bytes());
1431 rewrite_ecall_markers(&mut code).unwrap();
1432 let w1 = u32::from_le_bytes(code[4..8].try_into().unwrap());
1433 assert_eq!(w1, encode_custom0_ecalli(0));
1434 }
1435
1436 #[test]
1437 fn validate_accepts_auipc_and_jalr() {
1438 let auipc = (0x1000u32 << 12) | (1 << 7) | OP_AUIPC; validate_pvm2(&auipc.to_le_bytes()).unwrap();
1443 let jalr = (1u32 << 15) | (1 << 7) | OP_JALR; validate_pvm2(&jalr.to_le_bytes()).unwrap();
1445 let jalr_x3 = (3u32 << 15) | (1 << 7) | OP_JALR; assert!(validate_pvm2(&jalr_x3.to_le_bytes()).is_err());
1449 }
1450
1451 #[test]
1452 fn validate_rejects_standard_ecall() {
1453 let code = 0x0000_0073u32.to_le_bytes().to_vec();
1454 let err = validate_pvm2(&code).unwrap_err();
1455 assert!(matches!(err, LinkError::InvalidSection(_)));
1456 }
1457
1458 #[test]
1459 fn validate_rejects_producer_x3_use() {
1460 let w = (3u32 << 7) | OP_OP_IMM;
1462 let code = w.to_le_bytes().to_vec();
1463 let err = validate_pvm2(&code).unwrap_err();
1464 let LinkError::InvalidSection(msg) = err else {
1465 panic!();
1466 };
1467 assert!(msg.contains("x3"));
1468 }
1469
1470 #[test]
1471 fn validate_accepts_clean_addi() {
1472 let w = (5u32 << 20) | (1 << 7) | OP_OP_IMM;
1474 let code = w.to_le_bytes().to_vec();
1475 validate_pvm2(&code).unwrap();
1476 }
1477
1478 #[test]
1479 fn validate_accepts_rvc() {
1480 let cli = 0x4515u16;
1482 let code = cli.to_le_bytes().to_vec();
1483 validate_pvm2(&code).unwrap();
1484 }
1485}