Skip to main content

wasmtime_environ/
frame_table.rs

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