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