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
//! Program Relocations
//!
//! This module provides functionality for relocating program counters associated
//! with various jump types in the jump table. It handles the adjustment of
//! program counters based on their target locations, ensuring that jumps
//! point to the correct addresses after any modifications to the code section.

use crate::{
    jump::{relocate, JumpTable},
    wasm::ToLSBytes,
    Buffer, Error, Result, BUFFER_LIMIT,
};
use opcodes::ShangHai as OpCode;

impl JumpTable {
    /// Relocate program counter to all registered labels.
    ///
    /// This function is responsible for adjusting the program counters of all
    /// jumps in the jump table. It first pre-calculates and shifts the target
    /// program counters, then iterates through each jump to relocate its
    /// target address. The function ensures that the jumps are correctly
    /// updated in the buffer, which represents the compiled code.
    ///
    /// *WARNING*: This function should only be called once in the compiler.
    /// Consider moving it to the compiler's main logic.
    pub fn relocate(&mut self, buffer: &mut Buffer) -> Result<()> {
        // Pre-calculate and shift targets to ensure all jumps point to the correct addresses.
        self.shift_targets()?;
        tracing::trace!("code section offset: 0x{:x}", self.code.offset());

        // Relocate each function in the jump table.
        while let Some((pc, jump)) = self.jump.pop_first() {
            tracing::debug!(
                "Relocating jump {:?} at pc=0x{:x}, current_offset=0x{:x}",
                jump,
                pc,
                self.code.offset()
            );
            let target = self.target(&jump)?;

            tracing::debug!(
                "relocate: pc=0x{:x}, jump={:?}, target=0x{:x}",
                pc,
                jump,
                target
            );

            // Update the buffer with the new target program counter.
            let offset = relocate::pc(buffer, pc, target)?;
            self.shift_label_pc(pc, offset as u16)?;
        }

        // Extend the buffer with the finished code section.
        buffer.extend_from_slice(&self.code.finish());
        Ok(())
    }
}

/// Relocate program counter to buffer.
///
/// This function takes the original program counter and the target program
/// counter, and updates the provided buffer with the necessary opcode
/// instructions. It ensures that the buffer does not exceed the defined
/// size limit and handles the conversion of the target program counter
/// to the appropriate byte representation.
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();

    // Convert the target program counter to its byte representation.
    let target = target_pc.to_ls_bytes();
    if target.len() == 1 {
        new_buffer.push(OpCode::PUSH1.into());
    } else {
        new_buffer.push(OpCode::PUSH2.into());
    }

    tracing::trace!(
        "push bytes: 0x{} at 0x{}",
        hex::encode(&target),
        hex::encode(original_pc.to_ls_bytes())
    );
    new_buffer.extend_from_slice(&target);
    new_buffer.extend_from_slice(&rest_buffer);

    // Check if the new buffer size exceeds the defined limit.
    if new_buffer.len() > BUFFER_LIMIT {
        return Err(Error::BufferOverflow(new_buffer.len()));
    }

    // Update the original buffer with the new contents.
    *buffer = new_buffer;
    Ok(1 + target.len())
}