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