1 use std::ops::Range;
2 
3 use crate::{Relocation, mach_reloc_to_reloc, mach_trap_to_trap};
4 use cranelift_codegen::{
5     Final, MachBufferFinalized, MachBufferFrameLayout, MachSrcLoc, ValueLabelsRanges, ir,
6     isa::unwind::CfaUnwindInfo, isa::unwind::UnwindInfo,
7 };
8 use wasmtime_environ::{
9     FilePos, FrameStateSlotBuilder, InstructionAddressMap, ModulePC, 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.
15 pub 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)]
41 pub 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.
58 pub 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: module-relative Wasm PC, offset range in buffer.
71     pub breakpoint_patch_points: Vec<(ModulePC, Range<u32>)>,
72 }
73 
74 impl 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.
new( buffer: MachBufferFinalized<Final>, name_map: PrimaryMap<ir::UserExternalNameRef, ir::UserExternalName>, alignment: u32, ) -> Self78     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.
finalize_breakpoints(&mut self)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_raw) = 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((ModulePC::new(wasm_pc_raw), 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.
relocations(&self) -> impl Iterator<Item = Relocation> + '_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.
traps(&self) -> impl Iterator<Item = TrapInformation> + '_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.
address_map(&self) -> &FunctionAddressMap152     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.
set_address_map(&mut self, offset: u32, length: u32, with_instruction_addresses: bool)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.
unwind_info(&self) -> Option<&UnwindInfo>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.
metadata(&self) -> &CompiledFunctionMetadata192     pub fn metadata(&self) -> &CompiledFunctionMetadata {
193         &self.metadata
194     }
195 
196     /// Set the value labels ranges in the function's metadata.
set_value_labels_ranges(&mut self, ranges: ValueLabelsRanges)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.
set_unwind_info(&mut self, unwind: UnwindInfo)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.
set_cfa_unwind_info(&mut self, unwind: CfaUnwindInfo)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.
frame_layout(&self) -> &MachBufferFrameLayout212     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).
breakpoint_patches(&self) -> impl Iterator<Item = (ModulePC, Range<u32>)> + '_221     pub fn breakpoint_patches(&self) -> impl Iterator<Item = (ModulePC, 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.
collect_address_maps( code_size: u32, iter: impl IntoIterator<Item = (ir::SourceLoc, u32, u32)>, ) -> Vec<InstructionAddressMap>229 fn 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 }
288