Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/environ/src/compile/frame_table.rs
3067 views
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
11
use crate::{
12
FrameInstPos, FrameStackShape, FrameStateSlotOffset, FrameTableDescriptorIndex, FrameValType,
13
FuncKey, WasmHeapTopType, WasmValType, prelude::*,
14
};
15
use object::{LittleEndian, U32Bytes};
16
use std::collections::{HashMap, hash_map::Entry};
17
18
/// Builder for a stackslot descriptor.
19
pub 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
46
impl 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
65
impl 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)]
230
pub 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
250
impl 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
}
364
365