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