Path: blob/main/crates/environ/src/compile/frame_table.rs
3067 views
//! Builder for the `ELF_WASMTIME_FRAME_TABLE` ("frame table") section1//! in compiled executables.2//!3//! This section is present only if debug instrumentation is4//! enabled. It describes functions, stackslots that carry Wasm state,5//! and allows looking up active Wasm frames (including multiple6//! frames in one function due to inlining), Wasm local types and Wasm7//! operand stack depth in each frame by PC, with offsets to read8//! those values off of the state in the stack frame.910use crate::{11FrameInstPos, FrameStackShape, FrameStateSlotOffset, FrameTableDescriptorIndex, FrameValType,12FuncKey, WasmHeapTopType, WasmValType, prelude::*,13};14use object::{LittleEndian, U32Bytes};15use std::collections::{HashMap, hash_map::Entry};1617/// Builder for a stackslot descriptor.18pub struct FrameStateSlotBuilder {19/// Function identifier for this state slot.20func_key: FuncKey,2122/// Pointer size for target.23pointer_size: u32,2425/// Local types and offsets.26locals: Vec<(FrameValType, FrameStateSlotOffset)>,2728/// Stack nodes: (parent, type, offset) tuples.29stacks: Vec<(Option<FrameStackShape>, FrameValType, FrameStateSlotOffset)>,3031/// Hashconsing for stack-type nodes.32stacks_dedup:33HashMap<(Option<FrameStackShape>, FrameValType, FrameStateSlotOffset), FrameStackShape>,3435/// Size of vmctx (one pointer).36vmctx_size: u32,3738/// Size of all locals.39locals_size: u32,4041/// Maximum size of whole state slot.42slot_size: u32,43}4445impl From<WasmValType> for FrameValType {46fn from(ty: WasmValType) -> FrameValType {47match ty {48WasmValType::I32 => FrameValType::I32,49WasmValType::I64 => FrameValType::I64,50WasmValType::F32 => FrameValType::F32,51WasmValType::F64 => FrameValType::F64,52WasmValType::V128 => FrameValType::V128,53WasmValType::Ref(r) => match r.heap_type.top() {54WasmHeapTopType::Any => FrameValType::AnyRef,55WasmHeapTopType::Extern => FrameValType::ExternRef,56WasmHeapTopType::Func => FrameValType::FuncRef,57WasmHeapTopType::Exn => FrameValType::ExnRef,58WasmHeapTopType::Cont => FrameValType::ContRef,59},60}61}62}6364impl FrameStateSlotBuilder {65/// Create a new state-slot builder.66pub fn new(func_key: FuncKey, pointer_size: u32) -> FrameStateSlotBuilder {67FrameStateSlotBuilder {68func_key,69pointer_size,70locals: vec![],71stacks: vec![],72stacks_dedup: HashMap::new(),73vmctx_size: pointer_size,74locals_size: 0,75slot_size: pointer_size,76}77}7879/// Add a local to the state-slot.80///81/// Locals must be added in local index order, and must be added82/// before any stack shapes are defined. The offset in the state83/// slot is returned.84pub fn add_local(&mut self, ty: FrameValType) -> FrameStateSlotOffset {85// N.B.: the vmctx pointer is always at offset 0, so we add86// its size here.87let offset = FrameStateSlotOffset(self.vmctx_size + self.locals_size);88let size = ty.storage_size(self.pointer_size);89self.locals_size += size;90self.slot_size += size;91self.locals.push((ty, offset));92offset93}9495/// Get a local's offset in the state-slot.96pub fn local_offset(&self, local: u32) -> FrameStateSlotOffset {97let index = usize::try_from(local).unwrap();98self.locals[index].199}100101/// Push a stack entry. Returns the stack-shape descriptor and the102/// offset at which to write the pushed value.103pub fn push_stack(104&mut self,105parent: Option<FrameStackShape>,106ty: FrameValType,107) -> (FrameStackShape, FrameStateSlotOffset) {108let offset = parent109.map(|parent| {110let (_, ty, offset) = self.stacks[parent.index()];111offset.add(ty.storage_size(self.pointer_size))112})113// N.B.: the stack starts at vmctx_size + locals_size,114// because the layout puts vmctx first, then locals, then115// stack.116.unwrap_or(FrameStateSlotOffset(self.vmctx_size + self.locals_size));117118self.slot_size = core::cmp::max(119self.slot_size,120offset.0 + ty.storage_size(self.pointer_size),121);122123let shape = match self.stacks_dedup.entry((parent, ty, offset)) {124Entry::Occupied(o) => *o.get(),125Entry::Vacant(v) => {126let shape = FrameStackShape(u32::try_from(self.stacks.len()).unwrap());127self.stacks.push((parent, ty, offset));128*v.insert(shape)129}130};131132(shape, offset)133}134135/// Get the offset for the top slot in a given stack shape.136pub fn stack_last_offset(&self, shape: FrameStackShape) -> FrameStateSlotOffset {137self.stacks[shape.index()].2138}139140/// Serialize the frame-slot descriptor so it can be included as141/// metadata.142pub fn serialize(&self) -> Vec<u8> {143// Format (all little-endian):144// - func_key: (u32, u32)145// - num_locals: u32146// - num_stack_shapes: u32147// - local_offsets: num_locals times:148// - offset: u32 (offset from start of state slot)149// - stack_shape_parents: num_stack_shapes times:150// - parent_shape: u32 (or u32::MAX for none)151// - stack_shape_offsets: num_stack_shapes times:152// - offset: u32 (offset from start of state slot for top-of-stack value)153// - local_types: num_locals times:154// - type: u8155// - stack_shape_types: num_stack_shapes times:156// - type: u8 (type of top-of-stack value)157158let mut buffer = vec![];159let (func_key_namespace, func_key_index) = self.func_key.into_parts();160buffer.extend_from_slice(&u32::to_le_bytes(func_key_namespace.into_raw()));161buffer.extend_from_slice(&u32::to_le_bytes(func_key_index.into_raw()));162163buffer.extend_from_slice(&u32::to_le_bytes(u32::try_from(self.locals.len()).unwrap()));164buffer.extend_from_slice(&u32::to_le_bytes(u32::try_from(self.stacks.len()).unwrap()));165166for (_, offset) in &self.locals {167buffer.extend_from_slice(&u32::to_le_bytes(offset.0));168}169for (parent, _, _) in &self.stacks {170let parent = parent.map(|p| p.0).unwrap_or(u32::MAX);171buffer.extend_from_slice(&u32::to_le_bytes(parent));172}173for (_, _, offset) in &self.stacks {174buffer.extend_from_slice(&u32::to_le_bytes(offset.0));175}176for (ty, _) in &self.locals {177buffer.push(*ty as u8);178}179for (_, ty, _) in &self.stacks {180buffer.push(*ty as u8);181}182183buffer184}185186/// The total size required for all locals/stack storage.187pub fn size(&self) -> u32 {188self.slot_size189}190}191192/// Builder for the Frame Table.193///194/// Format:195///196/// - `num_slot_descriptors`: u32197/// - `num_progpoints`: u32198/// - `num_breakpoints`: u32199/// - `frame_descriptor_pool_length`: u32200/// - `progpoint_descriptor_pool_length`: u32201/// - `breakpoint_patch_pool_length`: u32202/// - `num_slot_descriptors` times:203/// - frame descriptor offset: u32204/// - length: u32205/// - `num_slot_descriptors` times:206/// - offset from frame up to FP: u32207/// - `num_progpoints` times:208/// - PC, from start of text section, position (post/pre): u32209/// - encoded as (pc << 1) | post_pre_bit210/// - `num_progpoints` times:211/// - progpoint descriptor offset: u32212/// - `num_breakpoints` times:213/// - Wasm PC: u32 (sorted order; may repeat)214/// - `num_breakpoints` times:215/// - patch offset in text: u32216/// - `num_breakpoints` times:217/// - end of breakpoint patch data in pool: u32218/// (find the start by end of previous; patches are in the219/// pool in order and this saves storing redundant start/end values)220/// - frame descriptors (format described above; `frame_descriptor_pool_length` bytes)221/// - progpoint descriptors (`progpoint_descriptor_pool_length` bytes)222/// - each descriptor: sequence of frames223/// - Wasm PC: u32 (high bit set to indicate a parent frame)224/// - slot descriptor index: u32225/// - stack shape index: u32 (or u32::MAX for none)226/// - breakpoint patch pool (`breakpoint_patch_pool_length` bytes)227/// - freeform slices of machine-code bytes to patch in228#[derive(Default)]229pub struct FrameTableBuilder {230/// (offset, length) pairs into `frame_descriptor_data`, indexed231/// by frame descriptor number.232frame_descriptor_ranges: Vec<U32Bytes<LittleEndian>>,233frame_descriptor_data: Vec<u8>,234235/// Offset from frame slot up to FP for each frame descriptor.236frame_descriptor_fp_offsets: Vec<U32Bytes<LittleEndian>>,237238progpoint_pcs: Vec<U32Bytes<LittleEndian>>,239progpoint_descriptor_offsets: Vec<U32Bytes<LittleEndian>>,240progpoint_descriptor_data: Vec<U32Bytes<LittleEndian>>,241242breakpoint_pcs: Vec<U32Bytes<LittleEndian>>,243breakpoint_patch_offsets: Vec<U32Bytes<LittleEndian>>,244breakpoint_patch_data_ends: Vec<U32Bytes<LittleEndian>>,245246breakpoint_patch_data: Vec<u8>,247}248249impl FrameTableBuilder {250/// Add one frame descriptor.251///252/// Returns the frame descriptor index.253pub fn add_frame_descriptor(254&mut self,255slot_to_fp_offset: u32,256data: &[u8],257) -> FrameTableDescriptorIndex {258let start = u32::try_from(self.frame_descriptor_data.len()).unwrap();259self.frame_descriptor_data.extend(data.iter().cloned());260let end = u32::try_from(self.frame_descriptor_data.len()).unwrap();261262let index = FrameTableDescriptorIndex(263u32::try_from(self.frame_descriptor_fp_offsets.len()).unwrap(),264);265self.frame_descriptor_fp_offsets266.push(U32Bytes::new(LittleEndian, slot_to_fp_offset));267self.frame_descriptor_ranges268.push(U32Bytes::new(LittleEndian, start));269self.frame_descriptor_ranges270.push(U32Bytes::new(LittleEndian, end));271272index273}274275/// Add one program point.276pub fn add_program_point(277&mut self,278native_pc: u32,279pos: FrameInstPos,280// For each frame: Wasm PC, frame descriptor, stack shape281// within the frame descriptor.282frames: &[(u32, FrameTableDescriptorIndex, FrameStackShape)],283) {284let pc_and_pos = FrameInstPos::encode(native_pc, pos);285// If we already have a program point record at this PC,286// overwrite it.287while let Some(last) = self.progpoint_pcs.last()288&& last.get(LittleEndian) == pc_and_pos289{290self.progpoint_pcs.pop();291self.progpoint_descriptor_offsets.pop();292self.progpoint_descriptor_data293.truncate(self.progpoint_descriptor_data.len() - 3);294}295296let start = u32::try_from(self.progpoint_descriptor_data.len()).unwrap();297self.progpoint_pcs298.push(U32Bytes::new(LittleEndian, pc_and_pos));299self.progpoint_descriptor_offsets300.push(U32Bytes::new(LittleEndian, start));301302for (i, &(wasm_pc, frame_descriptor, stack_shape)) in frames.iter().enumerate() {303debug_assert!(wasm_pc < 0x8000_0000);304let not_last = i < (frames.len() - 1);305let wasm_pc = wasm_pc | if not_last { 0x8000_0000 } else { 0 };306self.progpoint_descriptor_data307.push(U32Bytes::new(LittleEndian, wasm_pc));308self.progpoint_descriptor_data309.push(U32Bytes::new(LittleEndian, frame_descriptor.0));310self.progpoint_descriptor_data311.push(U32Bytes::new(LittleEndian, stack_shape.0));312}313}314315/// Add one breakpoint patch.316pub fn add_breakpoint_patch(&mut self, wasm_pc: u32, patch_start_native_pc: u32, patch: &[u8]) {317self.breakpoint_pcs318.push(U32Bytes::new(LittleEndian, wasm_pc));319self.breakpoint_patch_offsets320.push(U32Bytes::new(LittleEndian, patch_start_native_pc));321self.breakpoint_patch_data.extend(patch.iter().cloned());322let end = u32::try_from(self.breakpoint_patch_data.len()).unwrap();323self.breakpoint_patch_data_ends324.push(U32Bytes::new(LittleEndian, end));325}326327/// Serialize the framd-table data section, taking a closure to328/// consume slices.329pub fn serialize<F: FnMut(&[u8])>(&mut self, mut f: F) {330// Pad `frame_descriptor_data` to a multiple of 4 bytes so331// `progpoint_descriptor_data` is aligned as well.332while self.frame_descriptor_data.len() & 3 != 0 {333self.frame_descriptor_data.push(0);334}335336let num_frame_descriptors = u32::try_from(self.frame_descriptor_fp_offsets.len()).unwrap();337f(&num_frame_descriptors.to_le_bytes());338let num_prog_points = u32::try_from(self.progpoint_pcs.len()).unwrap();339f(&num_prog_points.to_le_bytes());340let num_breakpoints = u32::try_from(self.breakpoint_pcs.len()).unwrap();341f(&num_breakpoints.to_le_bytes());342343let frame_descriptor_pool_length = u32::try_from(self.frame_descriptor_data.len()).unwrap();344f(&frame_descriptor_pool_length.to_le_bytes());345let progpoint_descriptor_pool_length =346u32::try_from(self.progpoint_descriptor_data.len()).unwrap();347f(&progpoint_descriptor_pool_length.to_le_bytes());348let breakpoint_patch_pool_length = u32::try_from(self.breakpoint_patch_data.len()).unwrap();349f(&breakpoint_patch_pool_length.to_le_bytes());350351f(object::bytes_of_slice(&self.frame_descriptor_ranges));352f(object::bytes_of_slice(&self.frame_descriptor_fp_offsets));353f(object::bytes_of_slice(&self.progpoint_pcs));354f(object::bytes_of_slice(&self.progpoint_descriptor_offsets));355f(object::bytes_of_slice(&self.breakpoint_pcs));356f(object::bytes_of_slice(&self.breakpoint_patch_offsets));357f(object::bytes_of_slice(&self.breakpoint_patch_data_ends));358f(&self.frame_descriptor_data);359f(object::bytes_of_slice(&self.progpoint_descriptor_data));360f(&self.breakpoint_patch_data);361}362}363364365