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