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, 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: Wasm PC, offset range in buffer. 71 pub breakpoint_patch_points: Vec<(u32, 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. 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. 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