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