Skip to main content

wasmtime_internal_cranelift/
compiled_function.rs

1use std::ops::Range;
2
3use crate::{Relocation, mach_reloc_to_reloc, mach_trap_to_trap};
4use cranelift_codegen::{
5    Final, MachBufferFinalized, MachBufferFrameLayout, MachSrcLoc, ValueLabelsRanges, ir,
6    isa::unwind::CfaUnwindInfo, isa::unwind::UnwindInfo,
7};
8use wasmtime_environ::{
9    FilePos, FrameStateSlotBuilder, InstructionAddressMap, ModulePC, PrimaryMap, TrapInformation,
10    Tunables,
11};
12
13#[derive(Debug, Clone, PartialEq, Eq, Default)]
14/// Metadata to translate from binary offsets back to the original
15/// location found in the wasm input.
16pub struct FunctionAddressMap {
17    /// An array of data for the instructions in this function, indicating where
18    /// each instruction maps back to in the original function.
19    ///
20    /// This array is sorted least-to-greatest by the `code_offset` field.
21    /// Additionally the span of each `InstructionAddressMap` is implicitly the
22    /// gap between it and the next item in the array.
23    pub instructions: Box<[InstructionAddressMap]>,
24
25    /// Function's initial offset in the source file, specified in bytes from
26    /// the front of the file.
27    pub start_srcloc: FilePos,
28
29    /// Function's end offset in the source file, specified in bytes from
30    /// the front of the file.
31    pub end_srcloc: FilePos,
32
33    /// Generated function body offset if applicable, otherwise 0.
34    pub body_offset: usize,
35
36    /// Generated function body length.
37    pub body_len: u32,
38}
39
40/// The metadata for the compiled function.
41#[derive(Default)]
42pub struct CompiledFunctionMetadata {
43    /// The function address map to translate from binary
44    /// back to the original source.
45    pub address_map: FunctionAddressMap,
46    /// The unwind information.
47    pub unwind_info: Option<UnwindInfo>,
48    /// CFA-based unwind information for DWARF debugging support.
49    pub cfa_unwind_info: Option<CfaUnwindInfo>,
50    /// Mapping of value labels and their locations.
51    pub value_labels_ranges: ValueLabelsRanges,
52    /// Start source location.
53    pub start_srcloc: FilePos,
54    /// End source location.
55    pub end_srcloc: FilePos,
56}
57
58/// Compiled function: machine code body, jump table offsets, and unwind information.
59pub struct CompiledFunction {
60    /// The machine code buffer for this function.
61    pub buffer: MachBufferFinalized<Final>,
62    /// What names each name ref corresponds to.
63    name_map: PrimaryMap<ir::UserExternalNameRef, ir::UserExternalName>,
64    /// The alignment for the compiled function.
65    pub alignment: u32,
66    /// The metadata for the compiled function, including unwind information
67    /// the function address map.
68    metadata: CompiledFunctionMetadata,
69    /// Debug metadata for the top-level function's state slot.
70    pub debug_slot_descriptor: Option<FrameStateSlotBuilder>,
71    /// Debug breakpoint patches: module-relative Wasm PC, offset range in buffer.
72    pub breakpoint_patch_points: Vec<(ModulePC, Range<u32>)>,
73}
74
75impl CompiledFunction {
76    /// Creates a [CompiledFunction] from a [`cranelift_codegen::MachBufferFinalized<Final>`]
77    /// This function uses the information in the machine buffer to derive the traps and relocations
78    /// fields. The compiled function metadata is loaded with the default values.
79    pub fn new(
80        buffer: MachBufferFinalized<Final>,
81        name_map: PrimaryMap<ir::UserExternalNameRef, ir::UserExternalName>,
82        alignment: u32,
83    ) -> Self {
84        let mut this = Self {
85            buffer,
86            name_map,
87            alignment,
88            metadata: Default::default(),
89            debug_slot_descriptor: None,
90            breakpoint_patch_points: vec![],
91        };
92        this.finalize_breakpoints();
93
94        this
95    }
96
97    /// Finalize breakpoint patches: edit the buffer to have NOPs by
98    /// default, and place patch data in the debug breakpoint data
99    /// tables.
100    fn finalize_breakpoints(&mut self) {
101        // Traverse debug tags and patchable callsites together. All
102        // patchable callsites should have debug tags. Given both, we
103        // can know the Wasm PC and we can emit a breakpoint record.
104        let mut tags = self.buffer.debug_tags().peekable();
105        let mut patchable_callsites = self.buffer.patchable_call_sites().peekable();
106
107        while let (Some(tag), Some(patchable_callsite)) = (tags.peek(), patchable_callsites.peek())
108        {
109            if tag.offset > patchable_callsite.ret_addr {
110                patchable_callsites.next();
111                continue;
112            }
113            if patchable_callsite.ret_addr > tag.offset {
114                tags.next();
115                continue;
116            }
117            assert_eq!(tag.offset, patchable_callsite.ret_addr);
118
119            // Tag format used by our Wasm-to-CLIF format is
120            // (stackslot, wasm_pc, stack_shape). Taking the
121            // second-to-last tag will get the innermost Wasm PC (if
122            // there are multiple nested frames due to inlining).
123            assert!(tag.tags.len() >= 3);
124            let ir::DebugTag::User(wasm_pc_raw) = tag.tags[tag.tags.len() - 2] else {
125                panic!("invalid tag")
126            };
127
128            let patchable_start = patchable_callsite.ret_addr - patchable_callsite.len;
129            let patchable_end = patchable_callsite.ret_addr;
130
131            self.breakpoint_patch_points
132                .push((ModulePC::new(wasm_pc_raw), patchable_start..patchable_end));
133
134            tags.next();
135            patchable_callsites.next();
136        }
137    }
138
139    /// Returns an iterator to the function's relocation information.
140    pub fn relocations(&self) -> impl Iterator<Item = Relocation> + '_ {
141        self.buffer
142            .relocs()
143            .iter()
144            .map(|r| mach_reloc_to_reloc(r, &self.name_map))
145    }
146
147    /// Returns an iterator to the function's trap information.
148    pub fn traps<'a>(
149        &'a self,
150        tunables: &'a Tunables,
151    ) -> impl Iterator<Item = TrapInformation> + 'a {
152        self.buffer
153            .traps()
154            .iter()
155            .filter_map(move |t| mach_trap_to_trap(t, tunables))
156    }
157
158    /// Get the function's address map from the metadata.
159    pub fn address_map(&self) -> &FunctionAddressMap {
160        &self.metadata.address_map
161    }
162
163    /// Create and return the compiled function address map from the original source offset
164    /// and length.
165    pub fn set_address_map(&mut self, offset: u32, length: u32, with_instruction_addresses: bool) {
166        assert!((offset + length) <= u32::max_value());
167        let len = self.buffer.data().len();
168        let srclocs = self
169            .buffer
170            .get_srclocs_sorted()
171            .into_iter()
172            .map(|&MachSrcLoc { start, end, loc }| (loc, start, (end - start)));
173        let instructions = if with_instruction_addresses {
174            collect_address_maps(len.try_into().unwrap(), srclocs)
175        } else {
176            Default::default()
177        };
178        let start_srcloc = FilePos::new(offset);
179        let end_srcloc = FilePos::new(offset + length);
180
181        let address_map = FunctionAddressMap {
182            instructions: instructions.into(),
183            start_srcloc,
184            end_srcloc,
185            body_offset: 0,
186            body_len: len.try_into().unwrap(),
187        };
188
189        self.metadata.address_map = address_map;
190    }
191
192    /// Get a reference to the unwind information from the
193    /// function's metadata.
194    pub fn unwind_info(&self) -> Option<&UnwindInfo> {
195        self.metadata.unwind_info.as_ref()
196    }
197
198    /// Get a reference to the compiled function metadata.
199    pub fn metadata(&self) -> &CompiledFunctionMetadata {
200        &self.metadata
201    }
202
203    /// Set the value labels ranges in the function's metadata.
204    pub fn set_value_labels_ranges(&mut self, ranges: ValueLabelsRanges) {
205        self.metadata.value_labels_ranges = ranges;
206    }
207
208    /// Set the unwind info in the function's metadata.
209    pub fn set_unwind_info(&mut self, unwind: UnwindInfo) {
210        self.metadata.unwind_info = Some(unwind);
211    }
212
213    /// Set the CFA-based unwind info in the function's metadata.
214    pub fn set_cfa_unwind_info(&mut self, unwind: CfaUnwindInfo) {
215        self.metadata.cfa_unwind_info = Some(unwind);
216    }
217
218    /// Returns the frame-layout metadata for this function.
219    pub fn frame_layout(&self) -> &MachBufferFrameLayout {
220        self.buffer
221            .frame_layout()
222            .expect("Single-function MachBuffer must have frame layout information")
223    }
224
225    /// Returns an iterator over breakpoint patches for this function.
226    ///
227    /// Each tuple is (wasm PC, buffer offset range).
228    pub fn breakpoint_patches(&self) -> impl Iterator<Item = (ModulePC, Range<u32>)> + '_ {
229        self.breakpoint_patch_points.iter().cloned()
230    }
231}
232
233// Collects an iterator of `InstructionAddressMap` into a `Vec` for insertion
234// into a `FunctionAddressMap`. This will automatically coalesce adjacent
235// instructions which map to the same original source position.
236fn collect_address_maps(
237    code_size: u32,
238    iter: impl IntoIterator<Item = (ir::SourceLoc, u32, u32)>,
239) -> Vec<InstructionAddressMap> {
240    let mut iter = iter.into_iter();
241    let (mut cur_loc, mut cur_offset, mut cur_len) = match iter.next() {
242        Some(i) => i,
243        None => return Vec::new(),
244    };
245    let mut ret = Vec::new();
246    for (loc, offset, len) in iter {
247        // If this instruction is adjacent to the previous and has the same
248        // source location then we can "coalesce" it with the current
249        // instruction.
250        if cur_offset + cur_len == offset && loc == cur_loc {
251            cur_len += len;
252            continue;
253        }
254
255        // Push an entry for the previous source item.
256        ret.push(InstructionAddressMap {
257            srcloc: cvt(cur_loc),
258            code_offset: cur_offset,
259        });
260        // And push a "dummy" entry if necessary to cover the span of ranges,
261        // if any, between the previous source offset and this one.
262        if cur_offset + cur_len != offset {
263            ret.push(InstructionAddressMap {
264                srcloc: FilePos::default(),
265                code_offset: cur_offset + cur_len,
266            });
267        }
268        // Update our current location to get extended later or pushed on at
269        // the end.
270        cur_loc = loc;
271        cur_offset = offset;
272        cur_len = len;
273    }
274    ret.push(InstructionAddressMap {
275        srcloc: cvt(cur_loc),
276        code_offset: cur_offset,
277    });
278    if cur_offset + cur_len != code_size {
279        ret.push(InstructionAddressMap {
280            srcloc: FilePos::default(),
281            code_offset: cur_offset + cur_len,
282        });
283    }
284
285    return ret;
286
287    fn cvt(loc: ir::SourceLoc) -> FilePos {
288        if loc.is_default() {
289            FilePos::default()
290        } else {
291            FilePos::new(loc.bits())
292        }
293    }
294}