zingen/jump/relocate.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
//! Program Relocations
use crate::{
jump::{relocate, Jump, JumpTable},
wasm::ToLSBytes,
Buffer, Error, Result, BUFFER_LIMIT,
};
use opcodes::ShangHai as OpCode;
impl JumpTable {
/// Relocate program counter to all registered labels.
///
/// *WARNING*: This function should only be called once in the compiler.
/// considering move it to the compiler.
pub fn relocate(&mut self, buffer: &mut Buffer) -> Result<()> {
// pre-calculate and shift targets
self.shift_targets()?;
tracing::trace!("code section offset: 0x{:x}", self.code.offset());
// relocate functions
while let Some((pc, jump)) = self.jump.pop_first() {
tracing::debug!("run relocation for {jump}");
let mut target = self.target(&jump)?;
self.relocate_offset(pc, jump, &mut target)?;
let offset = relocate::pc(buffer, pc, target)?;
self.shift_label_pc(pc, offset as u16)?;
}
buffer.extend_from_slice(&self.code.finish());
Ok(())
}
/// relocate the target of offset jump
fn relocate_offset(&self, pc: u16, jump: Jump, target: &mut u16) -> Result<()> {
if !jump.is_offset() {
return Ok(());
}
// NOTE: If the target is offset the return data is
// the offset instead of the PC.
*target += pc;
// check the original pc of the offset is greater than 0xff
if pc > 0xff {
*target += 1;
}
// check if the offset of the embedded call is greater than 0xff
if let Some((_, next_target)) = self.jump.first_key_value() {
if next_target.is_call() && self.target(next_target)? > 0xff {
*target += 1
}
}
Ok(())
}
}
// /// Get the offset of the program counter for relocation.
// pub fn offset(pc: u16) -> Result<u16> {
// let mut offset = 0;
//
// // Update the target program counter
// {
// // The maximum size of the PC is 2 bytes, whatever PUSH1 or PUSH32
// // takes 1 more byte.
// offset += 1;
//
// // Update the program counter for the edge cases.
// //
// // Start from 0xff, the lowest significant bytes of the target
// // program counter will take 2 bytes instead of 1 byte.
// //
// // | PC | PC BYTES | TARGET PC |
// // |------|----------|-----------|
// // | 0xfe | 1 | 0xff |
// // | 0xff | 2 | 0x0101 |
// offset += if pc > 0xfe { 2 } else { 1 }
// }
//
// // Check PC range.
// if pc + offset > BUFFER_LIMIT as u16 {
// return Err(Error::InvalidPC((pc + offset) as usize));
// }
//
// Ok(offset)
// }
/// Relocate program counter to buffer.
fn pc(buffer: &mut Buffer, original_pc: u16, target_pc: u16) -> Result<usize> {
let original_pc = original_pc as usize;
let mut new_buffer: Buffer = buffer[..original_pc].into();
let rest_buffer: Buffer = buffer[original_pc..].into();
let pc_offset = target_pc.to_ls_bytes();
if pc_offset.len() == 1 {
new_buffer.push(OpCode::PUSH1.into());
} else {
new_buffer.push(OpCode::PUSH2.into());
}
tracing::trace!(
"push bytes: 0x{} at 0x{}",
hex::encode(&pc_offset),
hex::encode(original_pc.to_ls_bytes())
);
new_buffer.extend_from_slice(&pc_offset);
new_buffer.extend_from_slice(&rest_buffer);
// Check buffer size.
if new_buffer.len() > BUFFER_LIMIT {
return Err(Error::BufferOverflow(new_buffer.len()));
}
*buffer = new_buffer;
Ok(1 + pc_offset.len())
}