1 //! Frame-table parser and lookup logic. 2 //! 3 //! This module contains utilities to interpret the `.wasmtime.frame` 4 //! section in a compiled artifact as produced by 5 //! [`crate::compile::FrameTableBuilder`]. 6 7 use crate::{FuncKey, ModulePC}; 8 use alloc::vec::Vec; 9 use object::{Bytes, LittleEndian, U32}; 10 11 /// An index into the table of stack shapes. 12 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 13 pub struct FrameStackShape(pub(crate) u32); 14 impl FrameStackShape { index(self) -> usize15 pub(crate) fn index(self) -> usize { 16 usize::try_from(self.0).unwrap() 17 } 18 19 /// Get the raw stack-shape index suitable for serializing into 20 /// metadata. raw(self) -> u3221 pub fn raw(self) -> u32 { 22 self.0 23 } 24 25 /// Wrap a raw stack shape index (e.g. from debug tags) into a FrameStackShape. from_raw(index: u32) -> FrameStackShape26 pub fn from_raw(index: u32) -> FrameStackShape { 27 FrameStackShape(index) 28 } 29 } 30 31 /// An index to a frame descriptor that can be referenced from a 32 /// program point descriptor. 33 #[derive(Clone, Copy, Debug)] 34 pub struct FrameTableDescriptorIndex(pub(crate) u32); 35 impl FrameTableDescriptorIndex { index(self) -> usize36 fn index(self) -> usize { 37 usize::try_from(self.0).unwrap() 38 } 39 } 40 41 /// A parser for a frame-table section. 42 /// 43 /// This parser holds slices to the in-memory section data, and is 44 /// cheap to construct: it reads some header fields but does not 45 /// interpret or validate content data until queried. 46 pub struct FrameTable<'a> { 47 frame_descriptor_ranges: &'a [U32<LittleEndian>], 48 frame_descriptor_data: &'a [u8], 49 50 frame_descriptor_fp_offsets: &'a [U32<LittleEndian>], 51 52 progpoint_pcs: &'a [U32<LittleEndian>], 53 progpoint_descriptor_offsets: &'a [U32<LittleEndian>], 54 progpoint_descriptor_data: &'a [U32<LittleEndian>], 55 56 breakpoint_pcs: &'a [U32<LittleEndian>], 57 breakpoint_patch_offsets: &'a [U32<LittleEndian>], 58 breakpoint_patch_data_ends: &'a [U32<LittleEndian>], 59 breakpoint_patch_data: &'a [u8], 60 61 original_text: &'a [u8], 62 } 63 64 impl<'a> FrameTable<'a> { 65 /// Parse a frame table section from a byte-slice as produced by 66 /// [`crate::compile::FrameTableBuilder`]. parse(data: &'a [u8], original_text: &'a [u8]) -> anyhow::Result<FrameTable<'a>>67 pub fn parse(data: &'a [u8], original_text: &'a [u8]) -> anyhow::Result<FrameTable<'a>> { 68 let mut data = Bytes(data); 69 let num_frame_descriptors = data 70 .read::<U32<LittleEndian>>() 71 .map_err(|_| anyhow::anyhow!("Unable to read frame descriptor count prefix"))?; 72 let num_frame_descriptors = usize::try_from(num_frame_descriptors.get(LittleEndian))?; 73 let num_progpoint_descriptors = data 74 .read::<U32<LittleEndian>>() 75 .map_err(|_| anyhow::anyhow!("Unable to read progpoint descriptor count prefix"))?; 76 let num_progpoint_descriptors = 77 usize::try_from(num_progpoint_descriptors.get(LittleEndian))?; 78 let num_breakpoints = data 79 .read::<U32<LittleEndian>>() 80 .map_err(|_| anyhow::anyhow!("Unable to read breakpoint count prefix"))?; 81 let num_breakpoints = usize::try_from(num_breakpoints.get(LittleEndian))?; 82 83 let frame_descriptor_pool_length = data 84 .read::<U32<LittleEndian>>() 85 .map_err(|_| anyhow::anyhow!("Unable to read frame descriptor pool length"))?; 86 let frame_descriptor_pool_length = 87 usize::try_from(frame_descriptor_pool_length.get(LittleEndian))?; 88 let progpoint_descriptor_pool_length = data 89 .read::<U32<LittleEndian>>() 90 .map_err(|_| anyhow::anyhow!("Unable to read progpoint descriptor pool length"))?; 91 let progpoint_descriptor_pool_length = 92 usize::try_from(progpoint_descriptor_pool_length.get(LittleEndian))?; 93 let breakpoint_patch_pool_length = data 94 .read::<U32<LittleEndian>>() 95 .map_err(|_| anyhow::anyhow!("Unable to read breakpoint patch pool length"))?; 96 let breakpoint_patch_pool_length = 97 usize::try_from(breakpoint_patch_pool_length.get(LittleEndian))?; 98 99 let (frame_descriptor_ranges, data) = 100 object::slice_from_bytes::<U32<LittleEndian>>(data.0, 2 * num_frame_descriptors) 101 .map_err(|_| anyhow::anyhow!("Unable to read frame descriptor ranges slice"))?; 102 let (frame_descriptor_fp_offsets, data) = 103 object::slice_from_bytes::<U32<LittleEndian>>(data, num_frame_descriptors) 104 .map_err(|_| anyhow::anyhow!("Unable to read frame descriptor FP offset slice"))?; 105 106 let (progpoint_pcs, data) = 107 object::slice_from_bytes::<U32<LittleEndian>>(data, num_progpoint_descriptors) 108 .map_err(|_| anyhow::anyhow!("Unable to read progpoint PC slice"))?; 109 let (progpoint_descriptor_offsets, data) = 110 object::slice_from_bytes::<U32<LittleEndian>>(data, num_progpoint_descriptors) 111 .map_err(|_| anyhow::anyhow!("Unable to read progpoint descriptor offset slice"))?; 112 let (breakpoint_pcs, data) = 113 object::slice_from_bytes::<U32<LittleEndian>>(data, num_breakpoints) 114 .map_err(|_| anyhow::anyhow!("Unable to read breakpoint PC slice"))?; 115 let (breakpoint_patch_offsets, data) = 116 object::slice_from_bytes::<U32<LittleEndian>>(data, num_breakpoints) 117 .map_err(|_| anyhow::anyhow!("Unable to read breakpoint patch offsets slice"))?; 118 let (breakpoint_patch_data_ends, data) = 119 object::slice_from_bytes::<U32<LittleEndian>>(data, num_breakpoints) 120 .map_err(|_| anyhow::anyhow!("Unable to read breakpoint patch data ends slice"))?; 121 122 let (frame_descriptor_data, data) = data 123 .split_at_checked(frame_descriptor_pool_length) 124 .ok_or_else(|| anyhow::anyhow!("Unable to read frame descriptor pool"))?; 125 126 let (progpoint_descriptor_data, data) = 127 object::slice_from_bytes::<U32<LittleEndian>>(data, progpoint_descriptor_pool_length) 128 .map_err(|_| anyhow::anyhow!("Unable to read progpoint descriptor pool"))?; 129 130 let (breakpoint_patch_data, _) = data 131 .split_at_checked(breakpoint_patch_pool_length) 132 .ok_or_else(|| anyhow::anyhow!("Unable to read breakpoint patch pool"))?; 133 134 Ok(FrameTable { 135 frame_descriptor_ranges, 136 frame_descriptor_data, 137 frame_descriptor_fp_offsets, 138 progpoint_pcs, 139 progpoint_descriptor_offsets, 140 progpoint_descriptor_data, 141 breakpoint_pcs, 142 breakpoint_patch_offsets, 143 breakpoint_patch_data_ends, 144 breakpoint_patch_data, 145 original_text, 146 }) 147 } 148 149 /// Get raw frame descriptor data and slot-to-FP-offset for a 150 /// given frame descriptor. frame_descriptor( &self, frame_descriptor: FrameTableDescriptorIndex, ) -> Option<(&'a [u8], u32)>151 pub fn frame_descriptor( 152 &self, 153 frame_descriptor: FrameTableDescriptorIndex, 154 ) -> Option<(&'a [u8], u32)> { 155 let range_start = self 156 .frame_descriptor_ranges 157 .get(frame_descriptor.index() * 2)? 158 .get(LittleEndian); 159 let range_end = self 160 .frame_descriptor_ranges 161 .get(frame_descriptor.index() * 2 + 1)? 162 .get(LittleEndian); 163 let range_start = usize::try_from(range_start).unwrap(); 164 let range_end = usize::try_from(range_end).unwrap(); 165 if range_end < range_start || range_end > self.frame_descriptor_data.len() { 166 return None; 167 } 168 let descriptor = &self.frame_descriptor_data[range_start..range_end]; 169 let slot_to_fp_offset = self 170 .frame_descriptor_fp_offsets 171 .get(frame_descriptor.index())? 172 .get(LittleEndian); 173 Some((descriptor, slot_to_fp_offset)) 174 } 175 176 /// Get frames for the program point at the PC upper-bounded by a 177 /// given search PC (offset in text section). find_program_point( &self, search_pc: u32, search_pos: FrameInstPos, ) -> Option<impl Iterator<Item = (ModulePC, FrameTableDescriptorIndex, FrameStackShape)>>178 pub fn find_program_point( 179 &self, 180 search_pc: u32, 181 search_pos: FrameInstPos, 182 ) -> Option<impl Iterator<Item = (ModulePC, FrameTableDescriptorIndex, FrameStackShape)>> { 183 let key = FrameInstPos::encode(search_pc, search_pos); 184 let index = match self 185 .progpoint_pcs 186 .binary_search_by_key(&key, |entry| entry.get(LittleEndian)) 187 { 188 Ok(idx) => idx, 189 Err(idx) if idx > 0 => idx - 1, 190 Err(_) => return None, 191 }; 192 193 Some(self.program_point_frame_iter(index)) 194 } 195 196 /// Get all program point records with iterators over 197 /// corresponding frames for each. into_program_points( self, ) -> impl Iterator< Item = ( u32, FrameInstPos, Vec<(ModulePC, FrameTableDescriptorIndex, FrameStackShape)>, ), > + 'a198 pub fn into_program_points( 199 self, 200 ) -> impl Iterator< 201 Item = ( 202 u32, 203 FrameInstPos, 204 Vec<(ModulePC, FrameTableDescriptorIndex, FrameStackShape)>, 205 ), 206 > + 'a { 207 self.progpoint_pcs.iter().enumerate().map(move |(i, pc)| { 208 let pc_and_pos = pc.get(LittleEndian); 209 let (pc, pos) = FrameInstPos::decode(pc_and_pos); 210 ( 211 pc, 212 pos, 213 self.program_point_frame_iter(i).collect::<Vec<_>>(), 214 ) 215 }) 216 } 217 program_point_frame_iter( &self, index: usize, ) -> impl Iterator<Item = (ModulePC, FrameTableDescriptorIndex, FrameStackShape)>218 fn program_point_frame_iter( 219 &self, 220 index: usize, 221 ) -> impl Iterator<Item = (ModulePC, FrameTableDescriptorIndex, FrameStackShape)> { 222 let offset = 223 usize::try_from(self.progpoint_descriptor_offsets[index].get(LittleEndian)).unwrap(); 224 let mut data = &self.progpoint_descriptor_data[offset..]; 225 226 core::iter::from_fn(move || { 227 if data.len() < 3 { 228 return None; 229 } 230 let wasm_pc_raw = data[0].get(LittleEndian); 231 let frame_descriptor = FrameTableDescriptorIndex(data[1].get(LittleEndian)); 232 let stack_shape = FrameStackShape(data[2].get(LittleEndian)); 233 data = &data[3..]; 234 let not_last = wasm_pc_raw & 0x8000_0000 != 0; 235 let wasm_pc = ModulePC::new(wasm_pc_raw & 0x7fff_ffff); 236 if !not_last { 237 data = &[]; 238 } 239 Some((wasm_pc, frame_descriptor, stack_shape)) 240 }) 241 } 242 243 /// For a given breakpoint index, return the patch offset in text, 244 /// the patch data, and the original data. breakpoint_patch(&self, i: usize) -> FrameTableBreakpointData<'_>245 fn breakpoint_patch(&self, i: usize) -> FrameTableBreakpointData<'_> { 246 let patch_pool_start = if i == 0 { 247 0 248 } else { 249 self.breakpoint_patch_data_ends[i - 1].get(LittleEndian) 250 }; 251 let patch_pool_end = self.breakpoint_patch_data_ends[i].get(LittleEndian); 252 let patch_pool_start = usize::try_from(patch_pool_start).unwrap(); 253 let patch_pool_end = usize::try_from(patch_pool_end).unwrap(); 254 let len = patch_pool_end - patch_pool_start; 255 let offset = self.breakpoint_patch_offsets[i].get(LittleEndian); 256 let offset = usize::try_from(offset).unwrap(); 257 let original_data = &self.original_text[offset..offset + len]; 258 FrameTableBreakpointData { 259 offset, 260 enable: &self.breakpoint_patch_data[patch_pool_start..patch_pool_end], 261 disable: original_data, 262 } 263 } 264 265 /// Find a list of breakpoint patches for a given Wasm PC. lookup_breakpoint_patches_by_pc( &self, pc: ModulePC, ) -> impl Iterator<Item = FrameTableBreakpointData<'_>> + '_266 pub fn lookup_breakpoint_patches_by_pc( 267 &self, 268 pc: ModulePC, 269 ) -> impl Iterator<Item = FrameTableBreakpointData<'_>> + '_ { 270 // Find *some* entry with a matching Wasm PC. Note that there 271 // may be multiple entries for one PC. 272 let pc_raw = pc.raw(); 273 let range = match self 274 .breakpoint_pcs 275 .binary_search_by_key(&pc_raw, |p| p.get(LittleEndian)) 276 { 277 Ok(mut i) => { 278 // Scan backward to first index with this PC. 279 while i > 0 && self.breakpoint_pcs[i - 1].get(LittleEndian) == pc_raw { 280 i -= 1; 281 } 282 283 // Scan forward to find the end of the range. 284 let mut end = i; 285 while end < self.breakpoint_pcs.len() 286 && self.breakpoint_pcs[end].get(LittleEndian) == pc_raw 287 { 288 end += 1; 289 } 290 291 i..end 292 } 293 Err(_) => 0..0, 294 }; 295 296 range.map(|i| self.breakpoint_patch(i)) 297 } 298 299 /// Find the nearest breakpoint PC at or after the given PC. nearest_breakpoint(&self, pc: ModulePC) -> Option<ModulePC>300 pub fn nearest_breakpoint(&self, pc: ModulePC) -> Option<ModulePC> { 301 match self 302 .breakpoint_pcs 303 .binary_search_by_key(&pc.raw(), |p| p.get(LittleEndian)) 304 { 305 Ok(_) => Some(pc), 306 Err(i) => { 307 if i < self.breakpoint_pcs.len() { 308 Some(ModulePC::new(self.breakpoint_pcs[i].get(LittleEndian))) 309 } else { 310 None 311 } 312 } 313 } 314 } 315 316 /// Return an iterator over all breakpoint patches. 317 /// 318 /// Returned tuples are (module-relative Wasm PC, breakpoint data). breakpoint_patches( &self, ) -> impl Iterator<Item = (ModulePC, FrameTableBreakpointData<'_>)> + '_319 pub fn breakpoint_patches( 320 &self, 321 ) -> impl Iterator<Item = (ModulePC, FrameTableBreakpointData<'_>)> + '_ { 322 self.breakpoint_pcs.iter().enumerate().map(|(i, wasm_pc)| { 323 let wasm_pc = ModulePC::new(wasm_pc.get(LittleEndian)); 324 let data = self.breakpoint_patch(i); 325 (wasm_pc, data) 326 }) 327 } 328 } 329 330 /// Data describing how to patch code to enable or disable one 331 /// breakpoint. 332 pub struct FrameTableBreakpointData<'a> { 333 /// Offset in the code image's text section. 334 pub offset: usize, 335 /// Code bytes to patch in to enable the breakpoint. 336 pub enable: &'a [u8], 337 /// Code bytes to patch in to disable the breakpoint. 338 pub disable: &'a [u8], 339 } 340 341 /// An instruction position for a program point. 342 /// 343 /// We attach debug metadata to a *position* on an offset in the text 344 /// (code) section, either "post" or "pre". The "post" position 345 /// logically comes first, and is associated with the instruction that 346 /// ends at this offset (i.e., the previous instruction). The "pre" 347 /// position comes next, and is associated with the instruction that 348 /// begins at this offset (i.e., the next instruction). 349 /// 350 /// We make this distinction because metadata lookups sometimes occur 351 /// with a PC that is after the instruction (e.g., the return address 352 /// after a call instruction), and sometimes at the instruction (e.g., 353 /// a trapping PC address). The lookup context will know which one to 354 /// use -- e.g., when walking the stack, "pre" for a trapping PC and 355 /// "post" for every frame after that -- so we simply encode it as 356 /// part of the position and allow searching on it. 357 /// 358 /// The need for this distinction can be understood by way of an 359 /// example; say we have: 360 /// 361 /// ```plain 362 /// call ... 363 /// trapping_store ... 364 /// ``` 365 /// 366 /// where both instructions have debug metadata. We might look up the 367 /// PC of `trapping_store` once as we walk the stack from within the 368 /// call (we will get this PC because it is the return address) and 369 /// once when `trapping_store` itself traps; and we want different 370 /// metadata in each case. 371 /// 372 /// An alternative is to universally attach tags to the end offset of 373 /// an instruction, which allows us to handle return addresses 374 /// naturally but requires traps to adjust their PC. However, this 375 /// requires trap handlers to know the length of the trapping 376 /// instruction, which is not always easy -- in the most general case, 377 /// on variable-length instruction sets, it requires a full 378 /// instruction decoder. 379 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 380 pub enum FrameInstPos { 381 /// The "post" position at an offset attaches to the instruction 382 /// that ends at this offset, i.e., came previously. 383 Post, 384 /// The "pre" position at an offset attaches to the instruction 385 /// that begins at this offset, i.e., comes next. 386 Pre, 387 } 388 389 impl FrameInstPos { encode(pc: u32, pos: FrameInstPos) -> u32390 pub(crate) fn encode(pc: u32, pos: FrameInstPos) -> u32 { 391 let lsb = match pos { 392 Self::Post => 0, 393 Self::Pre => 1, 394 }; 395 debug_assert!(pc < 0x8000_0000); 396 (pc << 1) | lsb 397 } decode(bits: u32) -> (u32, FrameInstPos)398 pub(crate) fn decode(bits: u32) -> (u32, FrameInstPos) { 399 let pos = match bits & 1 { 400 0 => Self::Post, 401 1 => Self::Pre, 402 _ => unreachable!(), 403 }; 404 let pc = bits >> 1; 405 (pc, pos) 406 } 407 } 408 409 /// An offset into the state slot. 410 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 411 pub struct FrameStateSlotOffset(pub(crate) u32); 412 impl FrameStateSlotOffset { 413 #[cfg(feature = "compile")] add(self, offset: u32) -> FrameStateSlotOffset414 pub(crate) fn add(self, offset: u32) -> FrameStateSlotOffset { 415 FrameStateSlotOffset(self.0 + offset) 416 } 417 418 /// Get the offset into the state stackslot, suitable for use in a 419 /// `stack_store`/`stack_load` instruction. offset(self) -> i32420 pub fn offset(self) -> i32 { 421 i32::try_from(self.0).unwrap() 422 } 423 } 424 425 /// A type stored in a frame. 426 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 427 #[allow(missing_docs, reason = "self-describing variants")] 428 pub enum FrameValType { 429 I32, 430 I64, 431 F32, 432 F64, 433 V128, 434 AnyRef, 435 FuncRef, 436 ExternRef, 437 ExnRef, 438 ContRef, 439 } 440 441 impl FrameValType { 442 #[cfg(feature = "compile")] storage_size(&self, pointer_size: u32) -> u32443 pub(crate) fn storage_size(&self, pointer_size: u32) -> u32 { 444 match self { 445 FrameValType::I32 => 4, 446 FrameValType::I64 => 8, 447 FrameValType::F32 => 4, 448 FrameValType::F64 => 8, 449 FrameValType::V128 => 16, 450 FrameValType::AnyRef | FrameValType::ExternRef | FrameValType::ExnRef => 4, 451 FrameValType::FuncRef => pointer_size, 452 FrameValType::ContRef => 2 * pointer_size, 453 } 454 } 455 } 456 457 impl From<FrameValType> for u8 { from(value: FrameValType) -> u8458 fn from(value: FrameValType) -> u8 { 459 match value { 460 FrameValType::I32 => 0, 461 FrameValType::I64 => 1, 462 FrameValType::F32 => 2, 463 FrameValType::F64 => 3, 464 FrameValType::V128 => 4, 465 FrameValType::AnyRef => 5, 466 FrameValType::FuncRef => 6, 467 FrameValType::ExternRef => 7, 468 FrameValType::ExnRef => 8, 469 FrameValType::ContRef => 9, 470 } 471 } 472 } 473 474 impl TryFrom<u8> for FrameValType { 475 type Error = anyhow::Error; try_from(value: u8) -> anyhow::Result<Self>476 fn try_from(value: u8) -> anyhow::Result<Self> { 477 match value { 478 0 => Ok(Self::I32), 479 1 => Ok(Self::I64), 480 2 => Ok(Self::F32), 481 3 => Ok(Self::F64), 482 4 => Ok(Self::V128), 483 5 => Ok(Self::AnyRef), 484 6 => Ok(Self::FuncRef), 485 7 => Ok(Self::ExternRef), 486 8 => Ok(Self::ExnRef), 487 9 => Ok(Self::ContRef), 488 _ => Err(anyhow::anyhow!("Invalid type")), 489 } 490 } 491 } 492 493 /// Parser for a frame state slot descriptor. 494 /// 495 /// This provides the ability to extract offsets and types for locals 496 /// and for the stack given a stack shape. 497 pub struct FrameStateSlot<'a> { 498 func_key: FuncKey, 499 local_offsets: &'a [U32<LittleEndian>], 500 stack_shape_parents: &'a [U32<LittleEndian>], 501 stack_shape_offsets: &'a [U32<LittleEndian>], 502 local_types: &'a [u8], 503 stack_shape_types: &'a [u8], 504 } 505 506 impl<'a> FrameStateSlot<'a> { 507 /// Parse a slot descriptor. 508 /// 509 /// This parses the descriptor bytes as provided by 510 /// [`FrameTable::frame_descriptor`]. parse(descriptor: &'a [u8]) -> anyhow::Result<FrameStateSlot<'a>>511 pub fn parse(descriptor: &'a [u8]) -> anyhow::Result<FrameStateSlot<'a>> { 512 let mut data = Bytes(descriptor); 513 let func_key_namespace = data 514 .read::<U32<LittleEndian>>() 515 .map_err(|_| anyhow::anyhow!("Unable to read func key namespace"))? 516 .get(LittleEndian); 517 let func_key_index = data 518 .read::<U32<LittleEndian>>() 519 .map_err(|_| anyhow::anyhow!("Unable to read func key index"))? 520 .get(LittleEndian); 521 let func_key = FuncKey::from_raw_parts(func_key_namespace, func_key_index); 522 523 let num_locals = data 524 .read::<U32<LittleEndian>>() 525 .map_err(|_| anyhow::anyhow!("Unable to read num_locals"))? 526 .get(LittleEndian); 527 let num_locals = usize::try_from(num_locals)?; 528 let num_stack_shapes = data 529 .read::<U32<LittleEndian>>() 530 .map_err(|_| anyhow::anyhow!("Unable to read num_stack_shapes"))? 531 .get(LittleEndian); 532 let num_stack_shapes = usize::try_from(num_stack_shapes)?; 533 534 let (local_offsets, data) = 535 object::slice_from_bytes::<U32<LittleEndian>>(data.0, num_locals) 536 .map_err(|_| anyhow::anyhow!("Unable to read local_offsets slice"))?; 537 let (stack_shape_parents, data) = 538 object::slice_from_bytes::<U32<LittleEndian>>(data, num_stack_shapes) 539 .map_err(|_| anyhow::anyhow!("Unable to read stack_shape_parents slice"))?; 540 let (stack_shape_offsets, data) = 541 object::slice_from_bytes::<U32<LittleEndian>>(data, num_stack_shapes) 542 .map_err(|_| anyhow::anyhow!("Unable to read stack_shape_offsets slice"))?; 543 let (local_types, data) = data 544 .split_at_checked(num_locals) 545 .ok_or_else(|| anyhow::anyhow!("Unable to read local_types slice"))?; 546 let (stack_shape_types, _) = data 547 .split_at_checked(num_stack_shapes) 548 .ok_or_else(|| anyhow::anyhow!("Unable to read stack_shape_types slice"))?; 549 550 Ok(FrameStateSlot { 551 func_key, 552 local_offsets, 553 stack_shape_parents, 554 stack_shape_offsets, 555 local_types, 556 stack_shape_types, 557 }) 558 } 559 560 /// Get the FuncKey for the function that produced this frame 561 /// slot. func_key(&self) -> FuncKey562 pub fn func_key(&self) -> FuncKey { 563 self.func_key 564 } 565 566 /// Get the local offsets and types. locals(&self) -> impl Iterator<Item = (FrameStateSlotOffset, FrameValType)>567 pub fn locals(&self) -> impl Iterator<Item = (FrameStateSlotOffset, FrameValType)> { 568 (0..self.num_locals()).map(|i| self.local(i).unwrap()) 569 } 570 571 /// Get the type and offset for a given local. local(&self, index: usize) -> Option<(FrameStateSlotOffset, FrameValType)>572 pub fn local(&self, index: usize) -> Option<(FrameStateSlotOffset, FrameValType)> { 573 let offset = FrameStateSlotOffset(self.local_offsets.get(index)?.get(LittleEndian)); 574 let ty = FrameValType::try_from(*self.local_types.get(index)?).expect("Invalid type"); 575 Some((offset, ty)) 576 } 577 578 /// Get the number of locals in the frame. num_locals(&self) -> usize579 pub fn num_locals(&self) -> usize { 580 self.local_offsets.len() 581 } 582 583 /// Get the offsets and types for operand stack values, from top 584 /// of stack (most recently pushed) down. stack( &self, shape: FrameStackShape, ) -> impl Iterator<Item = (FrameStateSlotOffset, FrameValType)>585 pub fn stack( 586 &self, 587 shape: FrameStackShape, 588 ) -> impl Iterator<Item = (FrameStateSlotOffset, FrameValType)> { 589 fn unpack_option_shape(shape: FrameStackShape) -> Option<FrameStackShape> { 590 if shape.0 == u32::MAX { 591 None 592 } else { 593 Some(shape) 594 } 595 } 596 597 let mut shape = unpack_option_shape(shape); 598 core::iter::from_fn(move || { 599 shape.map(|s| { 600 let parent = FrameStackShape(self.stack_shape_parents[s.index()].get(LittleEndian)); 601 let parent = unpack_option_shape(parent); 602 let offset = 603 FrameStateSlotOffset(self.stack_shape_offsets[s.index()].get(LittleEndian)); 604 let ty = FrameValType::try_from(self.stack_shape_types[s.index()]) 605 .expect("Invalid type"); 606 shape = parent; 607 (offset, ty) 608 }) 609 }) 610 } 611 612 /// Returns an iterator over all storage in this frame. stack_and_locals( &self, shape: FrameStackShape, ) -> impl Iterator<Item = (FrameStateSlotOffset, FrameValType)> + '_613 pub fn stack_and_locals( 614 &self, 615 shape: FrameStackShape, 616 ) -> impl Iterator<Item = (FrameStateSlotOffset, FrameValType)> + '_ { 617 self.locals().chain(self.stack(shape)) 618 } 619 } 620