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 {
new() -> Self35 fn new() -> Self {
36 let endian = gimli::RunTimeEndian::Little;
37 let writer = write::EndianVec::new(endian);
38 ExpressionWriter(writer)
39 }
40
write_op(&mut self, op: gimli::DwOp) -> write::Result<()>41 fn write_op(&mut self, op: gimli::DwOp) -> write::Result<()> {
42 self.write_u8(op.0)
43 }
44
write_op_reg(&mut self, reg: u16) -> write::Result<()>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
write_op_breg(&mut self, reg: u16) -> write::Result<()>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
write_u8(&mut self, b: u8) -> write::Result<()>63 fn write_u8(&mut self, b: u8) -> write::Result<()> {
64 write::Writer::write_u8(&mut self.0, b)
65 }
66
write_u32(&mut self, b: u32) -> write::Result<()>67 fn write_u32(&mut self, b: u32) -> write::Result<()> {
68 write::Writer::write_u32(&mut self.0, b)
69 }
70
write_uleb128(&mut self, i: u64) -> write::Result<()>71 fn write_uleb128(&mut self, i: u64) -> write::Result<()> {
72 write::Writer::write_uleb128(&mut self.0, i)
73 }
74
write_sleb128(&mut self, i: i64) -> write::Result<()>75 fn write_sleb128(&mut self, i: i64) -> write::Result<()> {
76 write::Writer::write_sleb128(&mut self.0, i)
77 }
78
into_vec(self) -> Vec<u8>79 fn into_vec(self) -> Vec<u8> {
80 self.0.into_vec()
81 }
82
gen_address_of_memory_base_pointer( &mut self, vmctx: VmctxBase, memory_base: &ModuleMemoryOffset, ) -> write::Result<()>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 {
vmctx() -> CompiledExpression159 pub fn vmctx() -> CompiledExpression {
160 CompiledExpression::from_label(get_vmctx_value_label())
161 }
162
from_label(label: ValueLabel) -> CompiledExpression163 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
translate_loc( loc: LabelValueLoc, isa: &dyn TargetIsa, add_stack_value: bool, ) -> Result<Option<Vec<u8>>>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
append_memory_deref( buf: &mut Vec<u8>, frame_info: &FunctionFrameInfo, vmctx_loc: LabelValueLoc, isa: &dyn TargetIsa, ) -> Result<bool>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 {
is_simple(&self) -> bool236 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
build(&self) -> Option<write::Expression>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
build_with_locals<'a>( &'a self, scope: &'a [(u64, u64)], addr_tr: &'a AddressTransform, frame_info: Option<&'a FunctionFrameInfo>, isa: &'a dyn TargetIsa, ) -> BuiltCompiledExpression< impl Iterator<Item = Result<(write::Address, u64, write::Expression)>> + use<'a>, >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
is_old_expression_format(buf: &[u8]) -> bool426 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
compile_expression<R>( expr: &Expression<R>, encoding: gimli::Encoding, frame_base: Option<&CompiledExpression>, ) -> Result<Option<CompiledExpression>, Error> where R: Reader,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> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result686 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> {
new( scope: &[(u64, u64)], addr_tr: &'a AddressTransform, frame_info: Option<&'a FunctionFrameInfo<'b>>, isa: &'a dyn TargetIsa, ) -> Self722 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
process_label(&mut self, label: ValueLabel)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
into_ranges( self, ) -> BuiltValueLabelRanges<impl Iterator<Item = CachedValueLabelRange> + use<>>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 {
new() -> JumpTargetMarker861 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 {
eq(&self, other: &JumpTargetMarker) -> bool872 fn eq(&self, other: &JumpTargetMarker) -> bool {
873 Rc::ptr_eq(&self.0, &other.0)
874 }
875 }
876
877 impl Hash for JumpTargetMarker {
hash<H: Hasher>(&self, hasher: &mut H)878 fn hash<H: Hasher>(&self, hasher: &mut H) {
879 hasher.write_u32(*self.0);
880 }
881 }
882 impl std::fmt::Debug for JumpTargetMarker {
fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error>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
find_jump_targets<'a>(ce: &'a CompiledExpression) -> Vec<&'a JumpTargetMarker>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]
test_debug_expression_jump_target()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]
test_debug_parse_expressions()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
create_mock_address_transform() -> AddressTransform1230 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
create_mock_value_ranges() -> (ValueLabelsRanges, (ValueLabel, ValueLabel, ValueLabel))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]
test_debug_value_range_builder()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