Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/cranelift/src/debug/transform/expression.rs
1693 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 anyhow::{Context, Error, Result};
9
use core::fmt;
10
use cranelift_codegen::LabelValueLoc;
11
use cranelift_codegen::ValueLabelsRanges;
12
use cranelift_codegen::ir::ValueLabel;
13
use cranelift_codegen::isa::TargetIsa;
14
use gimli::{Expression, Operation, Reader, ReaderOffset, write};
15
use itertools::Itertools;
16
use std::cmp::PartialEq;
17
use std::collections::{HashMap, HashSet};
18
use std::hash::{Hash, Hasher};
19
use std::rc::Rc;
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
return Ok(None);
652
}
653
Operation::WasmGlobal { index: _ } | Operation::WasmStack { index: _ } => {
654
// TODO support those two
655
return Ok(None);
656
}
657
}
658
let chunk = &buf[pos..pc.offset_from(&expr.0).into_u64() as usize];
659
code_chunk.extend_from_slice(chunk);
660
}
661
662
flush_code_chunk!();
663
if let Some(marker) = jump_targets.get(&0) {
664
parts.push(CompiledExpressionPart::LandingPad(marker.clone()));
665
}
666
667
Ok(Some(CompiledExpression { parts, need_deref }))
668
}
669
670
#[derive(Debug, Clone)]
671
struct CachedValueLabelRange {
672
func_index: usize,
673
start: usize,
674
end: usize,
675
label_location: HashMap<ValueLabel, LabelValueLoc>,
676
}
677
678
struct BuiltRangeSummary<'a> {
679
range: &'a CachedValueLabelRange,
680
isa: &'a dyn TargetIsa,
681
}
682
683
impl<'a> fmt::Debug for BuiltRangeSummary<'a> {
684
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
685
let range = self.range;
686
write!(f, "[")?;
687
let mut is_first = true;
688
for (value, value_loc) in &range.label_location {
689
if !is_first {
690
write!(f, ", ")?;
691
} else {
692
is_first = false;
693
}
694
write!(
695
f,
696
"{:?}:{:?}",
697
log_get_value_name(*value),
698
log_get_value_loc(*value_loc, self.isa)
699
)?;
700
}
701
write!(f, "]@[{}..{})", range.start, range.end)?;
702
Ok(())
703
}
704
}
705
706
struct ValueLabelRangesBuilder<'a, 'b> {
707
isa: &'a dyn TargetIsa,
708
ranges: Vec<CachedValueLabelRange>,
709
frame_info: Option<&'a FunctionFrameInfo<'b>>,
710
processed_labels: HashSet<ValueLabel>,
711
covers_entire_scope: bool,
712
}
713
714
struct BuiltValueLabelRanges<TIter> {
715
ranges: TIter,
716
covers_entire_scope: bool,
717
}
718
719
impl<'a, 'b> ValueLabelRangesBuilder<'a, 'b> {
720
pub fn new(
721
scope: &[(u64, u64)], // wasm ranges
722
addr_tr: &'a AddressTransform,
723
frame_info: Option<&'a FunctionFrameInfo<'b>>,
724
isa: &'a dyn TargetIsa,
725
) -> Self {
726
let mut ranges = Vec::new();
727
for (wasm_start, wasm_end) in scope {
728
if let Some((func_index, tr)) = addr_tr.translate_ranges_raw(*wasm_start, *wasm_end) {
729
ranges.extend(tr.into_iter().map(|(start, end)| CachedValueLabelRange {
730
func_index,
731
start,
732
end,
733
label_location: HashMap::new(),
734
}));
735
}
736
}
737
ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start));
738
739
dbi_log!(
740
"Building ranges for values in scope: {}\n{:?}",
741
ranges
742
.iter()
743
.map(|r| format!("[{}..{})", r.start, r.end))
744
.join(" "),
745
log_get_value_ranges(frame_info.map(|f| f.value_ranges), isa)
746
);
747
ValueLabelRangesBuilder {
748
isa,
749
ranges,
750
frame_info,
751
processed_labels: HashSet::new(),
752
covers_entire_scope: true,
753
}
754
}
755
756
fn process_label(&mut self, label: ValueLabel) {
757
if self.processed_labels.contains(&label) {
758
return;
759
}
760
dbi_log!("Intersecting with {:?}", log_get_value_name(label));
761
self.processed_labels.insert(label);
762
763
let value_ranges = match self.frame_info.and_then(|fi| fi.value_ranges.get(&label)) {
764
Some(value_ranges) => value_ranges,
765
None => {
766
return;
767
}
768
};
769
770
let ranges = &mut self.ranges;
771
for value_range in value_ranges {
772
let range_start = value_range.start as usize;
773
let range_end = value_range.end as usize;
774
let loc = value_range.loc;
775
if range_start == range_end {
776
continue;
777
}
778
assert!(range_start < range_end);
779
780
// Find acceptable scope of ranges to intersect with.
781
let i = match ranges.binary_search_by(|s| s.start.cmp(&range_start)) {
782
Ok(i) => i,
783
Err(i) => {
784
if i > 0 && range_start < ranges[i - 1].end {
785
i - 1
786
} else {
787
i
788
}
789
}
790
};
791
let j = match ranges.binary_search_by(|s| s.start.cmp(&range_end)) {
792
Ok(i) | Err(i) => i,
793
};
794
// Starting from the end, intersect (range_start..range_end) with
795
// self.ranges array.
796
for i in (i..j).rev() {
797
if range_end <= ranges[i].start || ranges[i].end <= range_start {
798
continue;
799
}
800
if range_end < ranges[i].end {
801
// Cutting some of the range from the end.
802
let mut tail = ranges[i].clone();
803
ranges[i].end = range_end;
804
tail.start = range_end;
805
ranges.insert(i + 1, tail);
806
self.covers_entire_scope = false;
807
}
808
assert!(ranges[i].end <= range_end);
809
if range_start <= ranges[i].start {
810
ranges[i].label_location.insert(label, loc);
811
continue;
812
}
813
// Cutting some of the range from the start.
814
let mut tail = ranges[i].clone();
815
ranges[i].end = range_start;
816
tail.start = range_start;
817
tail.label_location.insert(label, loc);
818
ranges.insert(i + 1, tail);
819
self.covers_entire_scope = false;
820
}
821
}
822
}
823
824
pub fn into_ranges(
825
self,
826
) -> BuiltValueLabelRanges<impl Iterator<Item = CachedValueLabelRange> + use<>> {
827
// Ranges with not-enough labels are discarded.
828
let processed_labels_len = self.processed_labels.len();
829
let is_valid_range =
830
move |r: &CachedValueLabelRange| r.label_location.len() == processed_labels_len;
831
832
if dbi_log_enabled!() {
833
dbi_log!("Built ranges:");
834
for range in self.ranges.iter().filter(|r| is_valid_range(*r)) {
835
dbi_log!(
836
"{:?}",
837
BuiltRangeSummary {
838
range,
839
isa: self.isa
840
}
841
);
842
}
843
dbi_log!("");
844
}
845
846
BuiltValueLabelRanges {
847
ranges: self.ranges.into_iter().filter(is_valid_range),
848
covers_entire_scope: self.covers_entire_scope,
849
}
850
}
851
}
852
853
/// Marker for tracking incoming jumps.
854
/// Different when created new, and the same when cloned.
855
#[derive(Clone, Eq)]
856
struct JumpTargetMarker(Rc<u32>);
857
858
impl JumpTargetMarker {
859
fn new() -> JumpTargetMarker {
860
// Create somewhat unique hash data -- using part of
861
// the pointer of the RcBox.
862
let mut rc = Rc::new(0);
863
let hash_data = rc.as_ref() as *const u32 as usize as u32;
864
*Rc::get_mut(&mut rc).unwrap() = hash_data;
865
JumpTargetMarker(rc)
866
}
867
}
868
869
impl PartialEq for JumpTargetMarker {
870
fn eq(&self, other: &JumpTargetMarker) -> bool {
871
Rc::ptr_eq(&self.0, &other.0)
872
}
873
}
874
875
impl Hash for JumpTargetMarker {
876
fn hash<H: Hasher>(&self, hasher: &mut H) {
877
hasher.write_u32(*self.0);
878
}
879
}
880
impl std::fmt::Debug for JumpTargetMarker {
881
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
882
write!(
883
f,
884
"JumpMarker<{:08x}>",
885
self.0.as_ref() as *const u32 as usize
886
)
887
}
888
}
889
890
#[cfg(test)]
891
#[expect(trivial_numeric_casts, reason = "macro-generated code")]
892
mod tests {
893
use super::{
894
AddressTransform, CompiledExpression, CompiledExpressionPart, FunctionFrameInfo,
895
JumpTargetMarker, ValueLabel, ValueLabelsRanges, compile_expression,
896
};
897
use crate::CompiledFunctionMetadata;
898
use cranelift_codegen::{isa::lookup, settings::Flags};
899
use gimli::{Encoding, EndianSlice, Expression, RunTimeEndian, constants};
900
use target_lexicon::triple;
901
use wasmtime_environ::FilePos;
902
903
macro_rules! dw_op {
904
(DW_OP_WASM_location) => {
905
0xed
906
};
907
($i:literal) => {
908
$i
909
};
910
($d:ident) => {
911
constants::$d.0 as u8
912
};
913
($e:expr) => {
914
$e as u8
915
};
916
}
917
918
macro_rules! expression {
919
($($t:tt),*) => {
920
Expression(EndianSlice::new(
921
&[$(dw_op!($t)),*],
922
RunTimeEndian::Little,
923
))
924
}
925
}
926
927
fn find_jump_targets<'a>(ce: &'a CompiledExpression) -> Vec<&'a JumpTargetMarker> {
928
ce.parts
929
.iter()
930
.filter_map(|p| {
931
if let CompiledExpressionPart::LandingPad(t) = p {
932
Some(t)
933
} else {
934
None
935
}
936
})
937
.collect::<Vec<_>>()
938
}
939
940
static DWARF_ENCODING: Encoding = Encoding {
941
address_size: 4,
942
format: gimli::Format::Dwarf32,
943
version: 4,
944
};
945
946
#[test]
947
fn test_debug_expression_jump_target() {
948
let m1 = JumpTargetMarker::new();
949
let m2 = JumpTargetMarker::new();
950
assert!(m1 != m2);
951
assert!(m1 == m1.clone());
952
953
// Internal hash_data test (theoretically can fail intermittently).
954
assert!(m1.0 != m2.0);
955
}
956
957
#[test]
958
fn test_debug_parse_expressions() {
959
use cranelift_entity::EntityRef;
960
961
let (val1, val3, val20) = (ValueLabel::new(1), ValueLabel::new(3), ValueLabel::new(20));
962
963
let e = expression!(DW_OP_WASM_location, 0x0, 20, DW_OP_stack_value);
964
let ce = compile_expression(&e, DWARF_ENCODING, None)
965
.expect("non-error")
966
.expect("expression");
967
assert_eq!(
968
ce,
969
CompiledExpression {
970
parts: vec![CompiledExpressionPart::Local {
971
label: val20,
972
trailing: true
973
}],
974
need_deref: false,
975
}
976
);
977
978
let e = expression!(
979
DW_OP_WASM_location,
980
0x0,
981
1,
982
DW_OP_plus_uconst,
983
0x10,
984
DW_OP_stack_value
985
);
986
let ce = compile_expression(&e, DWARF_ENCODING, None)
987
.expect("non-error")
988
.expect("expression");
989
assert_eq!(
990
ce,
991
CompiledExpression {
992
parts: vec![
993
CompiledExpressionPart::Local {
994
label: val1,
995
trailing: false
996
},
997
CompiledExpressionPart::Code(vec![35, 16, 159])
998
],
999
need_deref: false,
1000
}
1001
);
1002
1003
let e = expression!(DW_OP_WASM_location, 0x0, 3, DW_OP_stack_value);
1004
let fe = compile_expression(&e, DWARF_ENCODING, None).expect("non-error");
1005
let e = expression!(DW_OP_fbreg, 0x12);
1006
let ce = compile_expression(&e, DWARF_ENCODING, fe.as_ref())
1007
.expect("non-error")
1008
.expect("expression");
1009
assert_eq!(
1010
ce,
1011
CompiledExpression {
1012
parts: vec![
1013
CompiledExpressionPart::Local {
1014
label: val3,
1015
trailing: false
1016
},
1017
CompiledExpressionPart::Code(vec![35, 18])
1018
],
1019
need_deref: true,
1020
}
1021
);
1022
1023
let e = expression!(
1024
DW_OP_WASM_location,
1025
0x0,
1026
1,
1027
DW_OP_plus_uconst,
1028
5,
1029
DW_OP_deref,
1030
DW_OP_stack_value
1031
);
1032
let ce = compile_expression(&e, DWARF_ENCODING, None)
1033
.expect("non-error")
1034
.expect("expression");
1035
assert_eq!(
1036
ce,
1037
CompiledExpression {
1038
parts: vec![
1039
CompiledExpressionPart::Local {
1040
label: val1,
1041
trailing: false
1042
},
1043
CompiledExpressionPart::Code(vec![35, 5]),
1044
CompiledExpressionPart::Deref,
1045
CompiledExpressionPart::Code(vec![6, 159])
1046
],
1047
need_deref: false,
1048
}
1049
);
1050
1051
let e = expression!(
1052
DW_OP_WASM_location,
1053
0x0,
1054
1,
1055
DW_OP_lit16,
1056
DW_OP_shra,
1057
DW_OP_stack_value
1058
);
1059
let ce = compile_expression(&e, DWARF_ENCODING, None)
1060
.expect("non-error")
1061
.expect("expression");
1062
assert_eq!(
1063
ce,
1064
CompiledExpression {
1065
parts: vec![
1066
CompiledExpressionPart::Local {
1067
label: val1,
1068
trailing: false
1069
},
1070
CompiledExpressionPart::Code(vec![64, 35, 32, 22, 8, 32, 36, 22, 38, 159])
1071
],
1072
need_deref: false,
1073
}
1074
);
1075
1076
let e = expression!(
1077
DW_OP_lit1,
1078
DW_OP_dup,
1079
DW_OP_WASM_location,
1080
0x0,
1081
1,
1082
DW_OP_and,
1083
DW_OP_bra,
1084
5,
1085
0, // --> pointer
1086
DW_OP_swap,
1087
DW_OP_shr,
1088
DW_OP_skip,
1089
2,
1090
0, // --> done
1091
// pointer:
1092
DW_OP_plus,
1093
DW_OP_deref,
1094
// done:
1095
DW_OP_stack_value
1096
);
1097
let ce = compile_expression(&e, DWARF_ENCODING, None)
1098
.expect("non-error")
1099
.expect("expression");
1100
let targets = find_jump_targets(&ce);
1101
assert_eq!(targets.len(), 2);
1102
assert_eq!(
1103
ce,
1104
CompiledExpression {
1105
parts: vec![
1106
CompiledExpressionPart::Code(vec![49, 18]),
1107
CompiledExpressionPart::Local {
1108
label: val1,
1109
trailing: false
1110
},
1111
CompiledExpressionPart::Code(vec![26]),
1112
CompiledExpressionPart::Jump {
1113
conditionally: true,
1114
target: targets[0].clone(),
1115
},
1116
CompiledExpressionPart::Code(vec![22, 35, 32, 22, 8, 32, 36, 22, 37]),
1117
CompiledExpressionPart::Jump {
1118
conditionally: false,
1119
target: targets[1].clone(),
1120
},
1121
CompiledExpressionPart::LandingPad(targets[0].clone()), // capture from
1122
CompiledExpressionPart::Code(vec![34]),
1123
CompiledExpressionPart::Deref,
1124
CompiledExpressionPart::Code(vec![6]),
1125
CompiledExpressionPart::LandingPad(targets[1].clone()), // capture to
1126
CompiledExpressionPart::Code(vec![159])
1127
],
1128
need_deref: false,
1129
}
1130
);
1131
1132
let e = expression!(
1133
DW_OP_lit1,
1134
DW_OP_dup,
1135
DW_OP_bra,
1136
2,
1137
0, // --> target
1138
DW_OP_deref,
1139
DW_OP_lit0,
1140
// target:
1141
DW_OP_stack_value
1142
);
1143
let ce = compile_expression(&e, DWARF_ENCODING, None)
1144
.expect("non-error")
1145
.expect("expression");
1146
let targets = find_jump_targets(&ce);
1147
assert_eq!(targets.len(), 1);
1148
assert_eq!(
1149
ce,
1150
CompiledExpression {
1151
parts: vec![
1152
CompiledExpressionPart::Code(vec![49, 18]),
1153
CompiledExpressionPart::Jump {
1154
conditionally: true,
1155
target: targets[0].clone(),
1156
},
1157
CompiledExpressionPart::Deref,
1158
CompiledExpressionPart::Code(vec![6, 48]),
1159
CompiledExpressionPart::LandingPad(targets[0].clone()), // capture to
1160
CompiledExpressionPart::Code(vec![159])
1161
],
1162
need_deref: false,
1163
}
1164
);
1165
1166
let e = expression!(
1167
DW_OP_lit1,
1168
/* loop */ DW_OP_dup,
1169
DW_OP_lit25,
1170
DW_OP_ge,
1171
DW_OP_bra,
1172
5,
1173
0, // --> done
1174
DW_OP_plus_uconst,
1175
1,
1176
DW_OP_skip,
1177
(-11_i8),
1178
(!0), // --> loop
1179
/* done */ DW_OP_stack_value
1180
);
1181
let ce = compile_expression(&e, DWARF_ENCODING, None)
1182
.expect("non-error")
1183
.expect("expression");
1184
let targets = find_jump_targets(&ce);
1185
assert_eq!(targets.len(), 2);
1186
assert_eq!(
1187
ce,
1188
CompiledExpression {
1189
parts: vec![
1190
CompiledExpressionPart::Code(vec![49]),
1191
CompiledExpressionPart::LandingPad(targets[0].clone()),
1192
CompiledExpressionPart::Code(vec![18, 73, 42]),
1193
CompiledExpressionPart::Jump {
1194
conditionally: true,
1195
target: targets[1].clone(),
1196
},
1197
CompiledExpressionPart::Code(vec![35, 1]),
1198
CompiledExpressionPart::Jump {
1199
conditionally: false,
1200
target: targets[0].clone(),
1201
},
1202
CompiledExpressionPart::LandingPad(targets[1].clone()),
1203
CompiledExpressionPart::Code(vec![159])
1204
],
1205
need_deref: false,
1206
}
1207
);
1208
1209
let e = expression!(DW_OP_WASM_location, 0x0, 1, DW_OP_plus_uconst, 5);
1210
let ce = compile_expression(&e, DWARF_ENCODING, None)
1211
.expect("non-error")
1212
.expect("expression");
1213
assert_eq!(
1214
ce,
1215
CompiledExpression {
1216
parts: vec![
1217
CompiledExpressionPart::Local {
1218
label: val1,
1219
trailing: false
1220
},
1221
CompiledExpressionPart::Code(vec![35, 5])
1222
],
1223
need_deref: true,
1224
}
1225
);
1226
}
1227
1228
fn create_mock_address_transform() -> AddressTransform {
1229
use crate::FunctionAddressMap;
1230
use cranelift_entity::PrimaryMap;
1231
use wasmtime_environ::InstructionAddressMap;
1232
use wasmtime_environ::WasmFileInfo;
1233
1234
let mut module_map = PrimaryMap::new();
1235
let code_section_offset: u32 = 100;
1236
let func = CompiledFunctionMetadata {
1237
address_map: FunctionAddressMap {
1238
instructions: vec![
1239
InstructionAddressMap {
1240
srcloc: FilePos::new(code_section_offset + 12),
1241
code_offset: 5,
1242
},
1243
InstructionAddressMap {
1244
srcloc: FilePos::default(),
1245
code_offset: 8,
1246
},
1247
InstructionAddressMap {
1248
srcloc: FilePos::new(code_section_offset + 17),
1249
code_offset: 15,
1250
},
1251
InstructionAddressMap {
1252
srcloc: FilePos::default(),
1253
code_offset: 23,
1254
},
1255
]
1256
.into(),
1257
start_srcloc: FilePos::new(code_section_offset + 10),
1258
end_srcloc: FilePos::new(code_section_offset + 20),
1259
body_offset: 0,
1260
body_len: 30,
1261
},
1262
..Default::default()
1263
};
1264
module_map.push(&func);
1265
let fi = WasmFileInfo {
1266
code_section_offset: code_section_offset.into(),
1267
funcs: Vec::new(),
1268
imported_func_count: 0,
1269
path: None,
1270
};
1271
AddressTransform::mock(&module_map, fi)
1272
}
1273
1274
fn create_mock_value_ranges() -> (ValueLabelsRanges, (ValueLabel, ValueLabel, ValueLabel)) {
1275
use cranelift_codegen::{LabelValueLoc, ValueLocRange};
1276
use cranelift_entity::EntityRef;
1277
use std::collections::HashMap;
1278
let mut value_ranges = HashMap::new();
1279
let value_0 = ValueLabel::new(0);
1280
let value_1 = ValueLabel::new(1);
1281
let value_2 = ValueLabel::new(2);
1282
value_ranges.insert(
1283
value_0,
1284
vec![ValueLocRange {
1285
loc: LabelValueLoc::CFAOffset(0),
1286
start: 0,
1287
end: 25,
1288
}],
1289
);
1290
value_ranges.insert(
1291
value_1,
1292
vec![ValueLocRange {
1293
loc: LabelValueLoc::CFAOffset(0),
1294
start: 5,
1295
end: 30,
1296
}],
1297
);
1298
value_ranges.insert(
1299
value_2,
1300
vec![
1301
ValueLocRange {
1302
loc: LabelValueLoc::CFAOffset(0),
1303
start: 0,
1304
end: 10,
1305
},
1306
ValueLocRange {
1307
loc: LabelValueLoc::CFAOffset(0),
1308
start: 20,
1309
end: 30,
1310
},
1311
],
1312
);
1313
(value_ranges, (value_0, value_1, value_2))
1314
}
1315
1316
#[test]
1317
fn test_debug_value_range_builder() {
1318
use super::ValueLabelRangesBuilder;
1319
use crate::debug::ModuleMemoryOffset;
1320
1321
// Ignore this test if cranelift doesn't support the native platform.
1322
if cranelift_native::builder().is_err() {
1323
return;
1324
}
1325
1326
let isa = lookup(triple!("x86_64"))
1327
.expect("expect x86_64 ISA")
1328
.finish(Flags::new(cranelift_codegen::settings::builder()))
1329
.expect("Creating ISA");
1330
1331
let addr_tr = create_mock_address_transform();
1332
let (value_ranges, value_labels) = create_mock_value_ranges();
1333
let fi = FunctionFrameInfo {
1334
memory_offset: ModuleMemoryOffset::None,
1335
value_ranges: &value_ranges,
1336
};
1337
1338
// No value labels, testing if entire function range coming through.
1339
let builder = ValueLabelRangesBuilder::new(&[(10, 20)], &addr_tr, Some(&fi), isa.as_ref());
1340
let ranges = builder.into_ranges().ranges.collect::<Vec<_>>();
1341
assert_eq!(ranges.len(), 1);
1342
assert_eq!(ranges[0].func_index, 0);
1343
assert_eq!(ranges[0].start, 0);
1344
assert_eq!(ranges[0].end, 30);
1345
1346
// Two labels ([email protected] and [email protected]), their common lifetime intersect at 5..25.
1347
let mut builder =
1348
ValueLabelRangesBuilder::new(&[(10, 20)], &addr_tr, Some(&fi), isa.as_ref());
1349
builder.process_label(value_labels.0);
1350
builder.process_label(value_labels.1);
1351
let ranges = builder.into_ranges().ranges.collect::<Vec<_>>();
1352
assert_eq!(ranges.len(), 1);
1353
assert_eq!(ranges[0].start, 5);
1354
assert_eq!(ranges[0].end, 25);
1355
1356
// Adds val2 with complex lifetime @0..10 and @20..30 to the previous test, and
1357
// also narrows range.
1358
let mut builder =
1359
ValueLabelRangesBuilder::new(&[(11, 17)], &addr_tr, Some(&fi), isa.as_ref());
1360
builder.process_label(value_labels.0);
1361
builder.process_label(value_labels.1);
1362
builder.process_label(value_labels.2);
1363
let ranges = builder.into_ranges().ranges.collect::<Vec<_>>();
1364
// Result is two ranges @5..10 and @20..23
1365
assert_eq!(ranges.len(), 2);
1366
assert_eq!(ranges[0].start, 5);
1367
assert_eq!(ranges[0].end, 10);
1368
assert_eq!(ranges[1].start, 20);
1369
assert_eq!(ranges[1].end, 23);
1370
}
1371
}
1372
1373