Skip to main content

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, ModulePC, StaticModuleIndex, WasmHeapTopType, WasmValType, prelude::*,
14};
15use object::{LittleEndian, U32};
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/// - `num_breakpoints`: u32
200/// - `num_funcs`: u32
201/// - `frame_descriptor_pool_length`: u32
202/// - `progpoint_descriptor_pool_length`: u32
203/// - `breakpoint_patch_pool_length`: u32
204/// - `num_slot_descriptors` times:
205///   - frame descriptor offset: u32
206///   - length: u32
207/// - `num_slot_descriptors` times:
208///   - offset from frame up to FP: u32
209/// - `num_progpoints` times:
210///   - PC, from start of text section, position (post/pre): u32
211///     - encoded as (pc << 1) | post_pre_bit
212/// - `num_progpoints` times:
213///   - progpoint descriptor offset: u32
214/// - `num_breakpoints` times:
215///    - static module index of the Wasm PC: u32
216/// - `num_breakpoints` times:
217///    - Wasm PC, relative to that module: u32 (sorted order; may repeat)
218/// - `num_breakpoints` times:
219///    - patch offset in text: u32
220/// - `num_breakpoints` times:
221///    - end of breakpoint patch data in pool: u32
222///      (find the start by end of previous; patches are in the
223///      pool in order and this saves storing redundant start/end values)
224/// - `num_funcs` times:
225///    - function start offset in text: u32 (sorted order)
226/// - frame descriptors (format described above; `frame_descriptor_pool_length` bytes)
227/// - progpoint descriptors (`progpoint_descriptor_pool_length` bytes)
228///   - each descriptor: sequence of frames
229///     - Wasm PC: u32 (high bit set to indicate a parent frame)
230///     - slot descriptor index: u32
231///     - stack shape index: u32 (or u32::MAX for none)
232/// - breakpoint patch pool (`breakpoint_patch_pool_length` bytes)
233///   - freeform slices of machine-code bytes to patch in
234#[derive(Default)]
235pub struct FrameTableBuilder {
236    /// (offset, length) pairs into `frame_descriptor_data`, indexed
237    /// by frame descriptor number.
238    frame_descriptor_ranges: Vec<U32<LittleEndian>>,
239    frame_descriptor_data: Vec<u8>,
240
241    /// Offset from frame slot up to FP for each frame descriptor.
242    frame_descriptor_fp_offsets: Vec<U32<LittleEndian>>,
243
244    progpoint_pcs: Vec<U32<LittleEndian>>,
245    progpoint_descriptor_offsets: Vec<U32<LittleEndian>>,
246    progpoint_descriptor_data: Vec<U32<LittleEndian>>,
247
248    breakpoint_modules: Vec<U32<LittleEndian>>,
249    breakpoint_pcs: Vec<U32<LittleEndian>>,
250    breakpoint_patch_offsets: Vec<U32<LittleEndian>>,
251    breakpoint_patch_data_ends: Vec<U32<LittleEndian>>,
252
253    breakpoint_patch_data: Vec<u8>,
254
255    /// Start offsets of every function in the text section.
256    func_starts: Vec<U32<LittleEndian>>,
257}
258
259impl FrameTableBuilder {
260    /// Add one frame descriptor.
261    ///
262    /// Returns the frame descriptor index.
263    pub fn add_frame_descriptor(
264        &mut self,
265        slot_to_fp_offset: u32,
266        data: &[u8],
267    ) -> FrameTableDescriptorIndex {
268        let start = u32::try_from(self.frame_descriptor_data.len()).unwrap();
269        self.frame_descriptor_data.extend(data.iter().cloned());
270        let end = u32::try_from(self.frame_descriptor_data.len()).unwrap();
271
272        let index = FrameTableDescriptorIndex(
273            u32::try_from(self.frame_descriptor_fp_offsets.len()).unwrap(),
274        );
275        self.frame_descriptor_fp_offsets
276            .push(U32::new(LittleEndian, slot_to_fp_offset));
277        self.frame_descriptor_ranges
278            .push(U32::new(LittleEndian, start));
279        self.frame_descriptor_ranges
280            .push(U32::new(LittleEndian, end));
281
282        index
283    }
284
285    /// Record the start of a function in the text section.
286    ///
287    /// This must be called for every function in the text section
288    /// (whether or not it has any program points), in ascending
289    /// order of offset, so that program-point lookups do not fall
290    /// through into a preceding function's metadata.
291    pub fn add_func_start(&mut self, native_pc: u32) {
292        debug_assert!(
293            self.func_starts
294                .last()
295                .map_or(true, |last| last.get(LittleEndian) <= native_pc)
296        );
297        self.func_starts.push(U32::new(LittleEndian, native_pc));
298    }
299
300    /// Add one program point.
301    pub fn add_program_point(
302        &mut self,
303        native_pc: u32,
304        pos: FrameInstPos,
305        // For each frame: module-relative Wasm PC, frame descriptor,
306        // stack shape within the frame descriptor.
307        frames: &[(ModulePC, FrameTableDescriptorIndex, FrameStackShape)],
308    ) {
309        let pc_and_pos = FrameInstPos::encode(native_pc, pos);
310        // If we already have a program point record at this PC,
311        // overwrite it.
312        while let Some(last) = self.progpoint_pcs.last()
313            && last.get(LittleEndian) == pc_and_pos
314        {
315            self.progpoint_pcs.pop();
316            self.progpoint_descriptor_offsets.pop();
317            self.progpoint_descriptor_data
318                .truncate(self.progpoint_descriptor_data.len() - 3);
319        }
320
321        let start = u32::try_from(self.progpoint_descriptor_data.len()).unwrap();
322        self.progpoint_pcs.push(U32::new(LittleEndian, pc_and_pos));
323        self.progpoint_descriptor_offsets
324            .push(U32::new(LittleEndian, start));
325
326        for (i, &(wasm_pc, frame_descriptor, stack_shape)) in frames.iter().enumerate() {
327            let wasm_pc_raw = wasm_pc.raw();
328            debug_assert!(wasm_pc_raw < 0x8000_0000);
329            let not_last = i < (frames.len() - 1);
330            let wasm_pc_raw = wasm_pc_raw | if not_last { 0x8000_0000 } else { 0 };
331            self.progpoint_descriptor_data
332                .push(U32::new(LittleEndian, wasm_pc_raw));
333            self.progpoint_descriptor_data
334                .push(U32::new(LittleEndian, frame_descriptor.0));
335            self.progpoint_descriptor_data
336                .push(U32::new(LittleEndian, stack_shape.0));
337        }
338    }
339
340    /// Add one breakpoint patch, for a Wasm PC in the given module.
341    ///
342    /// Patches must be added in order of Wasm PC.
343    pub fn add_breakpoint_patch(
344        &mut self,
345        module: StaticModuleIndex,
346        wasm_pc: ModulePC,
347        patch_start_native_pc: u32,
348        patch: &[u8],
349    ) {
350        self.breakpoint_modules
351            .push(U32::new(LittleEndian, module.as_u32()));
352        self.breakpoint_pcs
353            .push(U32::new(LittleEndian, wasm_pc.raw()));
354        self.breakpoint_patch_offsets
355            .push(U32::new(LittleEndian, patch_start_native_pc));
356        self.breakpoint_patch_data.extend(patch.iter().cloned());
357        let end = u32::try_from(self.breakpoint_patch_data.len()).unwrap();
358        self.breakpoint_patch_data_ends
359            .push(U32::new(LittleEndian, end));
360    }
361
362    /// Serialize the frame-table data section, taking a closure to
363    /// consume slices.
364    pub fn serialize<F: FnMut(&[u8])>(&mut self, mut f: F) {
365        // Pad `frame_descriptor_data` to a multiple of 4 bytes so
366        // `progpoint_descriptor_data` is aligned as well.
367        while self.frame_descriptor_data.len() & 3 != 0 {
368            self.frame_descriptor_data.push(0);
369        }
370
371        let num_frame_descriptors = u32::try_from(self.frame_descriptor_fp_offsets.len()).unwrap();
372        f(&num_frame_descriptors.to_le_bytes());
373        let num_prog_points = u32::try_from(self.progpoint_pcs.len()).unwrap();
374        f(&num_prog_points.to_le_bytes());
375        let num_breakpoints = u32::try_from(self.breakpoint_pcs.len()).unwrap();
376        f(&num_breakpoints.to_le_bytes());
377        let num_funcs = u32::try_from(self.func_starts.len()).unwrap();
378        f(&num_funcs.to_le_bytes());
379
380        let frame_descriptor_pool_length = u32::try_from(self.frame_descriptor_data.len()).unwrap();
381        f(&frame_descriptor_pool_length.to_le_bytes());
382        let progpoint_descriptor_pool_length =
383            u32::try_from(self.progpoint_descriptor_data.len()).unwrap();
384        f(&progpoint_descriptor_pool_length.to_le_bytes());
385        let breakpoint_patch_pool_length = u32::try_from(self.breakpoint_patch_data.len()).unwrap();
386        f(&breakpoint_patch_pool_length.to_le_bytes());
387
388        f(object::bytes_of_slice(&self.frame_descriptor_ranges));
389        f(object::bytes_of_slice(&self.frame_descriptor_fp_offsets));
390        f(object::bytes_of_slice(&self.progpoint_pcs));
391        f(object::bytes_of_slice(&self.progpoint_descriptor_offsets));
392        f(object::bytes_of_slice(&self.breakpoint_modules));
393        f(object::bytes_of_slice(&self.breakpoint_pcs));
394        f(object::bytes_of_slice(&self.breakpoint_patch_offsets));
395        f(object::bytes_of_slice(&self.breakpoint_patch_data_ends));
396        f(object::bytes_of_slice(&self.func_starts));
397        f(&self.frame_descriptor_data);
398        f(object::bytes_of_slice(&self.progpoint_descriptor_data));
399        f(&self.breakpoint_patch_data);
400    }
401}