Skip to main content

tidepool_codegen/
stack_map.rs

1use std::collections::BTreeMap;
2
3/// Information about GC roots at a single safepoint.
4#[derive(Debug, Clone)]
5pub struct StackMapInfo {
6    /// Size of the frame in bytes (span from user_stack_maps tuple).
7    pub frame_size: u32,
8    /// SP-relative offsets of heap pointer slots.
9    /// root_addr = SP + offset at the safepoint.
10    pub offsets: Vec<u32>,
11}
12
13pub type RawStackMapEntry = (cranelift_codegen::ir::types::Type, u32);
14pub type RawStackMap = (u32, u32, Vec<RawStackMapEntry>);
15
16/// Maps absolute return addresses to stack map info.
17///
18/// Key = function_base_ptr + code_offset
19/// (i.e., the return address, which is what the frame walker sees as caller_pc).
20/// Cranelift's `code_offset` for user stack maps already points to the
21/// instruction AFTER the call (the return point).
22#[derive(Debug, Default)]
23pub struct StackMapRegistry {
24    entries: BTreeMap<usize, StackMapInfo>,
25    /// Known JIT function address ranges (start, end).
26    ranges: Vec<(usize, usize)>,
27}
28
29impl StackMapRegistry {
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Register stack map entries from a compiled function.
35    ///
36    /// `base_ptr` is the start address of the compiled function in memory.
37    /// `size` is the total size of the function in bytes.
38    /// `raw_entries` come from `CompiledCode.buffer.user_stack_maps()`:
39    ///   each tuple is (code_offset, frame_size, UserStackMap).
40    ///
41    /// We key by `base_ptr + code_offset` as the return address. Cranelift's
42    /// `code_offset` for user stack maps points to the instruction AFTER the call
43    /// (the return point), so `base_ptr + code_offset` IS the absolute return address.
44    pub fn register(&mut self, base_ptr: usize, size: u32, raw_entries: &[RawStackMap]) {
45        self.ranges.push((base_ptr, base_ptr + size as usize));
46
47        for (code_offset, frame_size, slot_entries) in raw_entries {
48            let return_addr = base_ptr + *code_offset as usize;
49            let offsets: Vec<u32> = slot_entries.iter().map(|(_, offset)| *offset).collect();
50            self.entries.insert(
51                return_addr,
52                StackMapInfo {
53                    frame_size: *frame_size,
54                    offsets,
55                },
56            );
57        }
58    }
59
60    /// Look up stack map info by return address (PC value from frame walker).
61    pub fn lookup(&self, return_addr: usize) -> Option<&StackMapInfo> {
62        self.entries.get(&return_addr)
63    }
64
65    /// Number of registered safepoints.
66    pub fn len(&self) -> usize {
67        self.entries.len()
68    }
69
70    /// Whether the registry is empty.
71    pub fn is_empty(&self) -> bool {
72        self.entries.is_empty()
73    }
74
75    /// Check if an address falls within the known JIT code region.
76    /// Used by the frame walker to determine when to stop walking.
77    pub fn contains_address(&self, addr: usize) -> bool {
78        self.ranges
79            .iter()
80            .any(|(start, end)| addr >= *start && addr < *end)
81    }
82}