javm_transpiler/riscv.rs
1//! RISC-V instruction decoder and PVM instruction translator.
2//!
3//! Decodes rv32em/rv64em instructions and translates them to equivalent
4//! PVM bytecode sequences.
5
6use crate::TranspileError;
7
8/// RISC-V register to PVM register mapping.
9///
10/// RISC-V has 16 registers in the `e` (embedded) ABI:
11/// x0 (zero), x1 (ra), x2 (sp), x3 (gp), x4 (tp),
12/// x5 (t0), x6 (t1), x7 (t2), x8 (s0), x9 (s1),
13/// x10 (a0), x11 (a1), x12 (a2), x13 (a3), x14 (a4), x15 (a5)
14///
15/// PVM has 13 registers (0-12):
16/// 0=RA, 1=SP, 2=T0, 3=T1, 4=T2, 5=S0, 6=S1,
17/// 7=A0, 8=A1, 9=A2, 10=A3, 11=A4, 12=A5
18///
19/// Mapping: x0 → zero (special), x1 → 0, x2 → 1, x5-x15 → 2-12
20/// x3 (gp) and x4 (tp) have no direct mapping and must be spilled.
21fn map_register(rv_reg: u8) -> Result<Option<u8>, TranspileError> {
22 match rv_reg {
23 0 => Ok(None), // x0 = zero register (always 0)
24 1 => Ok(Some(0)), // x1 (ra) → PVM reg 0 (RA)
25 2 => Ok(Some(1)), // x2 (sp) → PVM reg 1 (SP)
26 3 | 4 => Err(TranspileError::RegisterMapping(rv_reg)), // gp, tp: no mapping
27 5 => Ok(Some(2)), // x5 (t0) → PVM reg 2 (T0)
28 6 => Ok(Some(3)), // x6 (t1) → PVM reg 3 (T1)
29 7 => Ok(Some(4)), // x7 (t2) → PVM reg 4 (T2)
30 8 => Ok(Some(5)), // x8 (s0) → PVM reg 5 (S0)
31 9 => Ok(Some(6)), // x9 (s1) → PVM reg 6 (S1)
32 10 => Ok(Some(7)), // x10 (a0) → PVM reg 7 (A0)
33 11 => Ok(Some(8)), // x11 (a1) → PVM reg 8 (A1)
34 12 => Ok(Some(9)), // x12 (a2) → PVM reg 9 (A2)
35 13 => Ok(Some(10)), // x13 (a3) → PVM reg 10 (A3)
36 14 => Ok(Some(11)), // x14 (a4) → PVM reg 11 (A4)
37 15 => Ok(Some(12)), // x15 (a5) → PVM reg 12 (A5)
38 _ => Err(TranspileError::RegisterMapping(rv_reg)),
39 }
40}
41
42/// Determine the minimum byte width for encoding a signed immediate in PVM format.
43///
44/// Returns `(lx, bytes)` where `lx` is the byte count (0, 1, 2, or 4) and `bytes`
45/// are the little-endian encoded value.
46fn encode_var_imm(imm: i32) -> (u8, Vec<u8>) {
47 if imm == 0 {
48 (0, vec![])
49 } else if (-128..=127).contains(&imm) {
50 (1, vec![imm as i8 as u8])
51 } else if (-32768..=32767).contains(&imm) {
52 (2, (imm as i16).to_le_bytes().to_vec())
53 } else {
54 (4, imm.to_le_bytes().to_vec())
55 }
56}
57
58/// Translation context for converting RISC-V to PVM.
59pub struct TranslationContext {
60 /// Emitted PVM code bytes.
61 pub code: Vec<u8>,
62 /// Bitmask: 1 for instruction start, 0 for continuation.
63 pub bitmask: Vec<u8>,
64 /// Jump table entries.
65 pub jump_table: Vec<u32>,
66 /// Whether translating 64-bit RISC-V.
67 pub is_64bit: bool,
68 /// Map from RISC-V address to PVM code offset.
69 pub address_map: std::collections::HashMap<u64, u32>,
70 /// Pending branch fixups: (pvm_imm_offset, target_rv_address, fixup_size)
71 fixups: Vec<(usize, u64, u8)>,
72 /// Map from fixup imm offset → instruction PC (for PC-relative encoding)
73 fixup_pcs: std::collections::HashMap<usize, u32>,
74 /// Return-address fixups: (jump_table_index, risc-v return address).
75 /// Resolved during `apply_fixups` to patch jump table entries.
76 pub(crate) return_fixups: Vec<(usize, u64)>,
77 /// Pending AUIPC: (rd, computed_address). Used to pair with the next JALR.
78 pending_auipc: Option<(u8, u64)>,
79 /// Pending LUI: (rd, upper_imm). Used to fuse LUI+ADDI into single load_imm.
80 pending_lui: Option<(u8, i64)>,
81 /// Last emitted load_imm: (rd, value, code_position_before_emit).
82 /// Enables fusion with a subsequent ADD/AND/OR/XOR/load/store into the
83 /// immediate form, eliminating the load_imm instruction entirely.
84 pub(crate) pending_load_imm: Option<(u8, i64, usize)>,
85 /// Last immediate loaded into t0 (x5) — used for ecalli cap slot.
86 last_t0_imm: Option<i32>,
87 /// CSR marker for ecall/ecalli distinction.
88 /// 0x800 = next ecall → PVM ecall, 0x801 = next ecall → PVM ecalli.
89 ecall_marker: Option<u32>,
90 /// RISC-V code address ranges (lo, hi) — used to detect function pointers
91 /// loaded via auipc+addi that need jump table entries.
92 pub code_ranges: Vec<(u64, u64)>,
93}
94
95impl TranslationContext {
96 pub fn new(is_64bit: bool) -> Self {
97 Self {
98 code: Vec::new(),
99 bitmask: Vec::new(),
100 jump_table: Vec::new(),
101 is_64bit,
102 address_map: std::collections::HashMap::new(),
103 fixups: Vec::new(),
104 fixup_pcs: std::collections::HashMap::new(),
105 return_fixups: Vec::new(),
106 pending_auipc: None,
107 pending_lui: None,
108 pending_load_imm: None,
109 last_t0_imm: None,
110 ecall_marker: None,
111 code_ranges: Vec::new(),
112 }
113 }
114
115 /// Flush any pending buffered instructions (LUI, AUIPC) at section boundaries.
116 pub(crate) fn flush_pending(&mut self) -> Result<(), TranspileError> {
117 if let Some((auipc_rd, auipc_val)) = self.pending_auipc.take() {
118 self.emit_load_imm(auipc_rd, auipc_val as i64)?;
119 }
120 if let Some((lui_rd, lui_val)) = self.pending_lui.take() {
121 self.emit_load_imm(lui_rd, lui_val)?;
122 }
123 // pending_load_imm is already emitted — just clear the tracking
124 if self.pending_load_imm.is_some() {
125 self.pending_load_imm = None;
126 }
127 Ok(())
128 }
129
130 /// Translate one or more 32-bit RISC-V instructions starting at `offset`.
131 /// Returns the number of bytes consumed (always 4).
132 pub(crate) fn translate_instruction(
133 &mut self,
134 section: &[u8],
135 offset: usize,
136 base: u64,
137 ) -> Result<usize, TranspileError> {
138 let inst = u32::from_le_bytes([
139 section[offset],
140 section[offset + 1],
141 section[offset + 2],
142 section[offset + 3],
143 ]);
144 let addr = base + offset as u64;
145 self.translate_one(inst, addr)?;
146 Ok(4)
147 }
148
149 /// Translate a single 32-bit RISC-V instruction.
150 fn translate_one(&mut self, inst: u32, _addr: u64) -> Result<(), TranspileError> {
151 let opcode = inst & 0x7F;
152 let rd = ((inst >> 7) & 0x1F) as u8;
153 let funct3 = (inst >> 12) & 0x7;
154 let rs1 = ((inst >> 15) & 0x1F) as u8;
155 let rs2 = ((inst >> 20) & 0x1F) as u8;
156 let funct7 = (inst >> 25) & 0x7F;
157
158 // Flush pending auipc if this isn't a JALR or OP-IMM (ADDI) that consumes it.
159 if opcode != 0x67
160 && opcode != 0x13
161 && let Some((auipc_rd, auipc_val)) = self.pending_auipc.take()
162 {
163 self.emit_load_imm(auipc_rd, auipc_val as i64)?;
164 }
165
166 // Flush pending LUI if this isn't an OP-IMM (ADDI) that consumes it.
167 if opcode != 0x13
168 && let Some((lui_rd, lui_val)) = self.pending_lui.take()
169 {
170 self.emit_load_imm(lui_rd, lui_val)?;
171 }
172
173 // Clear pending_load_imm if this isn't an instruction that can consume it.
174 // OP (0x33), OP-32 (0x3B), Branch (0x63), Store (0x23), and Load (0x03)
175 // handlers check and potentially fuse.
176 if opcode != 0x33 && opcode != 0x3B && opcode != 0x63 && opcode != 0x23 && opcode != 0x03 {
177 self.pending_load_imm = None; // already emitted, just clear tracking
178 }
179
180 match opcode {
181 0x37 => {
182 // LUI — buffer for potential LUI+ADDI fusion
183 let imm = (inst & 0xFFFFF000) as i32;
184 // Flush any previous pending LUI (consecutive LUIs)
185 if let Some((prev_rd, prev_val)) = self.pending_lui.take() {
186 self.emit_load_imm(prev_rd, prev_val)?;
187 }
188 self.pending_lui = Some((rd, imm as i64));
189 }
190 0x17 => {
191 // AUIPC — PC + upper immediate
192 let imm = (inst & 0xFFFFF000) as i32;
193 let computed = (_addr as i64 + imm as i64) as u64;
194 // Record for pairing with the next JALR instruction.
195 // Don't emit anything yet — the JALR handler will use this.
196 self.pending_auipc = Some((rd, computed));
197 }
198 0x6F => {
199 // JAL
200 let imm = decode_j_imm(inst);
201 let target = (_addr as i64 + imm as i64) as u64;
202 if rd == 0 {
203 // Plain jump (tail call / goto)
204 self.emit_jump(target);
205 } else {
206 // Function call: fused load_imm_jump (opcode 80)
207 let rv_return_addr = _addr + 4;
208 self.emit_call(rd, rv_return_addr, target)?;
209 }
210 }
211 0x67 => {
212 // JALR
213 match funct3 {
214 0 => {
215 let imm = (inst as i32) >> 20;
216 self.translate_jalr(rd, rs1, imm, _addr)?;
217 }
218 _ => {
219 return Err(TranspileError::UnsupportedInstruction {
220 offset: _addr as usize,
221 detail: format!("JALR funct3={}", funct3),
222 });
223 }
224 }
225 }
226 0x63 => {
227 // Branch
228 let imm = decode_b_imm(inst);
229 let target = (_addr as i64 + imm as i64) as u64;
230 self.translate_branch(funct3, rs1, rs2, target)?;
231 }
232 0x03 => {
233 // Load
234 let imm = (inst as i32) >> 20;
235 self.translate_load(funct3, rd, rs1, imm)?;
236 }
237 0x23 => {
238 // Store
239 let imm = decode_s_imm(inst);
240 self.translate_store(funct3, rs1, rs2, imm)?;
241 }
242 0x13 => {
243 // OP-IMM (add_i, xor_i, etc.)
244 let imm = (inst as i32) >> 20;
245 self.translate_op_imm(funct3, funct7, rd, rs1, imm)?;
246 }
247 0x33 => {
248 // OP (add, sub, mul, etc.)
249 self.translate_op(funct3, funct7, rd, rs1, rs2, _addr)?;
250 }
251 0x1B => {
252 // OP-IMM-32 (addiw, slliw, etc.) — RV64 only
253 let imm = (inst as i32) >> 20;
254 self.translate_op_imm_32(funct3, funct7, rd, rs1, imm)?;
255 }
256 0x3B => {
257 // OP-32 (addw, subw, etc.) — RV64 only
258 self.translate_op_32(funct3, funct7, rd, rs1, rs2)?;
259 }
260 0x73 => {
261 // SYSTEM
262 match funct3 {
263 0 => {
264 let csr = (inst >> 20) & 0xFFF;
265 match csr {
266 0 => {
267 // ECALL — dispatch based on CSR marker
268 match self.ecall_marker.take() {
269 Some(0x800) => {
270 // CSR 0x800 → PVM ecall (management ops)
271 self.emit_ecall();
272 }
273 Some(0x801) => {
274 // CSR 0x801 → PVM ecalli (CALL a cap)
275 let id = self.last_t0_imm.unwrap_or(0) as u32;
276 self.emit_ecalli(id);
277 self.last_t0_imm = None;
278 }
279 _ => {
280 // No marker (legacy) — treat as ecalli for backward compat
281 let id = self.last_t0_imm.unwrap_or(0) as u32;
282 self.emit_ecalli(id);
283 self.last_t0_imm = None;
284 }
285 }
286 }
287 1 => self.emit_inst(0), // EBREAK → trap
288 _ => self.emit_inst(0), // unimp/unknown CSR → trap
289 }
290 }
291 1 => {
292 // CSRRW: check for custom markers 0x800, 0x801
293 let csr = (inst >> 20) & 0xFFF;
294 match csr {
295 0x800 | 0x801 => {
296 // Custom CSR marker for ecall/ecalli distinction
297 self.ecall_marker = Some(csr);
298 // Don't emit any PVM instruction — marker consumed on next ecall
299 }
300 _ => self.emit_inst(0), // unknown CSR → trap
301 }
302 }
303 _ => self.emit_inst(0), // other CSR ops → trap
304 }
305 }
306 0x0F => {
307 // FENCE
308 self.emit_inst(1); // → fallthrough (nop)
309 }
310 0x0B => {
311 // CUSTOM-0 — T-Head extensions
312 match (funct7, funct3) {
313 (0x20, 1) => {
314 // th.mveqz rd, rs1, rs2: if rs2 == 0 then rd = rs1
315 if rd == 0 { /* nop */
316 } else if rs2 == 0 {
317 // Condition is x0 (always 0) → always execute: rd = rs1
318 if rs1 == 0 {
319 self.emit_load_imm(rd, 0)?;
320 } else if rd == rs1 {
321 // Self-move is a nop
322 self.emit_inst(1); // fallthrough
323 } else {
324 let pvm_rd = self.require_reg(rd)?;
325 let pvm_rs1 = self.require_reg(rs1)?;
326 self.emit_inst(100); // move_reg
327 self.emit_data(pvm_rd | (pvm_rs1 << 4));
328 }
329 } else if rs1 == 0 {
330 // Source is x0 (always 0): if rs2 == 0 then rd = 0
331 let pvm_rd = self.require_reg(rd)?;
332 let pvm_rs2 = self.require_reg(rs2)?;
333 self.emit_inst(147); // CmovIzImm
334 self.emit_data(pvm_rd | (pvm_rs2 << 4));
335 self.emit_var_imm(0);
336 } else {
337 let pvm_rd = self.require_reg(rd)?;
338 let pvm_rs1 = self.require_reg(rs1)?;
339 let pvm_rs2 = self.require_reg(rs2)?;
340 self.emit_inst(218); // CmovIz
341 self.emit_data(pvm_rs1 | (pvm_rs2 << 4));
342 self.emit_data(pvm_rd);
343 }
344 }
345 (0x21, 1) => {
346 // th.mvnez rd, rs1, rs2: if rs2 != 0 then rd = rs1
347 if rd == 0 || rs2 == 0 {
348 // rd==0: nop. rs2==x0: condition "x0 != 0" is always false → nop
349 self.emit_inst(1); // fallthrough
350 } else if rs1 == 0 {
351 let pvm_rd = self.require_reg(rd)?;
352 let pvm_rs2 = self.require_reg(rs2)?;
353 self.emit_inst(148); // CmovNzImm
354 self.emit_data(pvm_rd | (pvm_rs2 << 4));
355 self.emit_var_imm(0);
356 } else {
357 let pvm_rd = self.require_reg(rd)?;
358 let pvm_rs1 = self.require_reg(rs1)?;
359 let pvm_rs2 = self.require_reg(rs2)?;
360 self.emit_inst(219); // CmovNz
361 self.emit_data(pvm_rs1 | (pvm_rs2 << 4));
362 self.emit_data(pvm_rd);
363 }
364 }
365 _ => {
366 return Err(TranspileError::UnsupportedInstruction {
367 offset: _addr as usize,
368 detail: format!("custom-0 funct7={:#x} funct3={}", funct7, funct3),
369 });
370 }
371 }
372 }
373 _ => {
374 return Err(TranspileError::UnsupportedInstruction {
375 offset: _addr as usize,
376 detail: format!("unknown opcode {:#x}", opcode),
377 });
378 }
379 }
380
381 Ok(())
382 }
383
384 fn translate_jalr(
385 &mut self,
386 rd: u8,
387 rs1: u8,
388 imm: i32,
389 addr: u64,
390 ) -> Result<(), TranspileError> {
391 // Check for auipc+jalr pair (PC-relative call/jump)
392 if let Some((auipc_rd, auipc_val)) = self.pending_auipc.take() {
393 if auipc_rd == rs1 {
394 // Combined auipc+jalr: target = auipc_val + imm
395 let target = (auipc_val as i64 + imm as i64) as u64;
396 if rd == 0 {
397 // Tail call: just jump, no return address
398 self.emit_jump(target);
399 } else {
400 // Function call: fused load_imm_jump (opcode 80)
401 let rv_return_addr = addr + 4;
402 self.emit_call(rd, rv_return_addr, target)?;
403 }
404 return Ok(());
405 } else {
406 // auipc targeted a different register — emit it as load_imm
407 self.emit_load_imm(auipc_rd, auipc_val as i64)?;
408 }
409 }
410
411 // Plain JALR (no preceding auipc, or auipc was for different reg)
412 if rd == 0 {
413 // Tail call or return: jump_ind without saving return address.
414 // Handles ret (rs1=ra, imm=0) and tail calls through any register.
415 let pvm_rs1 = self.require_reg(rs1)?;
416 self.emit_inst(50); // jump_ind
417 self.emit_data(pvm_rs1);
418 self.emit_var_imm(imm);
419 } else {
420 // Indirect call (e.g. vtable dispatch): save return address then jump.
421 // Use load_imm_jump_ind (opcode 180): rd = return_addr, jump via rs1+imm.
422 let rv_return_addr = addr + 4;
423 let jt_idx = self.jump_table.len();
424 self.jump_table.push(0); // placeholder
425 self.return_fixups.push((jt_idx, rv_return_addr));
426 let jt_addr = ((jt_idx + 1) * 2) as i32;
427
428 let pvm_rd = self.require_reg(rd)?;
429 let pvm_rs1 = self.require_reg(rs1)?;
430 let lx = Self::var_imm_byte_count(jt_addr);
431
432 self.emit_inst(180); // load_imm_jump_ind
433 self.emit_data(pvm_rd | (pvm_rs1 << 4)); // reg_byte: ra=rd, rb=rs1
434 self.emit_data(lx as u8); // lx: byte count for imm_x (return addr)
435 self.emit_var_imm(jt_addr); // imm_x: jump table return address
436 self.emit_var_imm(imm); // imm_y: offset added to rs1
437 }
438 Ok(())
439 }
440
441 fn translate_branch(
442 &mut self,
443 funct3: u32,
444 rs1: u8,
445 rs2: u8,
446 target: u64,
447 ) -> Result<(), TranspileError> {
448 // Fuse load_imm + branch: if one operand was just loaded via load_imm,
449 // use the immediate branch form instead of a two-register branch.
450 // Saves one PVM instruction (the load_imm) per fused branch.
451 if let Some((load_rd, load_val, undo_pos)) = self.pending_load_imm.take()
452 && load_val >= i32::MIN as i64
453 && load_val <= i32::MAX as i64
454 {
455 let imm = load_val as i32;
456 // Check if rs2 is the loaded register: branch_*_imm rs1, imm, target
457 if rs2 == load_rd && rs1 != load_rd {
458 let pvm_rs1 = self.require_reg(rs1)?;
459 let pvm_opcode = match funct3 {
460 0 => Some(81), // BEQ → branch_eq_imm
461 1 => Some(82), // BNE → branch_ne_imm
462 4 => Some(87), // BLT → branch_lt_s_imm
463 5 => Some(89), // BGE → branch_ge_s_imm
464 6 => Some(83), // BLTU → branch_lt_u_imm
465 7 => Some(85), // BGEU → branch_ge_u_imm
466 _ => None,
467 };
468 if let Some(opc) = pvm_opcode {
469 self.code.truncate(undo_pos);
470 self.bitmask.truncate(undo_pos);
471 self.emit_branch_imm(opc, pvm_rs1, imm, target);
472 return Ok(());
473 }
474 }
475 // Check if rs1 is the loaded register: flip the comparison
476 // BEQ/BNE are symmetric. BLT(rs1,rs2) with rs1=imm → BGE(rs2,imm+1) etc.
477 // Only handle symmetric cases (EQ, NE) to avoid off-by-one complexity.
478 if rs1 == load_rd && rs2 != load_rd {
479 let pvm_rs2 = self.require_reg(rs2)?;
480 let pvm_opcode = match funct3 {
481 0 => Some(81), // BEQ is symmetric → branch_eq_imm rs2, imm
482 1 => Some(82), // BNE is symmetric → branch_ne_imm rs2, imm
483 _ => None, // Inequalities need careful flipping, skip for now
484 };
485 if let Some(opc) = pvm_opcode {
486 self.code.truncate(undo_pos);
487 self.bitmask.truncate(undo_pos);
488 self.emit_branch_imm(opc, pvm_rs2, imm, target);
489 return Ok(());
490 }
491 }
492 }
493 // Couldn't fuse — load_imm was already emitted, just clear tracking
494
495 // When one operand is x0 (zero register), use immediate branch variants
496 // since PVM register 0 = RA, not zero.
497 if rs2 == 0 {
498 let pvm_rs1 = self.require_reg(rs1)?;
499 let pvm_opcode = match funct3 {
500 0 => 81, // BEQ x, x0 → branch_eq_imm x, 0
501 1 => 82, // BNE x, x0 → branch_ne_imm x, 0
502 4 => 87, // BLT x, x0 → branch_lt_s_imm x, 0
503 5 => 89, // BGE x, x0 → branch_ge_s_imm x, 0
504 6 => 83, // BLTU x, x0 → branch_lt_u_imm x, 0
505 7 => 85, // BGEU x, x0 → branch_ge_u_imm x, 0
506 _ => {
507 return Err(TranspileError::UnsupportedInstruction {
508 offset: 0,
509 detail: format!("branch funct3={}", funct3),
510 });
511 }
512 };
513 self.emit_branch_imm(pvm_opcode, pvm_rs1, 0, target);
514 return Ok(());
515 }
516
517 if rs1 == 0 {
518 // Compare x0 against rs2: flip the condition
519 let pvm_rs2 = self.require_reg(rs2)?;
520 match funct3 {
521 0 => self.emit_branch_imm(81, pvm_rs2, 0, target), // BEQ x0, y → branch_eq_imm y, 0
522 1 => self.emit_branch_imm(82, pvm_rs2, 0, target), // BNE x0, y → branch_ne_imm y, 0
523 4 => self.emit_branch_imm(89, pvm_rs2, 1, target), // BLT x0, rs2 → rs2 >= 1 (signed)
524 5 => self.emit_branch_imm(87, pvm_rs2, 1, target), // BGE x0, rs2 → rs2 < 1 (signed)
525 6 => self.emit_branch_imm(82, pvm_rs2, 0, target), // BLTU x0, rs2 → rs2 != 0
526 7 => self.emit_branch_imm(81, pvm_rs2, 0, target), // BGEU x0, rs2 → rs2 == 0
527 _ => {
528 return Err(TranspileError::UnsupportedInstruction {
529 offset: 0,
530 detail: format!("branch funct3={}", funct3),
531 });
532 }
533 };
534 return Ok(());
535 }
536
537 let pvm_rs1 = self.require_reg(rs1)?;
538 let pvm_rs2 = self.require_reg(rs2)?;
539
540 // Two register + one offset: opcodes 170-175
541 let pvm_opcode = match funct3 {
542 0 => 170, // BEQ → branch_eq
543 1 => 171, // BNE → branch_ne
544 4 => 173, // BLT → branch_lt_s
545 5 => 175, // BGE → branch_ge_s
546 6 => 172, // BLTU → branch_lt_u
547 7 => 174, // BGEU → branch_ge_u
548 _ => {
549 return Err(TranspileError::UnsupportedInstruction {
550 offset: 0,
551 detail: format!("branch funct3={}", funct3),
552 });
553 }
554 };
555
556 let inst_pc = self.code.len() as u32;
557 self.emit_inst(pvm_opcode);
558 self.emit_data(pvm_rs1 | (pvm_rs2 << 4));
559 // Fixup target offset (PC-relative)
560 let fixup_pos = self.code.len();
561 self.fixups.push((fixup_pos, target, 4));
562 self.fixup_pcs.insert(fixup_pos, inst_pc);
563 self.emit_imm32(0); // placeholder
564
565 Ok(())
566 }
567
568 pub(crate) fn translate_load(
569 &mut self,
570 funct3: u32,
571 rd: u8,
572 rs1: u8,
573 imm: i32,
574 ) -> Result<(), TranspileError> {
575 if rd == 0 {
576 return Ok(());
577 } // Write to x0 is a no-op
578
579 // Fuse load_imm + load_ind: if the base register was just loaded with
580 // a constant address, use the direct load form (OneRegOneImm) instead.
581 // Saves one PVM instruction per fused load.
582 if let Some((load_rd, load_val, undo_pos)) = self.pending_load_imm.take()
583 && rs1 == load_rd
584 {
585 let combined = load_val.wrapping_add(imm as i64);
586 if combined >= i32::MIN as i64 && combined <= i32::MAX as i64 {
587 let direct_opcode = match funct3 {
588 0 => Some(53), // LB → load_i8
589 1 => Some(55), // LH → load_i16
590 2 => Some(57), // LW → load_i32
591 3 => Some(58), // LD → load_u64
592 4 => Some(52), // LBU → load_u8
593 5 => Some(54), // LHU → load_u16
594 6 => Some(56), // LWU → load_u32
595 _ => None,
596 };
597 if let Some(opc) = direct_opcode {
598 // Only truncate the load_imm if the load destination overwrites
599 // the base register (rd == rs1). If rd != rs1, the base register
600 // value may be needed later (e.g., RISC-V switch table pattern:
601 // lw offset, table(base); add target, offset, base; jr target).
602 if rd == rs1 {
603 self.code.truncate(undo_pos);
604 self.bitmask.truncate(undo_pos);
605 }
606 let pvm_rd = self.require_reg(rd)?;
607 self.emit_inst(opc);
608 self.emit_data(pvm_rd);
609 self.emit_var_imm(combined as i32);
610 return Ok(());
611 }
612 }
613 }
614 // Couldn't fuse — load_imm already emitted, just proceed
615
616 let pvm_rd = self.require_reg(rd)?;
617 let pvm_rs1 = self.require_reg(rs1)?;
618
619 // Two register + one immediate: load_ind_*
620 let pvm_opcode = match funct3 {
621 0 => 125, // LB → load_ind_i8
622 1 => 127, // LH → load_ind_i16
623 2 => 129, // LW → load_ind_i32
624 3 => 130, // LD → load_ind_u64
625 4 => 124, // LBU → load_ind_u8
626 5 => 126, // LHU → load_ind_u16
627 6 => 128, // LWU → load_ind_u32
628 _ => {
629 return Err(TranspileError::UnsupportedInstruction {
630 offset: 0,
631 detail: format!("load funct3={}", funct3),
632 });
633 }
634 };
635
636 self.emit_inst(pvm_opcode);
637 self.emit_data(pvm_rd | (pvm_rs1 << 4));
638 self.emit_var_imm(imm);
639
640 Ok(())
641 }
642
643 pub(crate) fn translate_store(
644 &mut self,
645 funct3: u32,
646 rs1: u8,
647 rs2: u8,
648 imm: i32,
649 ) -> Result<(), TranspileError> {
650 // Fuse load_imm + store: check if the base address or stored value was constant.
651 if let Some((load_rd, load_val, undo_pos)) = self.pending_load_imm.take()
652 && load_val >= i32::MIN as i64
653 && load_val <= i32::MAX as i64
654 {
655 // Case 1: Base register was loaded with constant address → direct store.
656 // store_ind_* data, base, offset where base = constant addr
657 // → store_* data, (addr + offset)
658 if rs1 == load_rd && rs2 != load_rd && rs2 != 0 {
659 let combined = load_val.wrapping_add(imm as i64);
660 if combined >= i32::MIN as i64 && combined <= i32::MAX as i64 {
661 let direct_opcode = match funct3 {
662 0 => Some(59), // SB → store_u8
663 1 => Some(60), // SH → store_u16
664 2 => Some(61), // SW → store_u32
665 3 => Some(62), // SD → store_u64
666 _ => None,
667 };
668 if let Some(opc) = direct_opcode {
669 self.code.truncate(undo_pos);
670 self.bitmask.truncate(undo_pos);
671 let pvm_rs2 = self.require_reg(rs2)?;
672 self.emit_inst(opc);
673 self.emit_data(pvm_rs2);
674 self.emit_var_imm(combined as i32);
675 return Ok(());
676 }
677 }
678 }
679 // Case 2: Value register was loaded with constant → store_imm_ind.
680 // NOTE: We intentionally do NOT undo the load_imm here because
681 // the register may still be needed after the store (e.g., as a
682 // function argument). The load_imm was already emitted, so the
683 // register holds the correct value. Just emit a normal store.
684 // The load_imm + store costs two instructions instead of one
685 // fused store_imm_ind, but is always correct.
686 }
687 // Couldn't fuse — load_imm was already emitted, just clear tracking
688
689 // x0 (zero register) has no PVM equivalent — PVM reg 0 is RA, not zero.
690 // Use store_imm_ind_* to store a literal zero instead.
691 if rs2 == 0 {
692 let pvm_rs1 = self.require_reg(rs1)?;
693 let pvm_opcode = match funct3 {
694 0 => 70, // store_imm_ind_u8
695 1 => 71, // store_imm_ind_u16
696 2 => 72, // store_imm_ind_u32
697 3 => 73, // store_imm_ind_u64
698 _ => {
699 return Err(TranspileError::UnsupportedInstruction {
700 offset: 0,
701 detail: format!("store funct3={}", funct3),
702 });
703 }
704 };
705 // Format: OneRegTwoImm — reg_byte encodes ra + imm_x length
706 // reg_byte = ra | (lx << 4)
707 // imm_y has length 0, which decodes as 0 (the value we want to store)
708 let (lx, imm_bytes) = encode_var_imm(imm);
709 self.emit_inst(pvm_opcode);
710 self.emit_data(pvm_rs1 | (lx << 4));
711 for b in &imm_bytes {
712 self.emit_data(*b);
713 }
714 return Ok(());
715 }
716
717 let pvm_rs2 = self.require_reg(rs2)?; // data register → rD
718 let pvm_rs1 = self.require_reg(rs1)?; // base register → rA
719
720 let pvm_opcode = match funct3 {
721 0 => 120, // SB → store_ind_u8
722 1 => 121, // SH → store_ind_u16
723 2 => 122, // SW → store_ind_u32
724 3 => 123, // SD → store_ind_u64
725 _ => {
726 return Err(TranspileError::UnsupportedInstruction {
727 offset: 0,
728 detail: format!("store funct3={}", funct3),
729 });
730 }
731 };
732
733 self.emit_inst(pvm_opcode);
734 self.emit_data(pvm_rs2 | (pvm_rs1 << 4));
735 self.emit_var_imm(imm);
736
737 Ok(())
738 }
739
740 fn translate_op_imm(
741 &mut self,
742 funct3: u32,
743 funct7: u32,
744 rd: u8,
745 rs1: u8,
746 imm: i32,
747 ) -> Result<(), TranspileError> {
748 // Track `li t0, N` (ADDI x5, x0, N) for ecall ID translation
749 if funct3 == 0 && rd == 5 && rs1 == 0 {
750 self.last_t0_imm = Some(imm);
751 }
752
753 // AUIPC+ADDI fusion: compute full address. If it's a code address (function
754 // pointer), emit a jump table entry so indirect calls via djump work.
755 if let Some((auipc_rd, auipc_val)) = self.pending_auipc.take() {
756 if funct3 == 0 && rd == auipc_rd && rs1 == auipc_rd && rd != 0 {
757 let full_addr = (auipc_val as i64 + imm as i64) as u64;
758 if self.is_code_addr(full_addr) {
759 let jt_idx = self.jump_table.len();
760 self.jump_table.push(0); // placeholder, resolved by apply_fixups
761 self.return_fixups.push((jt_idx, full_addr));
762 let jt_addr = ((jt_idx + 1) * 2) as i64;
763 let pos = self.code.len();
764 self.emit_load_imm(rd, jt_addr)?;
765 self.pending_load_imm = Some((rd, jt_addr, pos));
766 } else {
767 let combined = auipc_val as i64 + imm as i64;
768 let pos = self.code.len();
769 self.emit_load_imm(rd, combined)?;
770 self.pending_load_imm = Some((rd, combined, pos));
771 }
772 return Ok(());
773 }
774 // Not a matching ADDI — flush the pending AUIPC
775 self.emit_load_imm(auipc_rd, auipc_val as i64)?;
776 }
777
778 // LUI+ADDI fusion: if there's a pending LUI and this is ADDI rd, rd, imm
779 // with the same register, fuse into a single load_imm with the combined value.
780 if let Some((lui_rd, lui_val)) = self.pending_lui.take() {
781 if funct3 == 0 && rd == lui_rd && rs1 == lui_rd && rd != 0 {
782 let combined = lui_val.wrapping_add(imm as i64);
783 // Track for potential fusion with subsequent ALU op
784 let pos = self.code.len();
785 self.emit_load_imm(rd, combined)?;
786 self.pending_load_imm = Some((rd, combined, pos));
787 return Ok(());
788 }
789 // Not a matching ADDI — flush the pending LUI
790 self.emit_load_imm(lui_rd, lui_val)?;
791 }
792
793 if rd == 0 {
794 return Ok(());
795 } // Write to x0 is a no-op in RISC-V
796
797 // ADDI rd, rs, 0 is the RISC-V `mv rd, rs` pseudo-instruction.
798 // Use compact move_reg (2 bytes) instead of add_imm (6 bytes).
799 if funct3 == 0 && imm == 0 && rs1 != 0 {
800 if rd == rs1 {
801 // ADDI rd, rd, 0 is a NOP
802 self.emit_inst(1); // fallthrough
803 return Ok(());
804 }
805 let pvm_rd = self.require_reg(rd)?;
806 let pvm_rs1 = self.require_reg(rs1)?;
807 self.emit_inst(100); // move_reg
808 self.emit_data(pvm_rd | (pvm_rs1 << 4));
809 return Ok(());
810 }
811
812 // When rs1 = x0 (zero register), treat as loading immediate directly
813 // because PVM has no zero register — x0 maps to RA which is NOT zero.
814 if rs1 == 0 {
815 match funct3 {
816 0 => return self.emit_load_imm(rd, imm as i64), // li rd, imm
817 2 => {
818 // SLTI rd, x0, imm → rd = (0 < imm) ? 1 : 0
819 return self.emit_load_imm(rd, if 0 < imm { 1 } else { 0 });
820 }
821 3 => {
822 // SLTIU rd, x0, imm → rd = (0 < imm unsigned) ? 1 : 0
823 return self.emit_load_imm(rd, if imm != 0 { 1 } else { 0 });
824 }
825 4 => return self.emit_load_imm(rd, imm as i64), // XORI rd, x0, imm = imm
826 6 => return self.emit_load_imm(rd, imm as i64), // ORI rd, x0, imm = imm
827 7 => return self.emit_load_imm(rd, 0), // ANDI rd, x0, imm = 0
828 _ => {} // shifts with x0 → just 0, but rare
829 }
830 }
831
832 let pvm_rd = self.require_reg(rd)?;
833 let pvm_rs1 = self.require_reg(rs1)?;
834
835 // RV32 uses 32-bit PVM ops; RV64 uses 64-bit PVM ops
836 let pvm_opcode = match funct3 {
837 0 => {
838 if self.is_64bit {
839 149
840 } else {
841 131
842 }
843 } // ADDI → add_imm_64/32
844 1 => {
845 if funct7 == 0x30 {
846 // Zbb unary: clz/ctz/cpop/sext.b/sext.h
847 let rs2 = (imm & 0x1F) as u8;
848 let opc = match rs2 {
849 0 => {
850 if self.is_64bit {
851 104
852 } else {
853 105
854 }
855 }
856 1 => {
857 if self.is_64bit {
858 106
859 } else {
860 107
861 }
862 }
863 2 => {
864 if self.is_64bit {
865 102
866 } else {
867 103
868 }
869 }
870 4 => 108,
871 5 => 109,
872 _ => {
873 return Err(TranspileError::UnsupportedInstruction {
874 offset: 0,
875 detail: format!("Zbb rs2={}", rs2),
876 });
877 }
878 };
879 self.emit_inst(opc);
880 self.emit_data(pvm_rd | (pvm_rs1 << 4));
881 return Ok(());
882 }
883 // SLLI (funct7=0x00)
884 let shamt = imm & if self.is_64bit { 0x3F } else { 0x1F };
885 self.emit_inst(if self.is_64bit { 151 } else { 138 });
886 self.emit_data(pvm_rd | (pvm_rs1 << 4));
887 self.emit_var_imm(shamt);
888 return Ok(());
889 }
890 2 => 137, // SLTI → set_lt_s_imm
891 3 => 136, // SLTIU → set_lt_u_imm
892 4 => 133, // XORI → xor_imm
893 5 => {
894 if (funct7 == 0x35 || funct7 == 0x34) && (imm & 0x1F) == 0x18 {
895 // Zbb rev8 (RV64: 0x35, RV32: 0x34)
896 self.emit_inst(111);
897 self.emit_data(pvm_rd | (pvm_rs1 << 4));
898 return Ok(());
899 }
900 if funct7 == 0x30 || funct7 == 0x31 {
901 // Zbb RORI: funct6=0x18 (bits 31:26).
902 // funct7=0x30 when shamt<32, funct7=0x31 when shamt>=32 (bit 25 set).
903 let shamt = imm & if self.is_64bit { 0x3F } else { 0x1F };
904 self.emit_inst(if self.is_64bit { 158 } else { 160 });
905 self.emit_data(pvm_rd | (pvm_rs1 << 4));
906 self.emit_var_imm(shamt);
907 return Ok(());
908 }
909 if funct7 == 0x14 {
910 // Zbb orc.b — OR-combine bytes
911 // No PVM equivalent; emit as trap (rare instruction)
912 return Err(TranspileError::UnsupportedInstruction {
913 offset: 0,
914 detail: "Zbb orc.b not yet supported".into(),
915 });
916 }
917 // SRLI (funct7=0x00) / SRAI (funct7=0x20+)
918 let shamt = imm & if self.is_64bit { 0x3F } else { 0x1F };
919 if funct7 & 0x20 != 0 {
920 self.emit_inst(if self.is_64bit { 153 } else { 140 }); // shar_r_imm
921 } else {
922 self.emit_inst(if self.is_64bit { 152 } else { 139 }); // shlo_r_imm
923 }
924 self.emit_data(pvm_rd | (pvm_rs1 << 4));
925 self.emit_var_imm(shamt);
926 return Ok(());
927 }
928 6 => 134, // ORI → or_imm
929 7 => 132, // ANDI → and_imm
930 _ => unreachable!(),
931 };
932
933 self.emit_inst(pvm_opcode);
934 self.emit_data(pvm_rd | (pvm_rs1 << 4));
935 self.emit_var_imm(imm);
936
937 Ok(())
938 }
939
940 fn translate_op(
941 &mut self,
942 funct3: u32,
943 funct7: u32,
944 rd: u8,
945 rs1: u8,
946 rs2: u8,
947 addr: u64,
948 ) -> Result<(), TranspileError> {
949 if rd == 0 {
950 return Ok(());
951 } // Write to x0 is a no-op in RISC-V
952
953 // Fuse load_imm + ALU op: if one operand was just loaded via load_imm
954 // and the value fits in i32, undo the load_imm and emit the immediate
955 // form instead (saves one instruction).
956 if let Some((load_rd, load_val, undo_pos)) = self.pending_load_imm.take()
957 && load_val >= i32::MIN as i64
958 && load_val <= i32::MAX as i64
959 {
960 let imm = load_val as i32;
961 // Check if rs2 is the loaded register (ADD/AND/OR/XOR rd, rs1, load_rd)
962 let (fuse_base, _commutative) = if rs2 == load_rd && rs1 != load_rd {
963 (Some(rs1), true)
964 } else if rs1 == load_rd && rs2 != load_rd && (funct7, funct3) != (0x20, 0) {
965 // rs1 is the loaded register — only for commutative ops (not SUB)
966 (Some(rs2), true)
967 } else {
968 (None, false)
969 };
970
971 if let Some(base) = fuse_base {
972 let pvm_imm_opcode = match (funct7, funct3) {
973 (0, 0) => Some(if self.is_64bit { 149 } else { 131 }), // ADD → add_imm
974 (0, 7) => Some(132), // AND → and_imm
975 (0, 6) => Some(134), // OR → or_imm
976 (0, 4) => Some(133), // XOR → xor_imm
977 (1, 0) => Some(if self.is_64bit { 150 } else { 135 }), // MUL → mul_imm
978 (0, 2) => Some(137), // SLT → set_lt_s_imm
979 (0, 3) => Some(136), // SLTU → set_lt_u_imm
980 _ => None,
981 };
982
983 if let Some(pvm_opcode) = pvm_imm_opcode {
984 // Only truncate the load_imm if the loaded register IS the
985 // destination (rd == load_rd). If it's just an operand,
986 // keep the load_imm so the register retains its value for
987 // future use (e.g., switch table: add idx, base, idx; lw off, 0(idx);
988 // add target, off, base; jr target — base is used twice).
989 if rd == load_rd {
990 self.code.truncate(undo_pos);
991 self.bitmask.truncate(undo_pos);
992 self.address_map.insert(addr, undo_pos as u32);
993 }
994 let pvm_rd = self.require_reg(rd)?;
995 let pvm_base = self.require_reg(base)?;
996 self.emit_inst(pvm_opcode);
997 self.emit_data(pvm_rd | (pvm_base << 4));
998 self.emit_var_imm(imm);
999 return Ok(());
1000 }
1001 }
1002
1003 // Special case: shifts with loaded shift count → shift immediate forms.
1004 // SLL/SRL/SRA rd, rs1, load_rd → shlo_l/shlo_r/shar_r_imm rd, rs1, imm
1005 if rs2 == load_rd && matches!((funct7, funct3), (0, 1) | (0, 5) | (0x20, 5)) {
1006 let pvm_imm_opcode = match (funct7, funct3) {
1007 (0, 1) => {
1008 if self.is_64bit {
1009 151
1010 } else {
1011 138
1012 }
1013 } // SLL → shlo_l_imm
1014 (0, 5) => {
1015 if self.is_64bit {
1016 152
1017 } else {
1018 139
1019 }
1020 } // SRL → shlo_r_imm
1021 (0x20, 5) => {
1022 if self.is_64bit {
1023 153
1024 } else {
1025 140
1026 }
1027 } // SRA → shar_r_imm
1028 _ => unreachable!(),
1029 };
1030 self.code.truncate(undo_pos);
1031 self.bitmask.truncate(undo_pos);
1032 self.address_map.insert(addr, undo_pos as u32);
1033 let pvm_rd = self.require_reg(rd)?;
1034 let pvm_rs1 = self.require_reg(rs1)?;
1035 self.emit_inst(pvm_imm_opcode);
1036 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1037 self.emit_var_imm(imm);
1038 return Ok(());
1039 }
1040
1041 // Special case: SUB rd, rs1, load_rd → neg_add_imm rd, rs1, -imm
1042 // SUB is not commutative, but if rs2 is the loaded register:
1043 // rd = rs1 - imm = rs1 + (-imm)
1044 if (funct7, funct3) == (0x20, 0) && rs2 == load_rd && rs1 != load_rd {
1045 let neg_imm = (-(load_val as i32) as i64) as i32;
1046 // Use add_imm with negated immediate (avoids neg_add_imm)
1047 let pvm_opcode = if self.is_64bit { 149 } else { 131 }; // add_imm
1048 self.code.truncate(undo_pos);
1049 self.bitmask.truncate(undo_pos);
1050 self.address_map.insert(addr, undo_pos as u32);
1051 let pvm_rd = self.require_reg(rd)?;
1052 let pvm_rs1 = self.require_reg(rs1)?;
1053 self.emit_inst(pvm_opcode);
1054 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1055 self.emit_var_imm(neg_imm);
1056 return Ok(());
1057 }
1058 }
1059 // Couldn't fuse — load_imm is already emitted, just proceed normally
1060
1061 // Handle x0 as source: PVM reg 0 = RA, not zero.
1062 if rs1 == 0 && funct7 == 0 && funct3 == 0 {
1063 // add rd, x0, rs2 → mv rd, rs2
1064 let pvm_rd = self.require_reg(rd)?;
1065 let pvm_rs2 = self.require_reg(rs2)?;
1066 self.emit_inst(100); // move_reg
1067 self.emit_data(pvm_rd | (pvm_rs2 << 4));
1068 return Ok(());
1069 }
1070 if rs2 == 0 && funct7 == 0 && funct3 == 0 {
1071 // add rd, rs1, x0 → mv rd, rs1
1072 let pvm_rd = self.require_reg(rd)?;
1073 let pvm_rs1 = self.require_reg(rs1)?;
1074 self.emit_inst(100); // move_reg
1075 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1076 return Ok(());
1077 }
1078 // SUB rd, x0, rs2 → neg rd, rs2
1079 if rs1 == 0 && funct7 == 0x20 && funct3 == 0 {
1080 let pvm_rd = self.require_reg(rd)?;
1081 let pvm_rs2 = self.require_reg(rs2)?;
1082 let neg_op = if self.is_64bit { 154 } else { 141 }; // neg_add_imm_64/32
1083 self.emit_inst(neg_op);
1084 self.emit_data(pvm_rd | (pvm_rs2 << 4));
1085 self.emit_var_imm(0);
1086 return Ok(());
1087 }
1088 // Handle remaining x0 source cases
1089 if rs1 == 0 {
1090 let pvm_rd = self.require_reg(rd)?;
1091 let pvm_rs2 = self.require_reg(rs2)?;
1092 match (funct7, funct3) {
1093 (0, 1) | (0, 5) | (0x20, 5) => {
1094 // SLL/SRL/SRA rd, x0, rs2 → shift 0 by rs2 = 0
1095 return self.emit_load_imm(rd, 0);
1096 }
1097 (0, 4) | (0, 6) => {
1098 // XOR/OR rd, x0, rs2 → rs2
1099 self.emit_inst(100); // move_reg
1100 self.emit_data(pvm_rd | (pvm_rs2 << 4));
1101 return Ok(());
1102 }
1103 (0, 7) => {
1104 // AND rd, x0, rs2 → 0
1105 return self.emit_load_imm(rd, 0);
1106 }
1107 (0, 3) => {
1108 // SLTU rd, x0, rs2 → snez rd, rs2
1109 // When rd == rs2, skip the load_imm to avoid clobbering
1110 // the value before the conditional check.
1111 if pvm_rd != pvm_rs2 {
1112 self.emit_load_imm(rd, 0)?;
1113 }
1114 self.emit_inst(148); // cmov_nz_imm: if rs2 != 0 then rd = imm
1115 self.emit_data(pvm_rd | (pvm_rs2 << 4));
1116 self.emit_var_imm(1);
1117 return Ok(());
1118 }
1119 (1, _) => {
1120 // M extension with x0 → result is 0
1121 return self.emit_load_imm(rd, 0);
1122 }
1123 _ => {
1124 return Err(TranspileError::UnsupportedInstruction {
1125 offset: addr as usize,
1126 detail: format!(
1127 "unhandled x0-as-rs1 op: funct7={funct7:#x} funct3={funct3}"
1128 ),
1129 });
1130 }
1131 }
1132 }
1133 if rs2 == 0 {
1134 let pvm_rd = self.require_reg(rd)?;
1135 let pvm_rs1 = self.require_reg(rs1)?;
1136 match (funct7, funct3) {
1137 (0, 2) | (0, 3) => {
1138 // slt(u) rd, rs1, x0 → set_lt_(s|u)_imm rd, rs1, 0
1139 let pvm_opcode = if funct3 == 2 { 137 } else { 136 };
1140 self.emit_inst(pvm_opcode);
1141 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1142 self.emit_var_imm(0);
1143 return Ok(());
1144 }
1145 (0x20, 0) | (0, 4) | (0, 6) => {
1146 // SUB/XOR/OR rd, rs1, x0 → rs1 op 0 = rs1 → move
1147 self.emit_inst(100); // move_reg
1148 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1149 return Ok(());
1150 }
1151 (0, 7) => {
1152 // AND rd, rs1, x0 → 0
1153 return self.emit_load_imm(rd, 0);
1154 }
1155 (0, 1) | (0, 5) | (0x20, 5) => {
1156 // SLL/SRL/SRA rd, rs1, x0 → shift by 0 = rs1 → move
1157 self.emit_inst(100); // move_reg
1158 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1159 return Ok(());
1160 }
1161 (1, _) => {
1162 // M extension: mul rd, rs1, 0 = 0; div/rem by 0 is undefined
1163 return self.emit_load_imm(rd, 0);
1164 }
1165 _ => {
1166 return Err(TranspileError::UnsupportedInstruction {
1167 offset: addr as usize,
1168 detail: format!(
1169 "unhandled x0-as-rs2 op: funct7={funct7:#x} funct3={funct3}"
1170 ),
1171 });
1172 }
1173 }
1174 }
1175
1176 let pvm_rd = self.require_reg(rd)?;
1177 let pvm_rs1 = self.require_reg(rs1)?;
1178 let pvm_rs2 = self.require_reg(rs2)?;
1179
1180 // RV32 uses 32-bit PVM ops; RV64 uses 64-bit PVM ops
1181 let pvm_opcode = if funct7 == 1 {
1182 // M extension (multiply/divide)
1183 match funct3 {
1184 0 => {
1185 if self.is_64bit {
1186 202
1187 } else {
1188 192
1189 }
1190 } // MUL
1191 1 => 213, // MULH → mul_upper_ss (always 64-bit, gives upper bits)
1192 2 => 215, // MULHSU → mul_upper_su
1193 3 => 214, // MULHU → mul_upper_uu
1194 4 => {
1195 if self.is_64bit {
1196 204
1197 } else {
1198 194
1199 }
1200 } // DIV
1201 5 => {
1202 if self.is_64bit {
1203 203
1204 } else {
1205 193
1206 }
1207 } // DIVU
1208 6 => {
1209 if self.is_64bit {
1210 206
1211 } else {
1212 196
1213 }
1214 } // REM
1215 7 => {
1216 if self.is_64bit {
1217 205
1218 } else {
1219 195
1220 }
1221 } // REMU
1222 _ => unreachable!(),
1223 }
1224 } else if funct7 == 0x20 {
1225 match funct3 {
1226 0 => {
1227 if self.is_64bit {
1228 201
1229 } else {
1230 191
1231 }
1232 } // SUB
1233 5 => {
1234 if self.is_64bit {
1235 209
1236 } else {
1237 199
1238 }
1239 } // SRA
1240 7 | 6 | 4 => {
1241 // Zbb ANDN/ORN/XNOR: rd = rs1 OP ~rs2
1242 if rd == rs1 && rd == rs2 {
1243 return self.emit_load_imm(rd, if funct3 == 7 { 0 } else { -1i64 });
1244 }
1245 let alu: u8 = match funct3 {
1246 7 => 210,
1247 6 => 212,
1248 _ => 211,
1249 };
1250 if rd != rs1 {
1251 // use rd as temp for ~rs2
1252 self.emit_inst(133);
1253 self.emit_data(pvm_rd | (pvm_rs2 << 4));
1254 self.emit_var_imm(-1);
1255 self.emit_inst(alu);
1256 self.emit_data(pvm_rs1 | (pvm_rd << 4));
1257 self.emit_data(pvm_rd);
1258 } else {
1259 // rd==rs1: NOT rs2 in-place, OP, restore
1260 self.emit_inst(133);
1261 self.emit_data(pvm_rs2 | (pvm_rs2 << 4));
1262 self.emit_var_imm(-1);
1263 self.emit_inst(alu);
1264 self.emit_data(pvm_rd | (pvm_rs2 << 4));
1265 self.emit_data(pvm_rd);
1266 self.emit_inst(133);
1267 self.emit_data(pvm_rs2 | (pvm_rs2 << 4));
1268 self.emit_var_imm(-1);
1269 }
1270 return Ok(());
1271 }
1272 _ => {
1273 return Err(TranspileError::UnsupportedInstruction {
1274 offset: 0,
1275 detail: format!("OP funct7=0x20 funct3={}", funct3),
1276 });
1277 }
1278 }
1279 } else if funct7 == 0x05 {
1280 // Zbb min/max
1281 match funct3 {
1282 4 => 229,
1283 5 => 230,
1284 6 => 227,
1285 7 => 228,
1286 _ => {
1287 return Err(TranspileError::UnsupportedInstruction {
1288 offset: 0,
1289 detail: format!("Zbb f7=5 f3={}", funct3),
1290 });
1291 }
1292 }
1293 } else if funct7 == 0x30 {
1294 // Zbb rotations — emit and return early
1295 let opc = match funct3 {
1296 1 => {
1297 if self.is_64bit {
1298 220
1299 } else {
1300 221
1301 }
1302 }
1303 5 => {
1304 if self.is_64bit {
1305 222
1306 } else {
1307 223
1308 }
1309 }
1310 _ => {
1311 return Err(TranspileError::UnsupportedInstruction {
1312 offset: 0,
1313 detail: format!("Zbb f7=30 f3={}", funct3),
1314 });
1315 }
1316 };
1317 self.emit_inst(opc);
1318 self.emit_data(pvm_rs1 | (pvm_rs2 << 4));
1319 self.emit_data(pvm_rd);
1320 return Ok(());
1321 } else if funct7 == 0 {
1322 match funct3 {
1323 0 => {
1324 if self.is_64bit {
1325 200
1326 } else {
1327 190
1328 }
1329 } // ADD
1330 1 => {
1331 if self.is_64bit {
1332 207
1333 } else {
1334 197
1335 }
1336 } // SLL
1337 2 => 217,
1338 3 => 216,
1339 4 => 211,
1340 5 => {
1341 if self.is_64bit {
1342 208
1343 } else {
1344 198
1345 }
1346 }
1347 6 => 212,
1348 7 => 210,
1349 _ => unreachable!(),
1350 }
1351 } else {
1352 return Err(TranspileError::UnsupportedInstruction {
1353 offset: 0,
1354 detail: format!("OP funct7={:#x} funct3={}", funct7, funct3),
1355 });
1356 };
1357
1358 // ThreeReg encoding: byte1 = rA | (rB << 4), byte2 = rD
1359 self.emit_inst(pvm_opcode);
1360 self.emit_data(pvm_rs1 | (pvm_rs2 << 4));
1361 self.emit_data(pvm_rd);
1362
1363 Ok(())
1364 }
1365
1366 fn translate_op_imm_32(
1367 &mut self,
1368 funct3: u32,
1369 funct7: u32,
1370 rd: u8,
1371 rs1: u8,
1372 imm: i32,
1373 ) -> Result<(), TranspileError> {
1374 if rd == 0 {
1375 return Ok(());
1376 }
1377 let pvm_rd = self.require_reg(rd)?;
1378 let pvm_rs1 = self.require_reg(rs1)?;
1379
1380 match funct3 {
1381 0 => {
1382 // ADDIW → add_imm_32
1383 self.emit_inst(131);
1384 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1385 self.emit_var_imm(imm);
1386 }
1387 1 => {
1388 if funct7 == 0x30 {
1389 // Zbb: clzw(rs2=0), ctzw(rs2=1), cpopw(rs2=2)
1390 let rs2 = (imm & 0x1F) as u8;
1391 let opc = match rs2 {
1392 0 => 105,
1393 1 => 107,
1394 2 => 103,
1395 _ => {
1396 return Err(TranspileError::UnsupportedInstruction {
1397 offset: 0,
1398 detail: format!("Zbb-W rs2={}", rs2),
1399 });
1400 }
1401 };
1402 self.emit_inst(opc);
1403 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1404 } else {
1405 // SLLIW (funct7=0x00)
1406 let shamt = imm & 0x1F;
1407 self.emit_inst(138);
1408 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1409 self.emit_var_imm(shamt);
1410 }
1411 }
1412 5 => {
1413 if funct7 == 0x30 {
1414 // Zbb roriw
1415 let shamt = imm & 0x1F;
1416 self.emit_inst(160);
1417 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1418 self.emit_var_imm(shamt);
1419 } else if funct7 & 0x20 != 0 {
1420 // SRAIW (funct7=0x20)
1421 let shamt = imm & 0x1F;
1422 self.emit_inst(140);
1423 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1424 self.emit_var_imm(shamt);
1425 } else {
1426 // SRLIW (funct7=0x00)
1427 let shamt = imm & 0x1F;
1428 self.emit_inst(139);
1429 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1430 self.emit_var_imm(shamt);
1431 }
1432 }
1433 _ => {
1434 return Err(TranspileError::UnsupportedInstruction {
1435 offset: 0,
1436 detail: format!("OP-IMM-32 funct3={}", funct3),
1437 });
1438 }
1439 }
1440
1441 Ok(())
1442 }
1443
1444 fn translate_op_32(
1445 &mut self,
1446 funct3: u32,
1447 funct7: u32,
1448 rd: u8,
1449 rs1: u8,
1450 rs2: u8,
1451 ) -> Result<(), TranspileError> {
1452 if rd == 0 {
1453 return Ok(());
1454 }
1455
1456 // Fuse load_imm + 32-bit ALU op: ADDW, MULW, SUB as negated ADD.
1457 if let Some((load_rd, load_val, undo_pos)) = self.pending_load_imm.take()
1458 && load_val >= i32::MIN as i64
1459 && load_val <= i32::MAX as i64
1460 {
1461 let imm = load_val as i32;
1462 let (fuse_base, _comm) = if rs2 == load_rd && rs1 != load_rd {
1463 (Some(rs1), true)
1464 } else if rs1 == load_rd && rs2 != load_rd && (funct7, funct3) != (0x20, 0) {
1465 (Some(rs2), true)
1466 } else {
1467 (None, false)
1468 };
1469
1470 if let Some(base) = fuse_base {
1471 let pvm_imm_opcode = match (funct7, funct3) {
1472 (0, 0) => Some(131), // ADDW → add_imm_32
1473 (1, 0) => Some(135), // MULW → mul_imm_32
1474 _ => None,
1475 };
1476 if let Some(pvm_opcode) = pvm_imm_opcode {
1477 self.code.truncate(undo_pos);
1478 self.bitmask.truncate(undo_pos);
1479 let pvm_rd = self.require_reg(rd)?;
1480 let pvm_base = self.require_reg(base)?;
1481 self.emit_inst(pvm_opcode);
1482 self.emit_data(pvm_rd | (pvm_base << 4));
1483 self.emit_var_imm(imm);
1484 return Ok(());
1485 }
1486 }
1487 // 32-bit shifts with loaded shift count → shift immediate forms.
1488 // SLLW/SRLW/SRAW rd, rs1, load_rd → shlo_l/shlo_r/shar_r_imm_32
1489 if rs2 == load_rd && matches!((funct7, funct3), (0, 1) | (0, 5) | (0x20, 5)) {
1490 let pvm_imm_opcode = match (funct7, funct3) {
1491 (0, 1) => 138, // SLLW → shlo_l_imm_32
1492 (0, 5) => 139, // SRLW → shlo_r_imm_32
1493 (0x20, 5) => 140, // SRAW → shar_r_imm_32
1494 _ => unreachable!(),
1495 };
1496 self.code.truncate(undo_pos);
1497 self.bitmask.truncate(undo_pos);
1498 let pvm_rd = self.require_reg(rd)?;
1499 let pvm_rs1 = self.require_reg(rs1)?;
1500 self.emit_inst(pvm_imm_opcode);
1501 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1502 self.emit_var_imm(imm);
1503 return Ok(());
1504 }
1505
1506 // SUB with loaded rs2: SUBW rd, rs1, load_rd → add_imm_32 rd, rs1, -imm
1507 if (funct7, funct3) == (0x20, 0) && rs2 == load_rd && rs1 != load_rd {
1508 let neg_imm = (-(load_val as i32) as i64) as i32;
1509 self.code.truncate(undo_pos);
1510 self.bitmask.truncate(undo_pos);
1511 let pvm_rd = self.require_reg(rd)?;
1512 let pvm_rs1 = self.require_reg(rs1)?;
1513 self.emit_inst(131); // add_imm_32
1514 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1515 self.emit_var_imm(neg_imm);
1516 return Ok(());
1517 }
1518 }
1519 // Couldn't fuse
1520
1521 // Handle x0 as source: PVM reg 0 = RA, not zero.
1522 if rs1 == 0 {
1523 let pvm_rd = self.require_reg(rd)?;
1524 let pvm_rs2 = self.require_reg(rs2)?;
1525 match (funct7, funct3) {
1526 (0, 0) => {
1527 // ADDW rd, x0, rs2 → sext.w rd, rs2 (sign-extend lower 32 bits)
1528 self.emit_inst(131); // add_imm_32
1529 self.emit_data(pvm_rd | (pvm_rs2 << 4));
1530 self.emit_var_imm(0);
1531 return Ok(());
1532 }
1533 (0x20, 0) => {
1534 // SUBW rd, x0, rs2 → negw rd, rs2
1535 self.emit_inst(141); // neg_add_imm_32
1536 self.emit_data(pvm_rd | (pvm_rs2 << 4));
1537 self.emit_var_imm(0);
1538 return Ok(());
1539 }
1540 _ => {
1541 return Err(TranspileError::UnsupportedInstruction {
1542 offset: 0,
1543 detail: format!("OP-32 x0-as-rs1: funct7={:#x} funct3={}", funct7, funct3),
1544 });
1545 }
1546 }
1547 }
1548 if rs2 == 0 {
1549 let pvm_rd = self.require_reg(rd)?;
1550 let pvm_rs1 = self.require_reg(rs1)?;
1551 match (funct7, funct3) {
1552 (0, 0) => {
1553 // ADDW rd, rs1, x0 → sext.w rd, rs1
1554 self.emit_inst(131); // add_imm_32
1555 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1556 self.emit_var_imm(0);
1557 return Ok(());
1558 }
1559 (0x20, 0) => {
1560 // SUBW rd, rs1, x0 → sext.w rd, rs1 (subtract zero)
1561 self.emit_inst(131); // add_imm_32
1562 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1563 self.emit_var_imm(0);
1564 return Ok(());
1565 }
1566 (0x04, 4) => {
1567 // PACKW rd, rs1, x0 = ZEXT.H rd, rs1 (Zbb: zero-extend halfword)
1568 // rd = rs1 & 0xFFFF
1569 self.emit_inst(70); // and_imm
1570 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1571 self.emit_var_imm(0xFFFF);
1572 return Ok(());
1573 }
1574 _ => {
1575 return Err(TranspileError::UnsupportedInstruction {
1576 offset: 0,
1577 detail: format!("OP-32 x0-as-rs2: funct7={:#x} funct3={}", funct7, funct3),
1578 });
1579 }
1580 }
1581 }
1582
1583 let pvm_rd = self.require_reg(rd)?;
1584 let pvm_rs1 = self.require_reg(rs1)?;
1585 let pvm_rs2 = self.require_reg(rs2)?;
1586
1587 let pvm_opcode = if funct7 == 1 {
1588 match funct3 {
1589 0 => 192, // MULW → mul_32
1590 4 => 194, // DIVW → div_s_32
1591 5 => 193, // DIVUW → div_u_32
1592 6 => 196, // REMW → rem_s_32
1593 7 => 195, // REMUW → rem_u_32
1594 _ => {
1595 return Err(TranspileError::UnsupportedInstruction {
1596 offset: 0,
1597 detail: format!("OP-32 M funct3={}", funct3),
1598 });
1599 }
1600 }
1601 } else if funct7 == 0x20 {
1602 match funct3 {
1603 0 => 191, // SUBW → sub_32
1604 5 => 199, // SRAW → shar_r_32
1605 _ => {
1606 return Err(TranspileError::UnsupportedInstruction {
1607 offset: 0,
1608 detail: format!("OP-32 funct7=0x20 funct3={}", funct3),
1609 });
1610 }
1611 }
1612 } else if funct7 == 0x30 {
1613 // Zbb rolw/rorw
1614 let opc = match funct3 {
1615 1 => 221,
1616 5 => 223,
1617 _ => {
1618 return Err(TranspileError::UnsupportedInstruction {
1619 offset: 0,
1620 detail: format!("OP-32 f7=30 f3={}", funct3),
1621 });
1622 }
1623 };
1624 self.emit_inst(opc);
1625 self.emit_data(pvm_rs1 | (pvm_rs2 << 4));
1626 self.emit_data(pvm_rd);
1627 return Ok(());
1628 } else if funct7 == 0x04 && funct3 == 4 {
1629 // Zbb zext.h
1630 self.emit_inst(110);
1631 self.emit_data(pvm_rd | (pvm_rs1 << 4));
1632 return Ok(());
1633 } else if funct7 == 0 {
1634 match funct3 {
1635 0 => 190, // ADDW → add_32
1636 1 => 197, // SLLW → shlo_l_32
1637 5 => 198, // SRLW → shlo_r_32
1638 _ => {
1639 return Err(TranspileError::UnsupportedInstruction {
1640 offset: 0,
1641 detail: format!("OP-32 funct3={}", funct3),
1642 });
1643 }
1644 }
1645 } else {
1646 return Err(TranspileError::UnsupportedInstruction {
1647 offset: 0,
1648 detail: format!("OP-32 funct7={:#x} funct3={}", funct7, funct3),
1649 });
1650 };
1651
1652 // ThreeReg encoding: byte1 = rA | (rB << 4), byte2 = rD
1653 self.emit_inst(pvm_opcode);
1654 self.emit_data(pvm_rs1 | (pvm_rs2 << 4));
1655 self.emit_data(pvm_rd);
1656
1657 Ok(())
1658 }
1659
1660 // ===== Helpers =====
1661
1662 pub(crate) fn require_reg(&self, rv_reg: u8) -> Result<u8, TranspileError> {
1663 match map_register(rv_reg)? {
1664 Some(r) => Ok(r),
1665 None => Ok(0), // x0 → use reg 0 and ignore writes
1666 }
1667 }
1668
1669 pub(crate) fn emit_inst(&mut self, opcode: u8) {
1670 self.code.push(opcode);
1671 self.bitmask.push(1);
1672 }
1673
1674 pub(crate) fn emit_data(&mut self, byte: u8) {
1675 self.code.push(byte);
1676 self.bitmask.push(0);
1677 }
1678
1679 pub(crate) fn emit_imm32(&mut self, imm: i32) {
1680 let bytes = imm.to_le_bytes();
1681 for b in &bytes {
1682 self.emit_data(*b);
1683 }
1684 }
1685
1686 /// Emit a signed immediate using the minimum byte width.
1687 /// PVM instruction categories OneRegOneImm and TwoRegOneImm derive
1688 /// immediate length from the instruction skip distance, so shorter
1689 /// encodings are automatically decoded correctly via sign extension.
1690 /// Compute the number of bytes needed for a variable-length immediate.
1691 fn var_imm_byte_count(imm: i32) -> usize {
1692 if imm == 0 {
1693 0
1694 } else if (-128..=127).contains(&imm) {
1695 1
1696 } else if (-32768..=32767).contains(&imm) {
1697 2
1698 } else {
1699 4
1700 }
1701 }
1702
1703 pub(crate) fn emit_var_imm(&mut self, imm: i32) {
1704 if imm == 0 {
1705 // Zero bytes — decoder gets lx=0, sign_extend(0, 0) = 0
1706 } else if (-128..=127).contains(&imm) {
1707 self.emit_data(imm as i8 as u8);
1708 } else if (-32768..=32767).contains(&imm) {
1709 let bytes = (imm as i16).to_le_bytes();
1710 for b in &bytes {
1711 self.emit_data(*b);
1712 }
1713 } else {
1714 let bytes = imm.to_le_bytes();
1715 for b in &bytes {
1716 self.emit_data(*b);
1717 }
1718 }
1719 }
1720
1721 pub(crate) fn emit_load_imm(&mut self, rd: u8, imm: i64) -> Result<(), TranspileError> {
1722 if rd == 0 {
1723 return Ok(());
1724 } // Write to zero register is nop
1725 let pvm_rd = self.require_reg(rd)?;
1726
1727 if imm >= i32::MIN as i64 && imm <= i32::MAX as i64 {
1728 // load_imm (opcode 51)
1729 self.emit_inst(51);
1730 self.emit_data(pvm_rd);
1731 self.emit_var_imm(imm as i32);
1732 } else {
1733 // load_imm_64 (opcode 20)
1734 self.emit_inst(20);
1735 self.emit_data(pvm_rd);
1736 let bytes = (imm as u64).to_le_bytes();
1737 for b in &bytes {
1738 self.emit_data(*b);
1739 }
1740 }
1741 Ok(())
1742 }
1743
1744 pub(crate) fn emit_jump(&mut self, target: u64) {
1745 let inst_pc = self.code.len() as u32;
1746 self.emit_inst(40); // jump
1747 let fixup_pos = self.code.len();
1748 self.fixups.push((fixup_pos, target, 4));
1749 self.fixup_pcs.insert(fixup_pos, inst_pc);
1750 self.emit_imm32(0); // placeholder
1751 }
1752
1753 /// Emit load_imm_jump: fuse load_imm + jump into a single PVM instruction.
1754 /// Opcode 80: OneRegImmOffset format — sets register and jumps in one step.
1755 /// Saves one instruction (and one gas block boundary) per function call.
1756 fn emit_load_imm_jump(&mut self, rd: u8, imm: i64, target: u64) -> Result<(), TranspileError> {
1757 let pvm_rd = self.require_reg(rd)?;
1758
1759 let inst_pc = self.code.len() as u32;
1760 self.emit_inst(80); // load_imm_jump
1761
1762 // OneRegImmOffset encoding: reg_byte (rd + lX), then imm bytes, then offset bytes.
1763 // Use variable-length encoding for the immediate.
1764 let imm_len = Self::var_imm_byte_count(imm as i32);
1765 let reg_byte = pvm_rd | ((imm_len as u8) << 4);
1766 self.emit_data(reg_byte);
1767 self.emit_var_imm(imm as i32);
1768
1769 // Offset (4 bytes, patched by fixup)
1770 let fixup_pos = self.code.len();
1771 self.fixups.push((fixup_pos, target, 4));
1772 self.fixup_pcs.insert(fixup_pos, inst_pc);
1773 self.emit_imm32(0); // placeholder offset
1774 Ok(())
1775 }
1776
1777 /// Emit a function call: load return address into rd and jump to target.
1778 /// Uses load_imm_jump (opcode 80) to fuse into a single PVM instruction,
1779 /// saving one instruction per call site.
1780 pub(crate) fn emit_call(
1781 &mut self,
1782 rd: u8,
1783 rv_ret_addr: u64,
1784 target: u64,
1785 ) -> Result<(), TranspileError> {
1786 if rd == 0 {
1787 // No return address needed — just jump
1788 self.emit_jump(target);
1789 return Ok(());
1790 }
1791 let jt_idx = self.jump_table.len();
1792 self.jump_table.push(0); // placeholder
1793 self.return_fixups.push((jt_idx, rv_ret_addr));
1794 let jt_addr = ((jt_idx + 1) * 2) as i64;
1795 self.emit_load_imm_jump(rd, jt_addr, target)
1796 }
1797
1798 pub(crate) fn emit_ecalli(&mut self, id: u32) {
1799 self.emit_inst(10);
1800 self.emit_var_imm(id as i32);
1801 }
1802
1803 /// Emit PVM ecall (opcode 3, NoArgs). Management ops + dynamic CALL.
1804 /// φ[11]=op, φ[12]=subject|object — all operands in registers.
1805 pub(crate) fn emit_ecall(&mut self) {
1806 self.emit_inst(3);
1807 }
1808
1809 /// Emit a OneRegImmOffset instruction (used by branch_*_imm opcodes).
1810 ///
1811 /// PVM encoding: [opcode][ra | (lx << 4)][imm (lx bytes LE)][offset (4 bytes LE)]
1812 /// where lx = minimum bytes to represent the signed immediate.
1813 fn emit_branch_imm(&mut self, opcode: u8, ra: u8, imm: i32, target: u64) {
1814 let inst_pc = self.code.len() as u32;
1815 self.emit_inst(opcode);
1816
1817 let (lx, imm_bytes) = encode_var_imm(imm);
1818
1819 // Pack register and immediate length into one byte
1820 self.emit_data(ra | (lx << 4));
1821
1822 // Emit immediate bytes
1823 for b in &imm_bytes {
1824 self.emit_data(*b);
1825 }
1826
1827 // Emit offset placeholder (4 bytes, filled by fixup)
1828 let fixup_pos = self.code.len();
1829 self.fixups.push((fixup_pos, target, 4));
1830 self.fixup_pcs.insert(fixup_pos, inst_pc);
1831 self.emit_imm32(0);
1832 }
1833
1834 /// Build a mapping from RISC-V code addresses to PVM jump table addresses.
1835 ///
1836 /// Only creates jump table entries for the given `targets` — the set of
1837 /// RISC-V addresses actually referenced as function pointers (e.g. from
1838 /// Check whether an address falls within any RISC-V code section.
1839 pub fn is_code_addr(&self, addr: u64) -> bool {
1840 self.code_ranges
1841 .iter()
1842 .any(|(lo, hi)| addr >= *lo && addr < *hi)
1843 }
1844
1845 /// absolute relocations in data sections like vtables). Returns a map of
1846 /// RISC-V address → jump table address (= (index+1)*2).
1847 ///
1848 /// This is needed to fix indirect calls through function pointers stored in
1849 /// data sections (vtables, callbacks, etc.). The PVM's `jump_ind` instruction
1850 /// expects jump table addresses, not raw code offsets.
1851 pub fn build_function_pointer_map(
1852 &mut self,
1853 targets: &std::collections::HashSet<u64>,
1854 ) -> std::collections::HashMap<u64, u32> {
1855 let mut rv_to_jt: std::collections::HashMap<u64, u32> = std::collections::HashMap::new();
1856
1857 let mut target_addrs: Vec<u64> = targets.iter().copied().collect();
1858 target_addrs.sort();
1859
1860 for rv_addr in &target_addrs {
1861 if let Some(&pvm_offset) = self.address_map.get(rv_addr) {
1862 let jt_idx = self.jump_table.len();
1863 self.jump_table.push(pvm_offset);
1864 let jt_addr = ((jt_idx + 1) * 2) as u32;
1865 rv_to_jt.insert(*rv_addr, jt_addr);
1866 }
1867 }
1868
1869 rv_to_jt
1870 }
1871
1872 pub(crate) fn apply_fixups(&mut self) {
1873 // PC-relative fixups (branches, jumps)
1874 for (pvm_offset, rv_target, size) in self.fixups.drain(..).collect::<Vec<_>>() {
1875 if let Some(&pvm_target) = self.address_map.get(&rv_target) {
1876 let inst_pc = self
1877 .fixup_pcs
1878 .get(&pvm_offset)
1879 .copied()
1880 .unwrap_or(pvm_offset as u32 - 1);
1881 let relative = (pvm_target as i64 - inst_pc as i64) as i32;
1882 let bytes = relative.to_le_bytes();
1883 self.code[pvm_offset..pvm_offset + size as usize]
1884 .copy_from_slice(&bytes[..size as usize]);
1885 } else {
1886 tracing::warn!(
1887 "unresolved fixup: rv_target={:#x}, pvm_offset={}",
1888 rv_target,
1889 pvm_offset
1890 );
1891 }
1892 }
1893
1894 // Resolve return address fixups in the jump table
1895 let mut unresolved = 0usize;
1896 for (jt_idx, rv_addr) in self.return_fixups.drain(..).collect::<Vec<_>>() {
1897 if let Some(&pvm_target) = self.address_map.get(&rv_addr) {
1898 self.jump_table[jt_idx] = pvm_target;
1899 } else {
1900 unresolved += 1;
1901 eprintln!(
1902 "javm-transpiler: unresolved return_fixup jt_idx={jt_idx} rv_addr={:#x}",
1903 rv_addr
1904 );
1905 }
1906 }
1907 if unresolved > 0 {
1908 eprintln!(
1909 "javm-transpiler: {unresolved} unresolved return_fixups (will panic on jump)"
1910 );
1911 }
1912 }
1913}
1914
1915// ===== RISC-V immediate decoders =====
1916
1917fn decode_j_imm(inst: u32) -> i32 {
1918 let imm20 = (inst >> 31) & 1;
1919 let imm10_1 = (inst >> 21) & 0x3FF;
1920 let imm11 = (inst >> 20) & 1;
1921 let imm19_12 = (inst >> 12) & 0xFF;
1922 let imm = (imm20 << 20) | (imm19_12 << 12) | (imm11 << 11) | (imm10_1 << 1);
1923 // Sign extend from bit 20
1924 if imm20 != 0 {
1925 (imm | 0xFFE00000) as i32
1926 } else {
1927 imm as i32
1928 }
1929}
1930
1931fn decode_b_imm(inst: u32) -> i32 {
1932 let imm12 = (inst >> 31) & 1;
1933 let imm10_5 = (inst >> 25) & 0x3F;
1934 let imm4_1 = (inst >> 8) & 0xF;
1935 let imm11 = (inst >> 7) & 1;
1936 let imm = (imm12 << 12) | (imm11 << 11) | (imm10_5 << 5) | (imm4_1 << 1);
1937 if imm12 != 0 {
1938 (imm | 0xFFFFE000) as i32
1939 } else {
1940 imm as i32
1941 }
1942}
1943
1944fn decode_s_imm(inst: u32) -> i32 {
1945 let imm11_5 = (inst >> 25) & 0x7F;
1946 let imm4_0 = (inst >> 7) & 0x1F;
1947 let imm = (imm11_5 << 5) | imm4_0;
1948 if imm11_5 & 0x40 != 0 {
1949 (imm | 0xFFFFF000) as i32
1950 } else {
1951 imm as i32
1952 }
1953}
1954
1955#[cfg(test)]
1956mod tests {
1957 use super::*;
1958
1959 #[test]
1960 fn test_register_mapping() {
1961 assert_eq!(map_register(0).unwrap(), None); // zero
1962 assert_eq!(map_register(1).unwrap(), Some(0)); // ra
1963 assert_eq!(map_register(2).unwrap(), Some(1)); // sp
1964 assert_eq!(map_register(10).unwrap(), Some(7)); // a0
1965 assert_eq!(map_register(15).unwrap(), Some(12)); // a5
1966 assert!(map_register(3).is_err()); // gp: no mapping
1967 }
1968
1969 #[test]
1970 fn test_decode_j_imm() {
1971 // JAL x0, 0 (forward)
1972 assert_eq!(decode_j_imm(0x0000006F), 0);
1973 // JAL x0, 4
1974 assert_eq!(decode_j_imm(0x0040006F), 4);
1975 }
1976
1977 #[test]
1978 fn test_decode_j_imm_negative() {
1979 // JAL with negative offset (backward jump)
1980 // imm[20|10:1|11|19:12] = all 1s → -2
1981 // Encoding: bit31=1, bits[30:21]=0x3FF, bit20=1, bits[19:12]=0xFF
1982 let inst = 0xFFFFF06F; // JAL x0, -2 (the standard encoding for JAL x0, -2)
1983 let imm = decode_j_imm(inst);
1984 assert!(imm < 0, "negative offset should be negative");
1985 assert_eq!(imm, -2);
1986 }
1987
1988 #[test]
1989 fn test_decode_j_imm_max_positive() {
1990 // JAL with maximum positive offset: imm[20]=0, rest=max
1991 // imm = 0x0FFFFE (1048574)
1992 let inst = 0x7FFFF06F; // bit31=0, bits[30:21]=0x3FF, bit20=1, bits[19:12]=0xFF
1993 let imm = decode_j_imm(inst);
1994 assert!(imm > 0);
1995 }
1996
1997 #[test]
1998 fn test_decode_b_imm_zero() {
1999 // BEQ x0, x0, 0
2000 assert_eq!(decode_b_imm(0x00000063), 0);
2001 }
2002
2003 #[test]
2004 fn test_decode_b_imm_positive() {
2005 // BEQ with offset +8: imm[12|10:5]=0, imm[4:1]=0100, imm[11]=0
2006 // inst[11:8]=0100 → imm[4:1]=4, shift by 1 → offset=8
2007 let inst = 0x00000463; // BEQ x0, x0, +8
2008 let imm = decode_b_imm(inst);
2009 assert_eq!(imm, 8);
2010 }
2011
2012 #[test]
2013 fn test_decode_b_imm_negative() {
2014 // BEQ with negative offset
2015 // All bits set → -2
2016 let inst = 0xFE000FE3; // BEQ x0, x0, -2
2017 let imm = decode_b_imm(inst);
2018 assert_eq!(imm, -2);
2019 }
2020
2021 #[test]
2022 fn test_decode_s_imm_zero() {
2023 // SD x0, 0(x0): imm=0
2024 assert_eq!(decode_s_imm(0x00003023), 0);
2025 }
2026
2027 #[test]
2028 fn test_decode_s_imm_positive() {
2029 // SD x0, 8(x0): imm[11:5]=0, imm[4:0]=01000
2030 let inst = 0x00003423; // imm4_0 = 8 (at bits[11:7] = 01000)
2031 let imm = decode_s_imm(inst);
2032 assert_eq!(imm, 8);
2033 }
2034
2035 #[test]
2036 fn test_decode_s_imm_negative() {
2037 // SD x0, -8(x0): imm = -8 = 0xFFF8
2038 // imm[11:5] = 1111111, imm[4:0] = 11000
2039 let inst = 0xFE003C23; // SD x0, -8(x0)
2040 let imm = decode_s_imm(inst);
2041 assert_eq!(imm, -8);
2042 }
2043
2044 #[test]
2045 fn test_register_mapping_all_a_regs() {
2046 // a0-a5 map to PVM registers 7-12
2047 for (rv, pvm) in [(10, 7), (11, 8), (12, 9), (13, 10), (14, 11), (15, 12)] {
2048 assert_eq!(
2049 map_register(rv).unwrap(),
2050 Some(pvm),
2051 "rv{rv} should map to φ[{pvm}]"
2052 );
2053 }
2054 }
2055
2056 #[test]
2057 fn test_register_mapping_s_regs() {
2058 // s0-s1 map to PVM registers 5-6
2059 assert_eq!(map_register(8).unwrap(), Some(5)); // s0
2060 assert_eq!(map_register(9).unwrap(), Some(6)); // s1
2061 }
2062
2063 #[test]
2064 fn test_register_mapping_unmapped() {
2065 // tp(4), t3-t6(28-31) are unmapped → error
2066 assert!(map_register(4).is_err()); // tp
2067 assert!(map_register(28).is_err()); // t3
2068 }
2069}