wasmtime_environ/compile/
frame_table.rs

1//! Builder for the `ELF_WASMTIME_FRAME_TABLE` ("frame table") section
2//! in compiled executables.
3//!
4//! This section is present only if debug instrumentation is
5//! enabled. It describes functions, stackslots that carry Wasm state,
6//! and allows looking up active Wasm frames (including multiple
7//! frames in one function due to inlining), Wasm local types and Wasm
8//! operand stack depth in each frame by PC, with offsets to read
9//! those values off of the state in the stack frame.
10
11use crate::{
12    FrameInstPos, FrameStackShape, FrameStateSlotOffset, FrameTableDescriptorIndex, FrameValType,
13    FuncKey, WasmHeapTopType, WasmValType, prelude::*,
14};
15use object::{LittleEndian, U32Bytes};
16use std::collections::{HashMap, hash_map::Entry};
17
18/// Builder for a stackslot descriptor.
19pub struct FrameStateSlotBuilder {
20    /// Function identifier for this state slot.
21    func_key: FuncKey,
22
23    /// Pointer size for target.
24    pointer_size: u32,
25
26    /// Local types and offsets.
27    locals: Vec<(FrameValType, FrameStateSlotOffset)>,
28
29    /// Stack nodes: (parent, type, offset) tuples.
30    stacks: Vec<(Option<FrameStackShape>, FrameValType, FrameStateSlotOffset)>,
31
32    /// Hashconsing for stack-type nodes.
33    stacks_dedup:
34        HashMap<(Option<FrameStackShape>, FrameValType, FrameStateSlotOffset), FrameStackShape>,
35
36    /// Size of vmctx (one pointer).
37    vmctx_size: u32,
38
39    /// Size of all locals.
40    locals_size: u32,
41
42    /// Maximum size of whole state slot.
43    slot_size: u32,
44}
45
46impl From<WasmValType> for FrameValType {
47    fn from(ty: WasmValType) -> FrameValType {
48        match ty {
49            WasmValType::I32 => FrameValType::I32,
50            WasmValType::I64 => FrameValType::I64,
51            WasmValType::F32 => FrameValType::F32,
52            WasmValType::F64 => FrameValType::F64,
53            WasmValType::V128 => FrameValType::V128,
54            WasmValType::Ref(r) => match r.heap_type.top() {
55                WasmHeapTopType::Any => FrameValType::AnyRef,
56                WasmHeapTopType::Extern => FrameValType::ExternRef,
57                WasmHeapTopType::Func => FrameValType::FuncRef,
58                WasmHeapTopType::Exn => FrameValType::ExnRef,
59                WasmHeapTopType::Cont => FrameValType::ContRef,
60            },
61        }
62    }
63}
64
65impl FrameStateSlotBuilder {
66    /// Create a new state-slot builder.
67    pub fn new(func_key: FuncKey, pointer_size: u32) -> FrameStateSlotBuilder {
68        FrameStateSlotBuilder {
69            func_key,
70            pointer_size,
71            locals: vec![],
72            stacks: vec![],
73            stacks_dedup: HashMap::new(),
74            vmctx_size: pointer_size,
75            locals_size: 0,
76            slot_size: pointer_size,
77        }
78    }
79
80    /// Add a local to the state-slot.
81    ///
82    /// Locals must be added in local index order, and must be added
83    /// before any stack shapes are defined. The offset in the state
84    /// slot is returned.
85    pub fn add_local(&mut self, ty: FrameValType) -> FrameStateSlotOffset {
86        // N.B.: the vmctx pointer is always at offset 0, so we add
87        // its size here.
88        let offset = FrameStateSlotOffset(self.vmctx_size + self.locals_size);
89        let size = ty.storage_size(self.pointer_size);
90        self.locals_size += size;
91        self.slot_size += size;
92        self.locals.push((ty, offset));
93        offset
94    }
95
96    /// Get a local's offset in the state-slot.
97    pub fn local_offset(&self, local: u32) -> FrameStateSlotOffset {
98        let index = usize::try_from(local).unwrap();
99        self.locals[index].1
100    }
101
102    /// Push a stack entry. Returns the stack-shape descriptor and the
103    /// offset at which to write the pushed value.
104    pub fn push_stack(
105        &mut self,
106        parent: Option<FrameStackShape>,
107        ty: FrameValType,
108    ) -> (FrameStackShape, FrameStateSlotOffset) {
109        let offset = parent
110            .map(|parent| {
111                let (_, ty, offset) = self.stacks[parent.index()];
112                offset.add(ty.storage_size(self.pointer_size))
113            })
114            // N.B.: the stack starts at vmctx_size + locals_size,
115            // because the layout puts vmctx first, then locals, then
116            // stack.
117            .unwrap_or(FrameStateSlotOffset(self.vmctx_size + self.locals_size));
118
119        self.slot_size = core::cmp::max(
120            self.slot_size,
121            offset.0 + ty.storage_size(self.pointer_size),
122        );
123
124        let shape = match self.stacks_dedup.entry((parent, ty, offset)) {
125            Entry::Occupied(o) => *o.get(),
126            Entry::Vacant(v) => {
127                let shape = FrameStackShape(u32::try_from(self.stacks.len()).unwrap());
128                self.stacks.push((parent, ty, offset));
129                *v.insert(shape)
130            }
131        };
132
133        (shape, offset)
134    }
135
136    /// Get the offset for the top slot in a given stack shape.
137    pub fn stack_last_offset(&self, shape: FrameStackShape) -> FrameStateSlotOffset {
138        self.stacks[shape.index()].2
139    }
140
141    /// Serialize the frame-slot descriptor so it can be included as
142    /// metadata.
143    pub fn serialize(&self) -> Vec<u8> {
144        // Format (all little-endian):
145        // - func_key: (u32, u32)
146        // - num_locals: u32
147        // - num_stack_shapes: u32
148        // - local_offsets: num_locals times:
149        //   - offset: u32 (offset from start of state slot)
150        // - stack_shape_parents: num_stack_shapes times:
151        //   - parent_shape: u32 (or u32::MAX for none)
152        // - stack_shape_offsets: num_stack_shapes times:
153        //   - offset: u32 (offset from start of state slot for top-of-stack value)
154        // - local_types: num_locals times:
155        //   - type: u8
156        // - stack_shape_types: num_stack_shapes times:
157        //   - type: u8 (type of top-of-stack value)
158
159        let mut buffer = vec![];
160        let (func_key_namespace, func_key_index) = self.func_key.into_parts();
161        buffer.extend_from_slice(&u32::to_le_bytes(func_key_namespace.into_raw()));
162        buffer.extend_from_slice(&u32::to_le_bytes(func_key_index.into_raw()));
163
164        buffer.extend_from_slice(&u32::to_le_bytes(u32::try_from(self.locals.len()).unwrap()));
165        buffer.extend_from_slice(&u32::to_le_bytes(u32::try_from(self.stacks.len()).unwrap()));
166
167        for (_, offset) in &self.locals {
168            buffer.extend_from_slice(&u32::to_le_bytes(offset.0));
169        }
170        for (parent, _, _) in &self.stacks {
171            let parent = parent.map(|p| p.0).unwrap_or(u32::MAX);
172            buffer.extend_from_slice(&u32::to_le_bytes(parent));
173        }
174        for (_, _, offset) in &self.stacks {
175            buffer.extend_from_slice(&u32::to_le_bytes(offset.0));
176        }
177        for (ty, _) in &self.locals {
178            buffer.push(*ty as u8);
179        }
180        for (_, ty, _) in &self.stacks {
181            buffer.push(*ty as u8);
182        }
183
184        buffer
185    }
186
187    /// The total size required for all locals/stack storage.
188    pub fn size(&self) -> u32 {
189        self.slot_size
190    }
191}
192
193/// Builder for the Frame Table.
194///
195/// Format:
196///
197/// - `num_slot_descriptors`: u32
198/// - `num_progpoints`: u32
199/// - `frame_descriptor_pool_length`: u32
200/// - `progpoint_descriptor_pool_length`; U32
201/// - `num_slot_descriptors` times:
202///   - frame descriptor offset: u32
203///   - length: u32
204/// - `num_slot_descriptors` times:
205///   - offset from frame up to FP: u32
206/// - `num_progpoints` times:
207///   - PC, from start of text section, position (post/pre): u32
208///     - encoded as (pc << 1) | post_pre_bit
209/// - `num_progpoints` times:
210///   - progpoint descriptor offset: u32
211/// - frame descriptors (format described above; `frame_descriptor_pool_length` bytes)
212/// - progpoint descriptors (`progpoint_descriptor_pool_length` bytes)
213///   - each descriptor: sequence of frames
214///     - Wasm PC: u32 (high bit set to indicate a parent frame)
215///     - slot descriptor index: u32
216///     - stack shape index: u32 (or u32::MAX for none)
217#[derive(Default)]
218pub struct FrameTableBuilder {
219    /// (offset, length) pairs into `frame_descriptor_data`, indexed
220    /// by frame descriptor number.
221    frame_descriptor_ranges: Vec<U32Bytes<LittleEndian>>,
222    frame_descriptor_data: Vec<u8>,
223
224    /// Offset from frame slot up to FP for each frame descriptor.
225    frame_descriptor_fp_offsets: Vec<U32Bytes<LittleEndian>>,
226
227    progpoint_pcs: Vec<U32Bytes<LittleEndian>>,
228    progpoint_descriptor_offsets: Vec<U32Bytes<LittleEndian>>,
229    progpoint_descriptor_data: Vec<U32Bytes<LittleEndian>>,
230}
231
232impl FrameTableBuilder {
233    /// Add one frame descriptor.
234    ///
235    /// Returns the frame descriptor index.
236    pub fn add_frame_descriptor(
237        &mut self,
238        slot_to_fp_offset: u32,
239        data: &[u8],
240    ) -> FrameTableDescriptorIndex {
241        let start = u32::try_from(self.frame_descriptor_data.len()).unwrap();
242        self.frame_descriptor_data.extend(data.iter().cloned());
243        let end = u32::try_from(self.frame_descriptor_data.len()).unwrap();
244
245        let index = FrameTableDescriptorIndex(
246            u32::try_from(self.frame_descriptor_fp_offsets.len()).unwrap(),
247        );
248        self.frame_descriptor_fp_offsets
249            .push(U32Bytes::new(LittleEndian, slot_to_fp_offset));
250        self.frame_descriptor_ranges
251            .push(U32Bytes::new(LittleEndian, start));
252        self.frame_descriptor_ranges
253            .push(U32Bytes::new(LittleEndian, end));
254
255        index
256    }
257
258    /// Add one program point.
259    pub fn add_program_point(
260        &mut self,
261        native_pc: u32,
262        pos: FrameInstPos,
263        // For each frame: Wasm PC, frame descriptor, stack shape
264        // within the frame descriptor.
265        frames: &[(u32, FrameTableDescriptorIndex, FrameStackShape)],
266    ) {
267        let pc_and_pos = FrameInstPos::encode(native_pc, pos);
268        // If we already have a program point record at this PC,
269        // overwrite it.
270        while let Some(last) = self.progpoint_pcs.last()
271            && last.get(LittleEndian) == pc_and_pos
272        {
273            self.progpoint_pcs.pop();
274            self.progpoint_descriptor_offsets.pop();
275            self.progpoint_descriptor_data
276                .truncate(self.progpoint_descriptor_data.len() - 3);
277        }
278
279        let start = u32::try_from(self.progpoint_descriptor_data.len()).unwrap();
280        self.progpoint_pcs
281            .push(U32Bytes::new(LittleEndian, pc_and_pos));
282        self.progpoint_descriptor_offsets
283            .push(U32Bytes::new(LittleEndian, start));
284
285        for (i, &(wasm_pc, frame_descriptor, stack_shape)) in frames.iter().enumerate() {
286            debug_assert!(wasm_pc < 0x8000_0000);
287            let not_last = i < (frames.len() - 1);
288            let wasm_pc = wasm_pc | if not_last { 0x8000_0000 } else { 0 };
289            self.progpoint_descriptor_data
290                .push(U32Bytes::new(LittleEndian, wasm_pc));
291            self.progpoint_descriptor_data
292                .push(U32Bytes::new(LittleEndian, frame_descriptor.0));
293            self.progpoint_descriptor_data
294                .push(U32Bytes::new(LittleEndian, stack_shape.0));
295        }
296    }
297
298    /// Serialize the exception-handler data section, taking a closure
299    /// to consume slices.
300    pub fn serialize<F: FnMut(&[u8])>(&mut self, mut f: F) {
301        // Pad `frame_descriptor_data` to a multiple of 4 bytes so
302        // `progpoint_descriptor_data` is aligned as well.
303        while self.frame_descriptor_data.len() & 3 != 0 {
304            self.frame_descriptor_data.push(0);
305        }
306
307        let num_frame_descriptors = u32::try_from(self.frame_descriptor_fp_offsets.len()).unwrap();
308        f(&num_frame_descriptors.to_le_bytes());
309        let num_prog_points = u32::try_from(self.progpoint_pcs.len()).unwrap();
310        f(&num_prog_points.to_le_bytes());
311
312        let frame_descriptor_pool_length = u32::try_from(self.frame_descriptor_data.len()).unwrap();
313        f(&frame_descriptor_pool_length.to_le_bytes());
314        let progpoint_descriptor_pool_length =
315            u32::try_from(self.progpoint_descriptor_data.len()).unwrap();
316        f(&progpoint_descriptor_pool_length.to_le_bytes());
317
318        f(object::bytes_of_slice(&self.frame_descriptor_ranges));
319        f(object::bytes_of_slice(&self.frame_descriptor_fp_offsets));
320        f(object::bytes_of_slice(&self.progpoint_pcs));
321        f(object::bytes_of_slice(&self.progpoint_descriptor_offsets));
322        f(&self.frame_descriptor_data);
323        f(object::bytes_of_slice(&self.progpoint_descriptor_data));
324    }
325}