Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/cranelift/src/debug/transform/expression.rs
3064 views
1
use super::address_transform::AddressTransform;
2
use super::dbi_log;
3
use crate::debug::ModuleMemoryOffset;
4
use crate::debug::transform::debug_transform_logging::{
5
dbi_log_enabled, log_get_value_loc, log_get_value_name, log_get_value_ranges,
6
};
7
use crate::translate::get_vmctx_value_label;
8
use core::fmt;
9
use cranelift_codegen::LabelValueLoc;
10
use cranelift_codegen::ValueLabelsRanges;
11
use cranelift_codegen::ir::ValueLabel;
12
use cranelift_codegen::isa::TargetIsa;
13
use gimli::{Expression, Operation, Reader, ReaderOffset, write};
14
use itertools::Itertools;
15
use std::cmp::PartialEq;
16
use std::collections::{HashMap, HashSet};
17
use std::hash::{Hash, Hasher};
18
use std::rc::Rc;
19
use wasmtime_environ::error::{Context, Error, Result};
20
21
#[derive(Debug)]
22
pub struct FunctionFrameInfo<'a> {
23
pub value_ranges: &'a ValueLabelsRanges,
24
pub memory_offset: ModuleMemoryOffset,
25
}
26
27
struct ExpressionWriter(write::EndianVec<gimli::RunTimeEndian>);
28
29
enum VmctxBase {
30
Reg(u16),
31
OnStack,
32
}
33
34
impl ExpressionWriter {
35
fn new() -> Self {
36
let endian = gimli::RunTimeEndian::Little;
37
let writer = write::EndianVec::new(endian);
38
ExpressionWriter(writer)
39
}
40
41
fn write_op(&mut self, op: gimli::DwOp) -> write::Result<()> {
42
self.write_u8(op.0)
43
}
44
45
fn write_op_reg(&mut self, reg: u16) -> write::Result<()> {
46
if reg < 32 {
47
self.write_u8(gimli::constants::DW_OP_reg0.0 + reg as u8)
48
} else {
49
self.write_op(gimli::constants::DW_OP_regx)?;
50
self.write_uleb128(reg.into())
51
}
52
}
53
54
fn write_op_breg(&mut self, reg: u16) -> write::Result<()> {
55
if reg < 32 {
56
self.write_u8(gimli::constants::DW_OP_breg0.0 + reg as u8)
57
} else {
58
self.write_op(gimli::constants::DW_OP_bregx)?;
59
self.write_uleb128(reg.into())
60
}
61
}
62
63
fn write_u8(&mut self, b: u8) -> write::Result<()> {
64
write::Writer::write_u8(&mut self.0, b)
65
}
66
67
fn write_u32(&mut self, b: u32) -> write::Result<()> {
68
write::Writer::write_u32(&mut self.0, b)
69
}
70
71
fn write_uleb128(&mut self, i: u64) -> write::Result<()> {
72
write::Writer::write_uleb128(&mut self.0, i)
73
}
74
75
fn write_sleb128(&mut self, i: i64) -> write::Result<()> {
76
write::Writer::write_sleb128(&mut self.0, i)
77
}
78
79
fn into_vec(self) -> Vec<u8> {
80
self.0.into_vec()
81
}
82
83
fn gen_address_of_memory_base_pointer(
84
&mut self,
85
vmctx: VmctxBase,
86
memory_base: &ModuleMemoryOffset,
87
) -> write::Result<()> {
88
match *memory_base {
89
ModuleMemoryOffset::Defined(offset) => match vmctx {
90
VmctxBase::Reg(reg) => {
91
self.write_op_breg(reg)?;
92
self.write_sleb128(offset.into())?;
93
}
94
VmctxBase::OnStack => {
95
self.write_op(gimli::constants::DW_OP_consts)?;
96
self.write_sleb128(offset.into())?;
97
self.write_op(gimli::constants::DW_OP_plus)?;
98
}
99
},
100
ModuleMemoryOffset::Imported {
101
offset_to_vm_memory_definition,
102
offset_to_memory_base,
103
} => {
104
match vmctx {
105
VmctxBase::Reg(reg) => {
106
self.write_op_breg(reg)?;
107
self.write_sleb128(offset_to_vm_memory_definition.into())?;
108
}
109
VmctxBase::OnStack => {
110
if offset_to_vm_memory_definition > 0 {
111
self.write_op(gimli::constants::DW_OP_consts)?;
112
self.write_sleb128(offset_to_vm_memory_definition.into())?;
113
}
114
self.write_op(gimli::constants::DW_OP_plus)?;
115
}
116
}
117
self.write_op(gimli::constants::DW_OP_deref)?;
118
if offset_to_memory_base > 0 {
119
self.write_op(gimli::constants::DW_OP_consts)?;
120
self.write_sleb128(offset_to_memory_base.into())?;
121
self.write_op(gimli::constants::DW_OP_plus)?;
122
}
123
}
124
ModuleMemoryOffset::None => return Err(write::Error::InvalidAttributeValue),
125
}
126
Ok(())
127
}
128
}
129
130
#[derive(Debug, Clone, PartialEq)]
131
enum CompiledExpressionPart {
132
// Untranslated DWARF expression.
133
Code(Vec<u8>),
134
// The wasm-local DWARF operator. The label points to `ValueLabel`.
135
// The trailing field denotes that the operator was last in sequence,
136
// and it is the DWARF location (not a pointer).
137
Local {
138
label: ValueLabel,
139
trailing: bool,
140
},
141
// Dereference is needed.
142
Deref,
143
// Jumping in the expression.
144
Jump {
145
conditionally: bool,
146
target: JumpTargetMarker,
147
},
148
// Floating landing pad.
149
LandingPad(JumpTargetMarker),
150
}
151
152
#[derive(Debug, Clone, PartialEq)]
153
pub struct CompiledExpression {
154
parts: Vec<CompiledExpressionPart>,
155
need_deref: bool,
156
}
157
158
impl CompiledExpression {
159
pub fn vmctx() -> CompiledExpression {
160
CompiledExpression::from_label(get_vmctx_value_label())
161
}
162
163
pub fn from_label(label: ValueLabel) -> CompiledExpression {
164
CompiledExpression {
165
parts: vec![CompiledExpressionPart::Local {
166
label,
167
trailing: true,
168
}],
169
need_deref: false,
170
}
171
}
172
}
173
174
fn translate_loc(
175
loc: LabelValueLoc,
176
isa: &dyn TargetIsa,
177
add_stack_value: bool,
178
) -> Result<Option<Vec<u8>>> {
179
Ok(match loc {
180
LabelValueLoc::Reg(r) => {
181
let machine_reg = isa.map_regalloc_reg_to_dwarf(r)?;
182
let mut writer = ExpressionWriter::new();
183
if add_stack_value {
184
writer.write_op_reg(machine_reg)?;
185
} else {
186
writer.write_op_breg(machine_reg)?;
187
writer.write_sleb128(0)?;
188
}
189
Some(writer.into_vec())
190
}
191
LabelValueLoc::CFAOffset(off) => {
192
let mut writer = ExpressionWriter::new();
193
writer.write_op(gimli::constants::DW_OP_fbreg)?;
194
writer.write_sleb128(off)?;
195
if !add_stack_value {
196
writer.write_op(gimli::constants::DW_OP_deref)?;
197
}
198
return Ok(Some(writer.into_vec()));
199
}
200
})
201
}
202
203
fn append_memory_deref(
204
buf: &mut Vec<u8>,
205
frame_info: &FunctionFrameInfo,
206
vmctx_loc: LabelValueLoc,
207
isa: &dyn TargetIsa,
208
) -> Result<bool> {
209
let mut writer = ExpressionWriter::new();
210
let vmctx_base = match vmctx_loc {
211
LabelValueLoc::Reg(r) => VmctxBase::Reg(isa.map_regalloc_reg_to_dwarf(r)?),
212
LabelValueLoc::CFAOffset(off) => {
213
writer.write_op(gimli::constants::DW_OP_fbreg)?;
214
writer.write_sleb128(off)?;
215
writer.write_op(gimli::constants::DW_OP_deref)?;
216
VmctxBase::OnStack
217
}
218
};
219
writer.gen_address_of_memory_base_pointer(vmctx_base, &frame_info.memory_offset)?;
220
writer.write_op(gimli::constants::DW_OP_deref)?;
221
writer.write_op(gimli::constants::DW_OP_swap)?;
222
writer.write_op(gimli::constants::DW_OP_const4u)?;
223
writer.write_u32(0xffff_ffff)?;
224
writer.write_op(gimli::constants::DW_OP_and)?;
225
writer.write_op(gimli::constants::DW_OP_plus)?;
226
buf.extend(writer.into_vec());
227
Ok(true)
228
}
229
230
pub struct BuiltCompiledExpression<TIter> {
231
pub expressions: TIter,
232
pub covers_entire_scope: bool,
233
}
234
235
impl CompiledExpression {
236
pub fn is_simple(&self) -> bool {
237
if let [CompiledExpressionPart::Code(_)] = self.parts.as_slice() {
238
true
239
} else {
240
self.parts.is_empty()
241
}
242
}
243
244
pub fn build(&self) -> Option<write::Expression> {
245
if let [CompiledExpressionPart::Code(code)] = self.parts.as_slice() {
246
return Some(write::Expression::raw(code.to_vec()));
247
}
248
// locals found, not supported
249
None
250
}
251
252
pub fn build_with_locals<'a>(
253
&'a self,
254
scope: &'a [(u64, u64)], // wasm ranges
255
addr_tr: &'a AddressTransform,
256
frame_info: Option<&'a FunctionFrameInfo>,
257
isa: &'a dyn TargetIsa,
258
) -> BuiltCompiledExpression<
259
impl Iterator<Item = Result<(write::Address, u64, write::Expression)>> + use<'a>,
260
> {
261
enum BuildWithLocalsResult<'a> {
262
Empty,
263
Simple(
264
Box<dyn Iterator<Item = (write::Address, u64)> + 'a>,
265
Vec<u8>,
266
),
267
Ranges(Box<dyn Iterator<Item = Result<(usize, usize, usize, Vec<u8>)>> + 'a>),
268
}
269
impl Iterator for BuildWithLocalsResult<'_> {
270
type Item = Result<(write::Address, u64, write::Expression)>;
271
fn next(&mut self) -> Option<Self::Item> {
272
match self {
273
BuildWithLocalsResult::Empty => None,
274
BuildWithLocalsResult::Simple(it, code) => it
275
.next()
276
.map(|(addr, len)| Ok((addr, len, write::Expression::raw(code.to_vec())))),
277
BuildWithLocalsResult::Ranges(it) => it.next().map(|r| {
278
r.map(|(symbol, start, end, code_buf)| {
279
(
280
write::Address::Symbol {
281
symbol,
282
addend: start as i64,
283
},
284
(end - start) as u64,
285
write::Expression::raw(code_buf),
286
)
287
})
288
}),
289
}
290
}
291
}
292
293
if scope.is_empty() {
294
return BuiltCompiledExpression {
295
expressions: BuildWithLocalsResult::Empty,
296
covers_entire_scope: false,
297
};
298
}
299
300
// If it a simple DWARF code, no need in locals processing. Just translate
301
// the scope ranges.
302
if let [CompiledExpressionPart::Code(code)] = self.parts.as_slice() {
303
return BuiltCompiledExpression {
304
expressions: BuildWithLocalsResult::Simple(
305
Box::new(scope.iter().flat_map(move |(wasm_start, wasm_end)| {
306
addr_tr.translate_ranges(*wasm_start, *wasm_end)
307
})),
308
code.clone(),
309
),
310
covers_entire_scope: false,
311
};
312
}
313
314
let vmctx_label = get_vmctx_value_label();
315
316
// Some locals are present, preparing and divided ranges based on the scope
317
// and frame_info data.
318
let mut ranges_builder = ValueLabelRangesBuilder::new(scope, addr_tr, frame_info, isa);
319
for p in self.parts.iter() {
320
match p {
321
CompiledExpressionPart::Code(_)
322
| CompiledExpressionPart::Jump { .. }
323
| CompiledExpressionPart::LandingPad { .. } => (),
324
CompiledExpressionPart::Local { label, .. } => ranges_builder.process_label(*label),
325
CompiledExpressionPart::Deref => ranges_builder.process_label(vmctx_label),
326
}
327
}
328
if self.need_deref {
329
ranges_builder.process_label(vmctx_label);
330
}
331
332
let ranges = ranges_builder.into_ranges();
333
let expressions = BuildWithLocalsResult::Ranges(Box::new(
334
ranges
335
.ranges
336
.map(
337
move |CachedValueLabelRange {
338
func_index,
339
start,
340
end,
341
label_location,
342
}| {
343
// build expression
344
let mut code_buf = Vec::new();
345
let mut jump_positions = Vec::new();
346
let mut landing_positions = HashMap::new();
347
348
macro_rules! deref {
349
() => {
350
if let (Some(vmctx_loc), Some(frame_info)) =
351
(label_location.get(&vmctx_label), frame_info)
352
{
353
if !append_memory_deref(
354
&mut code_buf,
355
frame_info,
356
*vmctx_loc,
357
isa,
358
)? {
359
return Ok(None);
360
}
361
} else {
362
return Ok(None);
363
}
364
};
365
}
366
for part in &self.parts {
367
match part {
368
CompiledExpressionPart::Code(c) => {
369
code_buf.extend_from_slice(c.as_slice())
370
}
371
CompiledExpressionPart::LandingPad(marker) => {
372
landing_positions.insert(marker.clone(), code_buf.len());
373
}
374
CompiledExpressionPart::Jump {
375
conditionally,
376
target,
377
} => {
378
code_buf.push(
379
match conditionally {
380
true => gimli::constants::DW_OP_bra,
381
false => gimli::constants::DW_OP_skip,
382
}
383
.0,
384
);
385
code_buf.push(!0);
386
code_buf.push(!0); // these will be relocated below
387
jump_positions.push((target.clone(), code_buf.len()));
388
}
389
CompiledExpressionPart::Local { label, trailing } => {
390
let loc =
391
*label_location.get(&label).context("label_location")?;
392
if let Some(expr) = translate_loc(loc, isa, *trailing)? {
393
code_buf.extend_from_slice(&expr)
394
} else {
395
return Ok(None);
396
}
397
}
398
CompiledExpressionPart::Deref => deref!(),
399
}
400
}
401
if self.need_deref {
402
deref!();
403
}
404
405
for (marker, new_from) in jump_positions {
406
// relocate jump targets
407
let new_to = landing_positions[&marker];
408
let new_diff = new_to as isize - new_from as isize;
409
// FIXME: use encoding? LittleEndian for now...
410
code_buf[new_from - 2..new_from]
411
.copy_from_slice(&(new_diff as i16).to_le_bytes());
412
}
413
Ok(Some((func_index, start, end, code_buf)))
414
},
415
)
416
.filter_map(Result::transpose),
417
));
418
419
BuiltCompiledExpression {
420
expressions,
421
covers_entire_scope: ranges.covers_entire_scope,
422
}
423
}
424
}
425
426
fn is_old_expression_format(buf: &[u8]) -> bool {
427
// Heuristic to detect old variable expression format without DW_OP_fbreg:
428
// DW_OP_plus_uconst op must be present, but not DW_OP_fbreg.
429
if buf.contains(&(gimli::constants::DW_OP_fbreg.0)) {
430
// Stop check if DW_OP_fbreg exist.
431
return false;
432
}
433
buf.contains(&(gimli::constants::DW_OP_plus_uconst.0))
434
}
435
436
pub fn compile_expression<R>(
437
expr: &Expression<R>,
438
encoding: gimli::Encoding,
439
frame_base: Option<&CompiledExpression>,
440
) -> Result<Option<CompiledExpression>, Error>
441
where
442
R: Reader,
443
{
444
// Bail when `frame_base` is complicated.
445
if let Some(expr) = frame_base {
446
if expr.parts.iter().any(|p| match p {
447
CompiledExpressionPart::Jump { .. } => true,
448
_ => false,
449
}) {
450
return Ok(None);
451
}
452
}
453
454
// jump_targets key is offset in buf starting from the end
455
// (see also `unread_bytes` below)
456
let mut jump_targets: HashMap<u64, JumpTargetMarker> = HashMap::new();
457
let mut pc = expr.0.clone();
458
459
let buf = expr.0.to_slice()?;
460
let mut parts = Vec::new();
461
macro_rules! push {
462
($part:expr) => {{
463
let part = $part;
464
if let (CompiledExpressionPart::Code(cc2), Some(CompiledExpressionPart::Code(cc1))) =
465
(&part, parts.last_mut())
466
{
467
cc1.extend_from_slice(cc2);
468
} else {
469
parts.push(part)
470
}
471
}};
472
}
473
let mut need_deref = false;
474
if is_old_expression_format(&buf) && frame_base.is_some() {
475
// Still supporting old DWARF variable expressions without fbreg.
476
parts.extend_from_slice(&frame_base.unwrap().parts);
477
if let Some(CompiledExpressionPart::Local { trailing, .. }) = parts.last_mut() {
478
*trailing = false;
479
}
480
need_deref = frame_base.unwrap().need_deref;
481
}
482
let mut code_chunk = Vec::new();
483
macro_rules! flush_code_chunk {
484
() => {
485
if !code_chunk.is_empty() {
486
push!(CompiledExpressionPart::Code(code_chunk));
487
code_chunk = Vec::new();
488
let _ = code_chunk; // suppresses warning for final flush
489
}
490
};
491
}
492
493
// Find all landing pads by scanning bytes, do not care about
494
// false location at this moment.
495
// Looks hacky but it is fast; does not need to be really exact.
496
if buf.len() > 2 {
497
for i in 0..buf.len() - 2 {
498
let op = buf[i];
499
if op == gimli::constants::DW_OP_bra.0 || op == gimli::constants::DW_OP_skip.0 {
500
// TODO fix for big-endian
501
let offset = i16::from_le_bytes([buf[i + 1], buf[i + 2]]);
502
let origin = i + 3;
503
// Discarding out-of-bounds jumps (also some of falsely detected ops)
504
if (offset >= 0 && offset as usize + origin <= buf.len())
505
|| (offset < 0 && -offset as usize <= origin)
506
{
507
let target = buf.len() as isize - origin as isize - offset as isize;
508
jump_targets.insert(target as u64, JumpTargetMarker::new());
509
}
510
}
511
}
512
}
513
514
while !pc.is_empty() {
515
let unread_bytes = pc.len().into_u64();
516
if let Some(marker) = jump_targets.get(&unread_bytes) {
517
flush_code_chunk!();
518
parts.push(CompiledExpressionPart::LandingPad(marker.clone()));
519
}
520
521
need_deref = true;
522
523
let pos = pc.offset_from(&expr.0).into_u64() as usize;
524
let op = Operation::parse(&mut pc, encoding)?;
525
match op {
526
Operation::FrameOffset { offset } => {
527
// Expand DW_OP_fbreg into frame location and DW_OP_plus_uconst.
528
if frame_base.is_some() {
529
// Add frame base expressions.
530
flush_code_chunk!();
531
parts.extend_from_slice(&frame_base.unwrap().parts);
532
}
533
if let Some(CompiledExpressionPart::Local { trailing, .. }) = parts.last_mut() {
534
// Reset local trailing flag.
535
*trailing = false;
536
}
537
// Append DW_OP_plus_uconst part.
538
let mut writer = ExpressionWriter::new();
539
writer.write_op(gimli::constants::DW_OP_plus_uconst)?;
540
writer.write_uleb128(offset as u64)?;
541
code_chunk.extend(writer.into_vec());
542
continue;
543
}
544
Operation::Drop { .. }
545
| Operation::Pick { .. }
546
| Operation::Swap { .. }
547
| Operation::Rot { .. }
548
| Operation::Nop { .. }
549
| Operation::UnsignedConstant { .. }
550
| Operation::SignedConstant { .. }
551
| Operation::ConstantIndex { .. }
552
| Operation::PlusConstant { .. }
553
| Operation::Abs { .. }
554
| Operation::And { .. }
555
| Operation::Or { .. }
556
| Operation::Xor { .. }
557
| Operation::Shl { .. }
558
| Operation::Plus { .. }
559
| Operation::Minus { .. }
560
| Operation::Div { .. }
561
| Operation::Mod { .. }
562
| Operation::Mul { .. }
563
| Operation::Neg { .. }
564
| Operation::Not { .. }
565
| Operation::Lt { .. }
566
| Operation::Gt { .. }
567
| Operation::Le { .. }
568
| Operation::Ge { .. }
569
| Operation::Eq { .. }
570
| Operation::Ne { .. }
571
| Operation::TypedLiteral { .. }
572
| Operation::Convert { .. }
573
| Operation::Reinterpret { .. }
574
| Operation::Piece { .. } => (),
575
Operation::Bra { target } | Operation::Skip { target } => {
576
flush_code_chunk!();
577
let arc_to = (pc.len().into_u64() as isize - target as isize) as u64;
578
let marker = match jump_targets.get(&arc_to) {
579
Some(m) => m.clone(),
580
None => {
581
// Marker not found: probably out of bounds.
582
return Ok(None);
583
}
584
};
585
push!(CompiledExpressionPart::Jump {
586
conditionally: match op {
587
Operation::Bra { .. } => true,
588
_ => false,
589
},
590
target: marker,
591
});
592
continue;
593
}
594
Operation::StackValue => {
595
need_deref = false;
596
597
// Find extra stack_value, that follow wasm-local operators,
598
// and mark such locals with special flag.
599
if let (Some(CompiledExpressionPart::Local { trailing, .. }), true) =
600
(parts.last_mut(), code_chunk.is_empty())
601
{
602
*trailing = true;
603
continue;
604
}
605
}
606
Operation::Deref { .. } => {
607
flush_code_chunk!();
608
push!(CompiledExpressionPart::Deref);
609
// Don't re-enter the loop here (i.e. continue), because the
610
// DW_OP_deref still needs to be kept.
611
}
612
Operation::WasmLocal { index } => {
613
flush_code_chunk!();
614
let label = ValueLabel::from_u32(index);
615
push!(CompiledExpressionPart::Local {
616
label,
617
trailing: false,
618
});
619
continue;
620
}
621
Operation::Shr { .. } | Operation::Shra { .. } => {
622
// Insert value normalisation part.
623
// The semantic value is 32 bits (TODO: check unit)
624
// but the target architecture is 64-bits. So we'll
625
// clean out the upper 32 bits (in a sign-correct way)
626
// to avoid contamination of the result with randomness.
627
let mut writer = ExpressionWriter::new();
628
writer.write_op(gimli::constants::DW_OP_plus_uconst)?;
629
writer.write_uleb128(32)?; // increase shift amount
630
writer.write_op(gimli::constants::DW_OP_swap)?;
631
writer.write_op(gimli::constants::DW_OP_const1u)?;
632
writer.write_u8(32)?;
633
writer.write_op(gimli::constants::DW_OP_shl)?;
634
writer.write_op(gimli::constants::DW_OP_swap)?;
635
code_chunk.extend(writer.into_vec());
636
// Don't re-enter the loop here (i.e. continue), because the
637
// DW_OP_shr* still needs to be kept.
638
}
639
Operation::Address { .. }
640
| Operation::AddressIndex { .. }
641
| Operation::Call { .. }
642
| Operation::Register { .. }
643
| Operation::RegisterOffset { .. }
644
| Operation::CallFrameCFA
645
| Operation::PushObjectAddress
646
| Operation::TLS
647
| Operation::ImplicitValue { .. }
648
| Operation::ImplicitPointer { .. }
649
| Operation::EntryValue { .. }
650
| Operation::ParameterRef { .. }
651
| Operation::VariableValue { .. }
652
| Operation::Uninitialized => {
653
return Ok(None);
654
}
655
Operation::WasmGlobal { index: _ } | Operation::WasmStack { index: _ } => {
656
// TODO support those two
657
return Ok(None);
658
}
659
}
660
let chunk = &buf[pos..pc.offset_from(&expr.0).into_u64() as usize];
661
code_chunk.extend_from_slice(chunk);
662
}
663
664
flush_code_chunk!();
665
if let Some(marker) = jump_targets.get(&0) {
666
parts.push(CompiledExpressionPart::LandingPad(marker.clone()));
667
}
668
669
Ok(Some(CompiledExpression { parts, need_deref }))
670
}
671
672
#[derive(Debug, Clone)]
673
struct CachedValueLabelRange {
674
func_index: usize,
675
start: usize,
676
end: usize,
677
label_location: HashMap<ValueLabel, LabelValueLoc>,
678
}
679
680
struct BuiltRangeSummary<'a> {
681
range: &'a CachedValueLabelRange,
682
isa: &'a dyn TargetIsa,
683
}
684
685
impl<'a> fmt::Debug for BuiltRangeSummary<'a> {
686
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
687
let range = self.range;
688
write!(f, "[")?;
689
let mut is_first = true;
690
for (value, value_loc) in &range.label_location {
691
if !is_first {
692
write!(f, ", ")?;
693
} else {
694
is_first = false;
695
}
696
write!(
697
f,
698
"{:?}:{:?}",
699
log_get_value_name(*value),
700
log_get_value_loc(*value_loc, self.isa)
701
)?;
702
}
703
write!(f, "]@[{}..{})", range.start, range.end)?;
704
Ok(())
705
}
706
}
707
708
struct ValueLabelRangesBuilder<'a, 'b> {
709
isa: &'a dyn TargetIsa,
710
ranges: Vec<CachedValueLabelRange>,
711
frame_info: Option<&'a FunctionFrameInfo<'b>>,
712
processed_labels: HashSet<ValueLabel>,
713
covers_entire_scope: bool,
714
}
715
716
struct BuiltValueLabelRanges<TIter> {
717
ranges: TIter,
718
covers_entire_scope: bool,
719
}
720
721
impl<'a, 'b> ValueLabelRangesBuilder<'a, 'b> {
722
pub fn new(
723
scope: &[(u64, u64)], // wasm ranges
724
addr_tr: &'a AddressTransform,
725
frame_info: Option<&'a FunctionFrameInfo<'b>>,
726
isa: &'a dyn TargetIsa,
727
) -> Self {
728
let mut ranges = Vec::new();
729
for (wasm_start, wasm_end) in scope {
730
if let Some((func_index, tr)) = addr_tr.translate_ranges_raw(*wasm_start, *wasm_end) {
731
ranges.extend(tr.into_iter().map(|(start, end)| CachedValueLabelRange {
732
func_index,
733
start,
734
end,
735
label_location: HashMap::new(),
736
}));
737
}
738
}
739
ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start));
740
741
dbi_log!(
742
"Building ranges for values in scope: {}\n{:?}",
743
ranges
744
.iter()
745
.map(|r| format!("[{}..{})", r.start, r.end))
746
.join(" "),
747
log_get_value_ranges(frame_info.map(|f| f.value_ranges), isa)
748
);
749
ValueLabelRangesBuilder {
750
isa,
751
ranges,
752
frame_info,
753
processed_labels: HashSet::new(),
754
covers_entire_scope: true,
755
}
756
}
757
758
fn process_label(&mut self, label: ValueLabel) {
759
if self.processed_labels.contains(&label) {
760
return;
761
}
762
dbi_log!("Intersecting with {:?}", log_get_value_name(label));
763
self.processed_labels.insert(label);
764
765
let value_ranges = match self.frame_info.and_then(|fi| fi.value_ranges.get(&label)) {
766
Some(value_ranges) => value_ranges,
767
None => {
768
return;
769
}
770
};
771
772
let ranges = &mut self.ranges;
773
for value_range in value_ranges {
774
let range_start = value_range.start as usize;
775
let range_end = value_range.end as usize;
776
let loc = value_range.loc;
777
if range_start == range_end {
778
continue;
779
}
780
assert!(range_start < range_end);
781
782
// Find acceptable scope of ranges to intersect with.
783
let i = match ranges.binary_search_by(|s| s.start.cmp(&range_start)) {
784
Ok(i) => i,
785
Err(i) => {
786
if i > 0 && range_start < ranges[i - 1].end {
787
i - 1
788
} else {
789
i
790
}
791
}
792
};
793
let j = match ranges.binary_search_by(|s| s.start.cmp(&range_end)) {
794
Ok(i) | Err(i) => i,
795
};
796
// Starting from the end, intersect (range_start..range_end) with
797
// self.ranges array.
798
for i in (i..j).rev() {
799
if range_end <= ranges[i].start || ranges[i].end <= range_start {
800
continue;
801
}
802
if range_end < ranges[i].end {
803
// Cutting some of the range from the end.
804
let mut tail = ranges[i].clone();
805
ranges[i].end = range_end;
806
tail.start = range_end;
807
ranges.insert(i + 1, tail);
808
self.covers_entire_scope = false;
809
}
810
assert!(ranges[i].end <= range_end);
811
if range_start <= ranges[i].start {
812
ranges[i].label_location.insert(label, loc);
813
continue;
814
}
815
// Cutting some of the range from the start.
816
let mut tail = ranges[i].clone();
817
ranges[i].end = range_start;
818
tail.start = range_start;
819
tail.label_location.insert(label, loc);
820
ranges.insert(i + 1, tail);
821
self.covers_entire_scope = false;
822
}
823
}
824
}
825
826
pub fn into_ranges(
827
self,
828
) -> BuiltValueLabelRanges<impl Iterator<Item = CachedValueLabelRange> + use<>> {
829
// Ranges with not-enough labels are discarded.
830
let processed_labels_len = self.processed_labels.len();
831
let is_valid_range =
832
move |r: &CachedValueLabelRange| r.label_location.len() == processed_labels_len;
833
834
if dbi_log_enabled!() {
835
dbi_log!("Built ranges:");
836
for range in self.ranges.iter().filter(|r| is_valid_range(*r)) {
837
dbi_log!(
838
"{:?}",
839
BuiltRangeSummary {
840
range,
841
isa: self.isa
842
}
843
);
844
}
845
dbi_log!("");
846
}
847
848
BuiltValueLabelRanges {
849
ranges: self.ranges.into_iter().filter(is_valid_range),
850
covers_entire_scope: self.covers_entire_scope,
851
}
852
}
853
}
854
855
/// Marker for tracking incoming jumps.
856
/// Different when created new, and the same when cloned.
857
#[derive(Clone, Eq)]
858
struct JumpTargetMarker(Rc<u32>);
859
860
impl JumpTargetMarker {
861
fn new() -> JumpTargetMarker {
862
// Create somewhat unique hash data -- using part of
863
// the pointer of the RcBox.
864
let mut rc = Rc::new(0);
865
let hash_data = rc.as_ref() as *const u32 as usize as u32;
866
*Rc::get_mut(&mut rc).unwrap() = hash_data;
867
JumpTargetMarker(rc)
868
}
869
}
870
871
impl PartialEq for JumpTargetMarker {
872
fn eq(&self, other: &JumpTargetMarker) -> bool {
873
Rc::ptr_eq(&self.0, &other.0)
874
}
875
}
876
877
impl Hash for JumpTargetMarker {
878
fn hash<H: Hasher>(&self, hasher: &mut H) {
879
hasher.write_u32(*self.0);
880
}
881
}
882
impl std::fmt::Debug for JumpTargetMarker {
883
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
884
write!(
885
f,
886
"JumpMarker<{:08x}>",
887
self.0.as_ref() as *const u32 as usize
888
)
889
}
890
}
891
892
#[cfg(test)]
893
#[expect(trivial_numeric_casts, reason = "macro-generated code")]
894
mod tests {
895
use super::{
896
AddressTransform, CompiledExpression, CompiledExpressionPart, FunctionFrameInfo,
897
JumpTargetMarker, ValueLabel, ValueLabelsRanges, compile_expression,
898
};
899
use crate::CompiledFunctionMetadata;
900
use cranelift_codegen::{isa::lookup, settings::Flags};
901
use gimli::{Encoding, EndianSlice, Expression, RunTimeEndian, constants};
902
use target_lexicon::triple;
903
use wasmtime_environ::FilePos;
904
905
macro_rules! dw_op {
906
(DW_OP_WASM_location) => {
907
0xed
908
};
909
($i:literal) => {
910
$i
911
};
912
($d:ident) => {
913
constants::$d.0 as u8
914
};
915
($e:expr) => {
916
$e as u8
917
};
918
}
919
920
macro_rules! expression {
921
($($t:tt),*) => {
922
Expression(EndianSlice::new(
923
&[$(dw_op!($t)),*],
924
RunTimeEndian::Little,
925
))
926
}
927
}
928
929
fn find_jump_targets<'a>(ce: &'a CompiledExpression) -> Vec<&'a JumpTargetMarker> {
930
ce.parts
931
.iter()
932
.filter_map(|p| {
933
if let CompiledExpressionPart::LandingPad(t) = p {
934
Some(t)
935
} else {
936
None
937
}
938
})
939
.collect::<Vec<_>>()
940
}
941
942
static DWARF_ENCODING: Encoding = Encoding {
943
address_size: 4,
944
format: gimli::Format::Dwarf32,
945
version: 4,
946
};
947
948
#[test]
949
fn test_debug_expression_jump_target() {
950
let m1 = JumpTargetMarker::new();
951
let m2 = JumpTargetMarker::new();
952
assert!(m1 != m2);
953
assert!(m1 == m1.clone());
954
955
// Internal hash_data test (theoretically can fail intermittently).
956
assert!(m1.0 != m2.0);
957
}
958
959
#[test]
960
fn test_debug_parse_expressions() {
961
use cranelift_entity::EntityRef;
962
963
let (val1, val3, val20) = (ValueLabel::new(1), ValueLabel::new(3), ValueLabel::new(20));
964
965
let e = expression!(DW_OP_WASM_location, 0x0, 20, DW_OP_stack_value);
966
let ce = compile_expression(&e, DWARF_ENCODING, None)
967
.expect("non-error")
968
.expect("expression");
969
assert_eq!(
970
ce,
971
CompiledExpression {
972
parts: vec![CompiledExpressionPart::Local {
973
label: val20,
974
trailing: true
975
}],
976
need_deref: false,
977
}
978
);
979
980
let e = expression!(
981
DW_OP_WASM_location,
982
0x0,
983
1,
984
DW_OP_plus_uconst,
985
0x10,
986
DW_OP_stack_value
987
);
988
let ce = compile_expression(&e, DWARF_ENCODING, None)
989
.expect("non-error")
990
.expect("expression");
991
assert_eq!(
992
ce,
993
CompiledExpression {
994
parts: vec![
995
CompiledExpressionPart::Local {
996
label: val1,
997
trailing: false
998
},
999
CompiledExpressionPart::Code(vec![35, 16, 159])
1000
],
1001
need_deref: false,
1002
}
1003
);
1004
1005
let e = expression!(DW_OP_WASM_location, 0x0, 3, DW_OP_stack_value);
1006
let fe = compile_expression(&e, DWARF_ENCODING, None).expect("non-error");
1007
let e = expression!(DW_OP_fbreg, 0x12);
1008
let ce = compile_expression(&e, DWARF_ENCODING, fe.as_ref())
1009
.expect("non-error")
1010
.expect("expression");
1011
assert_eq!(
1012
ce,
1013
CompiledExpression {
1014
parts: vec![
1015
CompiledExpressionPart::Local {
1016
label: val3,
1017
trailing: false
1018
},
1019
CompiledExpressionPart::Code(vec![35, 18])
1020
],
1021
need_deref: true,
1022
}
1023
);
1024
1025
let e = expression!(
1026
DW_OP_WASM_location,
1027
0x0,
1028
1,
1029
DW_OP_plus_uconst,
1030
5,
1031
DW_OP_deref,
1032
DW_OP_stack_value
1033
);
1034
let ce = compile_expression(&e, DWARF_ENCODING, None)
1035
.expect("non-error")
1036
.expect("expression");
1037
assert_eq!(
1038
ce,
1039
CompiledExpression {
1040
parts: vec![
1041
CompiledExpressionPart::Local {
1042
label: val1,
1043
trailing: false
1044
},
1045
CompiledExpressionPart::Code(vec![35, 5]),
1046
CompiledExpressionPart::Deref,
1047
CompiledExpressionPart::Code(vec![6, 159])
1048
],
1049
need_deref: false,
1050
}
1051
);
1052
1053
let e = expression!(
1054
DW_OP_WASM_location,
1055
0x0,
1056
1,
1057
DW_OP_lit16,
1058
DW_OP_shra,
1059
DW_OP_stack_value
1060
);
1061
let ce = compile_expression(&e, DWARF_ENCODING, None)
1062
.expect("non-error")
1063
.expect("expression");
1064
assert_eq!(
1065
ce,
1066
CompiledExpression {
1067
parts: vec![
1068
CompiledExpressionPart::Local {
1069
label: val1,
1070
trailing: false
1071
},
1072
CompiledExpressionPart::Code(vec![64, 35, 32, 22, 8, 32, 36, 22, 38, 159])
1073
],
1074
need_deref: false,
1075
}
1076
);
1077
1078
let e = expression!(
1079
DW_OP_lit1,
1080
DW_OP_dup,
1081
DW_OP_WASM_location,
1082
0x0,
1083
1,
1084
DW_OP_and,
1085
DW_OP_bra,
1086
5,
1087
0, // --> pointer
1088
DW_OP_swap,
1089
DW_OP_shr,
1090
DW_OP_skip,
1091
2,
1092
0, // --> done
1093
// pointer:
1094
DW_OP_plus,
1095
DW_OP_deref,
1096
// done:
1097
DW_OP_stack_value
1098
);
1099
let ce = compile_expression(&e, DWARF_ENCODING, None)
1100
.expect("non-error")
1101
.expect("expression");
1102
let targets = find_jump_targets(&ce);
1103
assert_eq!(targets.len(), 2);
1104
assert_eq!(
1105
ce,
1106
CompiledExpression {
1107
parts: vec![
1108
CompiledExpressionPart::Code(vec![49, 18]),
1109
CompiledExpressionPart::Local {
1110
label: val1,
1111
trailing: false
1112
},
1113
CompiledExpressionPart::Code(vec![26]),
1114
CompiledExpressionPart::Jump {
1115
conditionally: true,
1116
target: targets[0].clone(),
1117
},
1118
CompiledExpressionPart::Code(vec![22, 35, 32, 22, 8, 32, 36, 22, 37]),
1119
CompiledExpressionPart::Jump {
1120
conditionally: false,
1121
target: targets[1].clone(),
1122
},
1123
CompiledExpressionPart::LandingPad(targets[0].clone()), // capture from
1124
CompiledExpressionPart::Code(vec![34]),
1125
CompiledExpressionPart::Deref,
1126
CompiledExpressionPart::Code(vec![6]),
1127
CompiledExpressionPart::LandingPad(targets[1].clone()), // capture to
1128
CompiledExpressionPart::Code(vec![159])
1129
],
1130
need_deref: false,
1131
}
1132
);
1133
1134
let e = expression!(
1135
DW_OP_lit1,
1136
DW_OP_dup,
1137
DW_OP_bra,
1138
2,
1139
0, // --> target
1140
DW_OP_deref,
1141
DW_OP_lit0,
1142
// target:
1143
DW_OP_stack_value
1144
);
1145
let ce = compile_expression(&e, DWARF_ENCODING, None)
1146
.expect("non-error")
1147
.expect("expression");
1148
let targets = find_jump_targets(&ce);
1149
assert_eq!(targets.len(), 1);
1150
assert_eq!(
1151
ce,
1152
CompiledExpression {
1153
parts: vec![
1154
CompiledExpressionPart::Code(vec![49, 18]),
1155
CompiledExpressionPart::Jump {
1156
conditionally: true,
1157
target: targets[0].clone(),
1158
},
1159
CompiledExpressionPart::Deref,
1160
CompiledExpressionPart::Code(vec![6, 48]),
1161
CompiledExpressionPart::LandingPad(targets[0].clone()), // capture to
1162
CompiledExpressionPart::Code(vec![159])
1163
],
1164
need_deref: false,
1165
}
1166
);
1167
1168
let e = expression!(
1169
DW_OP_lit1,
1170
/* loop */ DW_OP_dup,
1171
DW_OP_lit25,
1172
DW_OP_ge,
1173
DW_OP_bra,
1174
5,
1175
0, // --> done
1176
DW_OP_plus_uconst,
1177
1,
1178
DW_OP_skip,
1179
(-11_i8),
1180
(!0), // --> loop
1181
/* done */ DW_OP_stack_value
1182
);
1183
let ce = compile_expression(&e, DWARF_ENCODING, None)
1184
.expect("non-error")
1185
.expect("expression");
1186
let targets = find_jump_targets(&ce);
1187
assert_eq!(targets.len(), 2);
1188
assert_eq!(
1189
ce,
1190
CompiledExpression {
1191
parts: vec![
1192
CompiledExpressionPart::Code(vec![49]),
1193
CompiledExpressionPart::LandingPad(targets[0].clone()),
1194
CompiledExpressionPart::Code(vec![18, 73, 42]),
1195
CompiledExpressionPart::Jump {
1196
conditionally: true,
1197
target: targets[1].clone(),
1198
},
1199
CompiledExpressionPart::Code(vec![35, 1]),
1200
CompiledExpressionPart::Jump {
1201
conditionally: false,
1202
target: targets[0].clone(),
1203
},
1204
CompiledExpressionPart::LandingPad(targets[1].clone()),
1205
CompiledExpressionPart::Code(vec![159])
1206
],
1207
need_deref: false,
1208
}
1209
);
1210
1211
let e = expression!(DW_OP_WASM_location, 0x0, 1, DW_OP_plus_uconst, 5);
1212
let ce = compile_expression(&e, DWARF_ENCODING, None)
1213
.expect("non-error")
1214
.expect("expression");
1215
assert_eq!(
1216
ce,
1217
CompiledExpression {
1218
parts: vec![
1219
CompiledExpressionPart::Local {
1220
label: val1,
1221
trailing: false
1222
},
1223
CompiledExpressionPart::Code(vec![35, 5])
1224
],
1225
need_deref: true,
1226
}
1227
);
1228
}
1229
1230
fn create_mock_address_transform() -> AddressTransform {
1231
use crate::FunctionAddressMap;
1232
use cranelift_entity::PrimaryMap;
1233
use wasmtime_environ::InstructionAddressMap;
1234
use wasmtime_environ::WasmFileInfo;
1235
1236
let mut module_map = PrimaryMap::new();
1237
let code_section_offset: u32 = 100;
1238
let func = CompiledFunctionMetadata {
1239
address_map: FunctionAddressMap {
1240
instructions: vec![
1241
InstructionAddressMap {
1242
srcloc: FilePos::new(code_section_offset + 12),
1243
code_offset: 5,
1244
},
1245
InstructionAddressMap {
1246
srcloc: FilePos::default(),
1247
code_offset: 8,
1248
},
1249
InstructionAddressMap {
1250
srcloc: FilePos::new(code_section_offset + 17),
1251
code_offset: 15,
1252
},
1253
InstructionAddressMap {
1254
srcloc: FilePos::default(),
1255
code_offset: 23,
1256
},
1257
]
1258
.into(),
1259
start_srcloc: FilePos::new(code_section_offset + 10),
1260
end_srcloc: FilePos::new(code_section_offset + 20),
1261
body_offset: 0,
1262
body_len: 30,
1263
},
1264
..Default::default()
1265
};
1266
module_map.push(&func);
1267
let fi = WasmFileInfo {
1268
code_section_offset: code_section_offset.into(),
1269
funcs: Vec::new(),
1270
imported_func_count: 0,
1271
path: None,
1272
};
1273
AddressTransform::mock(&module_map, fi)
1274
}
1275
1276
fn create_mock_value_ranges() -> (ValueLabelsRanges, (ValueLabel, ValueLabel, ValueLabel)) {
1277
use cranelift_codegen::{LabelValueLoc, ValueLocRange};
1278
use cranelift_entity::EntityRef;
1279
use std::collections::HashMap;
1280
let mut value_ranges = HashMap::new();
1281
let value_0 = ValueLabel::new(0);
1282
let value_1 = ValueLabel::new(1);
1283
let value_2 = ValueLabel::new(2);
1284
value_ranges.insert(
1285
value_0,
1286
vec![ValueLocRange {
1287
loc: LabelValueLoc::CFAOffset(0),
1288
start: 0,
1289
end: 25,
1290
}],
1291
);
1292
value_ranges.insert(
1293
value_1,
1294
vec![ValueLocRange {
1295
loc: LabelValueLoc::CFAOffset(0),
1296
start: 5,
1297
end: 30,
1298
}],
1299
);
1300
value_ranges.insert(
1301
value_2,
1302
vec![
1303
ValueLocRange {
1304
loc: LabelValueLoc::CFAOffset(0),
1305
start: 0,
1306
end: 10,
1307
},
1308
ValueLocRange {
1309
loc: LabelValueLoc::CFAOffset(0),
1310
start: 20,
1311
end: 30,
1312
},
1313
],
1314
);
1315
(value_ranges, (value_0, value_1, value_2))
1316
}
1317
1318
#[test]
1319
fn test_debug_value_range_builder() {
1320
use super::ValueLabelRangesBuilder;
1321
use crate::debug::ModuleMemoryOffset;
1322
1323
// Ignore this test if cranelift doesn't support the native platform.
1324
if cranelift_native::builder().is_err() {
1325
return;
1326
}
1327
1328
let isa = lookup(triple!("x86_64"))
1329
.expect("expect x86_64 ISA")
1330
.finish(Flags::new(cranelift_codegen::settings::builder()))
1331
.expect("Creating ISA");
1332
1333
let addr_tr = create_mock_address_transform();
1334
let (value_ranges, value_labels) = create_mock_value_ranges();
1335
let fi = FunctionFrameInfo {
1336
memory_offset: ModuleMemoryOffset::None,
1337
value_ranges: &value_ranges,
1338
};
1339
1340
// No value labels, testing if entire function range coming through.
1341
let builder = ValueLabelRangesBuilder::new(&[(10, 20)], &addr_tr, Some(&fi), isa.as_ref());
1342
let ranges = builder.into_ranges().ranges.collect::<Vec<_>>();
1343
assert_eq!(ranges.len(), 1);
1344
assert_eq!(ranges[0].func_index, 0);
1345
assert_eq!(ranges[0].start, 0);
1346
assert_eq!(ranges[0].end, 30);
1347
1348
// Two labels ([email protected] and [email protected]), their common lifetime intersect at 5..25.
1349
let mut builder =
1350
ValueLabelRangesBuilder::new(&[(10, 20)], &addr_tr, Some(&fi), isa.as_ref());
1351
builder.process_label(value_labels.0);
1352
builder.process_label(value_labels.1);
1353
let ranges = builder.into_ranges().ranges.collect::<Vec<_>>();
1354
assert_eq!(ranges.len(), 1);
1355
assert_eq!(ranges[0].start, 5);
1356
assert_eq!(ranges[0].end, 25);
1357
1358
// Adds val2 with complex lifetime @0..10 and @20..30 to the previous test, and
1359
// also narrows range.
1360
let mut builder =
1361
ValueLabelRangesBuilder::new(&[(11, 17)], &addr_tr, Some(&fi), isa.as_ref());
1362
builder.process_label(value_labels.0);
1363
builder.process_label(value_labels.1);
1364
builder.process_label(value_labels.2);
1365
let ranges = builder.into_ranges().ranges.collect::<Vec<_>>();
1366
// Result is two ranges @5..10 and @20..23
1367
assert_eq!(ranges.len(), 2);
1368
assert_eq!(ranges[0].start, 5);
1369
assert_eq!(ranges[0].end, 10);
1370
assert_eq!(ranges[1].start, 20);
1371
assert_eq!(ranges[1].end, 23);
1372
}
1373
}
1374
1375