diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3885cd9f091c..936fd61788d2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -292,6 +292,14 @@ jobs: env: RUST_BACKTRACE: 1 + # Test debug (DWARF) related functionality on new backend. + - run: | + sudo apt-get update && sudo apt-get install -y gdb + cargo test --features experimental_x64 test_debug_dwarf -- --ignored --test-threads 1 --test debug:: + if: matrix.os == 'ubuntu-latest' + env: + RUST_BACKTRACE: 1 + # Build and test lightbeam. Note that # Lightbeam tests fail right now, but we don't want to block on that. - run: cargo build --package lightbeam diff --git a/cranelift/codegen/Cargo.toml b/cranelift/codegen/Cargo.toml index 15b592772a95..e7a618ba46b5 100644 --- a/cranelift/codegen/Cargo.toml +++ b/cranelift/codegen/Cargo.toml @@ -74,7 +74,7 @@ all-arch = [ ] # For dependent crates that want to serialize some parts of cranelift -enable-serde = ["serde"] +enable-serde = ["serde", "regalloc/enable-serde"] # Allow snapshotting regalloc test cases. Useful only to report bad register # allocation failures, or for regalloc.rs developers. diff --git a/cranelift/codegen/src/context.rs b/cranelift/codegen/src/context.rs index ef092fb8183a..b831f9966a31 100644 --- a/cranelift/codegen/src/context.rs +++ b/cranelift/codegen/src/context.rs @@ -473,6 +473,7 @@ impl Context { Ok(build_value_labels_ranges::( &self.func, &self.regalloc, + self.mach_compile_result.as_ref(), isa, )) } diff --git a/cranelift/codegen/src/ir/mod.rs b/cranelift/codegen/src/ir/mod.rs index c5e827db3d9f..c78dde81deef 100644 --- a/cranelift/codegen/src/ir/mod.rs +++ b/cranelift/codegen/src/ir/mod.rs @@ -58,6 +58,7 @@ pub use crate::ir::table::TableData; pub use crate::ir::trapcode::TrapCode; pub use crate::ir::types::Type; pub use crate::ir::valueloc::{ArgumentLoc, ValueLoc}; +pub use crate::value_label::LabelValueLoc; pub use cranelift_codegen_shared::condcodes; use crate::binemit; diff --git a/cranelift/codegen/src/isa/aarch64/inst/emit.rs b/cranelift/codegen/src/isa/aarch64/inst/emit.rs index 1195e879bbfc..599a8edacd6d 100644 --- a/cranelift/codegen/src/isa/aarch64/inst/emit.rs +++ b/cranelift/codegen/src/isa/aarch64/inst/emit.rs @@ -2371,6 +2371,9 @@ impl MachInstEmit for Inst { sink.bind_label(jump_around_label); } } + &Inst::ValueLabelMarker { .. } => { + // Nothing; this is only used to compute debug info. + } } let end_off = sink.cur_offset(); diff --git a/cranelift/codegen/src/isa/aarch64/inst/mod.rs b/cranelift/codegen/src/isa/aarch64/inst/mod.rs index 5e88a783f5b1..b85928edcc0c 100644 --- a/cranelift/codegen/src/isa/aarch64/inst/mod.rs +++ b/cranelift/codegen/src/isa/aarch64/inst/mod.rs @@ -7,7 +7,7 @@ use crate::binemit::CodeOffset; use crate::ir::types::{ B1, B128, B16, B32, B64, B8, F32, F64, FFLAGS, I128, I16, I32, I64, I8, I8X16, IFLAGS, R32, R64, }; -use crate::ir::{ExternalName, MemFlags, Opcode, SourceLoc, TrapCode, Type}; +use crate::ir::{ExternalName, MemFlags, Opcode, SourceLoc, TrapCode, Type, ValueLabel}; use crate::isa::CallConv; use crate::machinst::*; use crate::{settings, CodegenError, CodegenResult}; @@ -1210,6 +1210,12 @@ pub enum Inst { /// The needed space before the next deadline. needed_space: CodeOffset, }, + + /// A definition of a value label. + ValueLabelMarker { + reg: Reg, + label: ValueLabel, + }, } fn count_zero_half_words(mut value: u64, num_half_words: u8) -> usize { @@ -2017,6 +2023,9 @@ fn aarch64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) { memarg_regs(mem, collector); } &Inst::VirtualSPOffsetAdj { .. } => {} + &Inst::ValueLabelMarker { reg, .. } => { + collector.add_use(reg); + } &Inst::EmitIsland { .. } => {} } } @@ -2767,6 +2776,9 @@ fn aarch64_map_regs(inst: &mut Inst, mapper: &RUM) { } &mut Inst::VirtualSPOffsetAdj { .. } => {} &mut Inst::EmitIsland { .. } => {} + &mut Inst::ValueLabelMarker { ref mut reg, .. } => { + map_use(mapper, reg); + } } } @@ -2962,6 +2974,17 @@ impl MachInst for Inst { fn ref_type_regclass(_: &settings::Flags) -> RegClass { RegClass::I64 } + + fn gen_value_label_marker(label: ValueLabel, reg: Reg) -> Self { + Inst::ValueLabelMarker { label, reg } + } + + fn defines_value_label(&self) -> Option<(ValueLabel, Reg)> { + match self { + Inst::ValueLabelMarker { label, reg } => Some((*label, *reg)), + _ => None, + } + } } //============================================================================= @@ -4071,6 +4094,10 @@ impl Inst { format!("virtual_sp_offset_adjust {}", offset) } &Inst::EmitIsland { needed_space } => format!("emit_island {}", needed_space), + + &Inst::ValueLabelMarker { label, reg } => { + format!("value_label {:?}, {}", label, reg.show_rru(mb_rru)) + } } } } diff --git a/cranelift/codegen/src/isa/aarch64/mod.rs b/cranelift/codegen/src/isa/aarch64/mod.rs index c3c56632d361..11eb0a6ea6a2 100644 --- a/cranelift/codegen/src/isa/aarch64/mod.rs +++ b/cranelift/codegen/src/isa/aarch64/mod.rs @@ -79,6 +79,7 @@ impl MachBackend for AArch64Backend { frame_size, disasm, unwind_info, + value_labels_ranges: None, }) } diff --git a/cranelift/codegen/src/isa/arm32/mod.rs b/cranelift/codegen/src/isa/arm32/mod.rs index 4b9701fd1d44..6ab0f9c57cfa 100644 --- a/cranelift/codegen/src/isa/arm32/mod.rs +++ b/cranelift/codegen/src/isa/arm32/mod.rs @@ -74,6 +74,7 @@ impl MachBackend for Arm32Backend { frame_size, disasm, unwind_info: None, + value_labels_ranges: None, }) } diff --git a/cranelift/codegen/src/isa/mod.rs b/cranelift/codegen/src/isa/mod.rs index 900dfd9ddd42..73a83dda3477 100644 --- a/cranelift/codegen/src/isa/mod.rs +++ b/cranelift/codegen/src/isa/mod.rs @@ -325,6 +325,12 @@ pub trait TargetIsa: fmt::Display + Send + Sync { Err(RegisterMappingError::UnsupportedArchitecture) } + #[cfg(feature = "unwind")] + /// Map a regalloc::Reg to its corresponding DWARF register. + fn map_regalloc_reg_to_dwarf(&self, _: ::regalloc::Reg) -> Result { + Err(RegisterMappingError::UnsupportedArchitecture) + } + /// Returns an iterator over legal encodings for the instruction. fn legal_encodings<'a>( &'a self, diff --git a/cranelift/codegen/src/isa/x64/inst/emit.rs b/cranelift/codegen/src/isa/x64/inst/emit.rs index 66229034524a..095256ab4977 100644 --- a/cranelift/codegen/src/isa/x64/inst/emit.rs +++ b/cranelift/codegen/src/isa/x64/inst/emit.rs @@ -3029,6 +3029,10 @@ pub(crate) fn emit( sink.put1(0xff); sink.put1(0x17); } + + Inst::ValueLabelMarker { .. } => { + // Nothing; this is only used to compute debug info. + } } state.clear_post_insn(); diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs index d1302cacbc74..bab28f2aa098 100644 --- a/cranelift/codegen/src/isa/x64/inst/mod.rs +++ b/cranelift/codegen/src/isa/x64/inst/mod.rs @@ -1,7 +1,7 @@ //! This module defines x86_64-specific machine instruction types. use crate::binemit::{CodeOffset, StackMap}; -use crate::ir::{types, ExternalName, Opcode, SourceLoc, TrapCode, Type}; +use crate::ir::{types, ExternalName, Opcode, SourceLoc, TrapCode, Type, ValueLabel}; use crate::isa::x64::abi::X64ABIMachineSpec; use crate::isa::x64::settings as x64_settings; use crate::isa::CallConv; @@ -484,6 +484,9 @@ pub enum Inst { /// A Mach-O TLS symbol access. Returns address of the TLS /// symbol in rax. MachOTlsGetAddr { symbol: ExternalName }, + + /// A definition of a value label. + ValueLabelMarker { reg: Reg, label: ValueLabel }, } pub(crate) fn low32_will_sign_extend_to_64(x: u64) -> bool { @@ -544,7 +547,8 @@ impl Inst { | Inst::XmmMinMaxSeq { .. } | Inst::XmmUninitializedValue { .. } | Inst::ElfTlsGetAddr { .. } - | Inst::MachOTlsGetAddr { .. } => None, + | Inst::MachOTlsGetAddr { .. } + | Inst::ValueLabelMarker { .. } => None, // These use dynamic SSE opcodes. Inst::GprToXmm { op, .. } @@ -1800,6 +1804,10 @@ impl PrettyPrint for Inst { Inst::MachOTlsGetAddr { ref symbol } => { format!("macho_tls_get_addr {:?}", symbol) } + + Inst::ValueLabelMarker { label, reg } => { + format!("value_label {:?}, {}", label, reg.show_rru(mb_rru)) + } } } } @@ -2071,6 +2079,10 @@ fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) { collector.add_def(reg); } } + + Inst::ValueLabelMarker { reg, .. } => { + collector.add_use(*reg); + } } } @@ -2446,6 +2458,8 @@ fn x64_map_regs(inst: &mut Inst, mapper: &RUM) { dst.map_uses(mapper); } + Inst::ValueLabelMarker { ref mut reg, .. } => map_use(mapper, reg), + Inst::Ret | Inst::EpiloguePlaceholder | Inst::JmpKnown { .. } @@ -2536,6 +2550,25 @@ impl MachInst for Inst { } } + fn stack_op_info(&self) -> Option { + match self { + Self::VirtualSPOffsetAdj { offset } => Some(MachInstStackOpInfo::NomSPAdj(*offset)), + Self::MovRM { + size: 8, + src, + dst: SyntheticAmode::NominalSPOffset { simm32 }, + } => Some(MachInstStackOpInfo::StoreNomSPOff(*src, *simm32 as i64)), + Self::Mov64MR { + src: SyntheticAmode::NominalSPOffset { simm32 }, + dst, + } => Some(MachInstStackOpInfo::LoadNomSPOff( + dst.to_reg(), + *simm32 as i64, + )), + _ => None, + } + } + fn gen_move(dst_reg: Writable, src_reg: Reg, ty: Type) -> Inst { let rc_dst = dst_reg.to_reg().get_class(); let rc_src = src_reg.get_class(); @@ -2710,6 +2743,17 @@ impl MachInst for Inst { RegClass::I64 } + fn gen_value_label_marker(label: ValueLabel, reg: Reg) -> Self { + Inst::ValueLabelMarker { label, reg } + } + + fn defines_value_label(&self) -> Option<(ValueLabel, Reg)> { + match self { + Inst::ValueLabelMarker { label, reg } => Some((*label, *reg)), + _ => None, + } + } + type LabelUse = LabelUse; } diff --git a/cranelift/codegen/src/isa/x64/mod.rs b/cranelift/codegen/src/isa/x64/mod.rs index 73183f79e87d..ca809e2d55c0 100644 --- a/cranelift/codegen/src/isa/x64/mod.rs +++ b/cranelift/codegen/src/isa/x64/mod.rs @@ -4,13 +4,14 @@ use self::inst::EmitInfo; use super::TargetIsa; use crate::ir::{condcodes::IntCC, Function}; +use crate::isa::unwind::systemv::RegisterMappingError; use crate::isa::x64::{inst::regs::create_reg_universe_systemv, settings as x64_settings}; use crate::isa::Builder as IsaBuilder; use crate::machinst::{compile, MachBackend, MachCompileResult, TargetIsaAdapter, VCode}; use crate::result::CodegenResult; use crate::settings::{self as shared_settings, Flags}; use alloc::boxed::Box; -use regalloc::{PrettyPrint, RealRegUniverse}; +use regalloc::{PrettyPrint, RealRegUniverse, Reg}; use target_lexicon::Triple; mod abi; @@ -60,6 +61,7 @@ impl MachBackend for X64Backend { let buffer = buffer.finish(); let frame_size = vcode.frame_size(); let unwind_info = vcode.unwind_info()?; + let value_labels_ranges = vcode.value_labels_ranges()?; let disasm = if want_disasm { Some(vcode.show_rru(Some(&create_reg_universe_systemv(flags)))) @@ -72,6 +74,7 @@ impl MachBackend for X64Backend { frame_size, disasm, unwind_info, + value_labels_ranges, }) } @@ -127,6 +130,11 @@ impl MachBackend for X64Backend { fn create_systemv_cie(&self) -> Option { Some(inst::unwind::systemv::create_cie()) } + + #[cfg(feature = "unwind")] + fn map_reg_to_dwarf(&self, reg: Reg) -> Result { + inst::unwind::systemv::map_reg(reg).map(|reg| reg.0) + } } /// Create a new `isa::Builder`. diff --git a/cranelift/codegen/src/machinst/adapter.rs b/cranelift/codegen/src/machinst/adapter.rs index 43ff9e51a470..4b3be0f3c018 100644 --- a/cranelift/codegen/src/machinst/adapter.rs +++ b/cranelift/codegen/src/machinst/adapter.rs @@ -10,6 +10,9 @@ use crate::settings::Flags; #[cfg(feature = "testing_hooks")] use crate::regalloc::RegDiversions; +#[cfg(feature = "unwind")] +use crate::isa::unwind::systemv::RegisterMappingError; + use core::any::Any; use std::borrow::Cow; use std::fmt; @@ -134,6 +137,11 @@ impl TargetIsa for TargetIsaAdapter { self.backend.create_systemv_cie() } + #[cfg(feature = "unwind")] + fn map_regalloc_reg_to_dwarf(&self, r: Reg) -> Result { + self.backend.map_reg_to_dwarf(r) + } + fn as_any(&self) -> &dyn Any { self as &dyn Any } diff --git a/cranelift/codegen/src/machinst/debug.rs b/cranelift/codegen/src/machinst/debug.rs new file mode 100644 index 000000000000..4768b8d06e10 --- /dev/null +++ b/cranelift/codegen/src/machinst/debug.rs @@ -0,0 +1,500 @@ +//! Debug info analysis: computes value-label ranges from value-label markers in +//! generated VCode. +//! +//! We "reverse-engineer" debug info like this because it is far more reliable +//! than generating it while emitting code and keeping it in sync. +//! +//! This works by (i) observing "value-label marker" instructions, which are +//! semantically just an assignment from a register to a "value label" (which +//! one can think of as another register; they represent, e.g., Wasm locals) at +//! a certain point in the code, and (ii) observing loads and stores to the +//! stack and register moves. +//! +//! We track, at every program point, the correspondence between each value +//! label and *all* locations in which it resides. E.g., if it is stored to the +//! stack, we remember that it is in both a register and the stack slot; but if +//! the register is later overwritten, then we have it just in the stack slot. +//! This allows us to avoid false-positives observing loads/stores that we think +//! are spillslots but really aren't. +//! +//! We do a standard forward dataflow analysis to compute this info. + +use crate::ir::ValueLabel; +use crate::machinst::*; +use crate::value_label::{LabelValueLoc, ValueLabelsRanges, ValueLocRange}; +use log::trace; +use regalloc::{Reg, RegUsageCollector}; +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; + +/// Location of a labeled value: in a register or in a stack slot. Note that a +/// value may live in more than one location; `AnalysisInfo` maps each +/// value-label to multiple `ValueLoc`s. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +enum ValueLoc { + Reg(Reg), + /// Nominal-SP offset. + Stack(i64), +} + +impl From for LabelValueLoc { + fn from(v: ValueLoc) -> Self { + match v { + ValueLoc::Reg(r) => LabelValueLoc::Reg(r), + ValueLoc::Stack(off) => LabelValueLoc::SPOffset(off), + } + } +} + +impl ValueLoc { + fn is_reg(self) -> bool { + match self { + ValueLoc::Reg(_) => true, + _ => false, + } + } + fn is_stack(self) -> bool { + match self { + ValueLoc::Stack(_) => true, + _ => false, + } + } +} + +/// Mappings at one program point. +#[derive(Clone, Debug)] +struct AnalysisInfo { + /// Nominal SP relative to real SP. If `None`, then the offset is + /// indeterminate (i.e., we merged to the lattice 'bottom' element). This + /// should not happen in well-formed code. + nominal_sp_offset: Option, + /// Forward map from labeled values to sets of locations. + label_to_locs: HashMap>, + /// Reverse map for each register indicating the value it holds, if any. + reg_to_label: HashMap, + /// Reverse map for each stack offset indicating the value it holds, if any. + stack_to_label: HashMap, +} + +/// Get the registers written (mod'd or def'd) by a machine instruction. +fn get_inst_writes(m: &M) -> Vec { + // TODO: expose this part of regalloc.rs's interface publicly. + let mut vecs = RegUsageCollector::get_empty_reg_vecs_test_framework_only(false); + let mut coll = RegUsageCollector::new(&mut vecs); + m.get_regs(&mut coll); + vecs.defs.extend(vecs.mods.into_iter()); + vecs.defs +} + +impl AnalysisInfo { + /// Create a new analysis state. This is the "top" lattice element at which + /// the fixpoint dataflow analysis starts. + fn new() -> Self { + AnalysisInfo { + nominal_sp_offset: Some(0), + label_to_locs: HashMap::new(), + reg_to_label: HashMap::new(), + stack_to_label: HashMap::new(), + } + } + + /// Remove all locations for a given labeled value. Used when the labeled + /// value is redefined (so old values become stale). + fn clear_label(&mut self, label: ValueLabel) { + if let Some(locs) = self.label_to_locs.remove(&label) { + for loc in locs { + match loc { + ValueLoc::Reg(r) => { + self.reg_to_label.remove(&r); + } + ValueLoc::Stack(off) => { + self.stack_to_label.remove(&off); + } + } + } + } + } + + /// Remove a label from a register, if any. Used, e.g., if the register is + /// overwritten. + fn clear_reg(&mut self, reg: Reg) { + if let Some(label) = self.reg_to_label.remove(®) { + if let Some(locs) = self.label_to_locs.get_mut(&label) { + locs.remove(&ValueLoc::Reg(reg)); + } + } + } + + /// Remove a label from a stack offset, if any. Used, e.g., when the stack + /// slot is overwritten. + fn clear_stack_off(&mut self, off: i64) { + if let Some(label) = self.stack_to_label.remove(&off) { + if let Some(locs) = self.label_to_locs.get_mut(&label) { + locs.remove(&ValueLoc::Stack(off)); + } + } + } + + /// Indicate that a labeled value is newly defined and its new value is in + /// `reg`. + fn def_label_at_reg(&mut self, label: ValueLabel, reg: Reg) { + self.clear_label(label); + self.label_to_locs + .entry(label) + .or_insert_with(|| HashSet::new()) + .insert(ValueLoc::Reg(reg)); + self.reg_to_label.insert(reg, label); + } + + /// Process a store from a register to a stack slot (offset). + fn store_reg(&mut self, reg: Reg, off: i64) { + self.clear_stack_off(off); + if let Some(label) = self.reg_to_label.get(®) { + if let Some(locs) = self.label_to_locs.get_mut(label) { + locs.insert(ValueLoc::Stack(off)); + } + self.stack_to_label.insert(off, *label); + } + } + + /// Process a load from a stack slot (offset) to a register. + fn load_reg(&mut self, reg: Reg, off: i64) { + self.clear_reg(reg); + if let Some(&label) = self.stack_to_label.get(&off) { + if let Some(locs) = self.label_to_locs.get_mut(&label) { + locs.insert(ValueLoc::Reg(reg)); + } + self.reg_to_label.insert(reg, label); + } + } + + /// Process a move from one register to another. + fn move_reg(&mut self, to: Reg, from: Reg) { + self.clear_reg(to); + if let Some(&label) = self.reg_to_label.get(&from) { + if let Some(locs) = self.label_to_locs.get_mut(&label) { + locs.insert(ValueLoc::Reg(to)); + } + self.reg_to_label.insert(to, label); + } + } + + /// Update the analysis state w.r.t. an instruction's effects. Given the + /// state just before `inst`, this method updates `self` to be the state + /// just after `inst`. + fn step(&mut self, inst: &M) { + for write in get_inst_writes(inst) { + self.clear_reg(write); + } + if let Some((label, reg)) = inst.defines_value_label() { + self.def_label_at_reg(label, reg); + } + match inst.stack_op_info() { + Some(MachInstStackOpInfo::LoadNomSPOff(reg, offset)) => { + self.load_reg(reg, offset + self.nominal_sp_offset.unwrap()); + } + Some(MachInstStackOpInfo::StoreNomSPOff(reg, offset)) => { + self.store_reg(reg, offset + self.nominal_sp_offset.unwrap()); + } + Some(MachInstStackOpInfo::NomSPAdj(offset)) => { + if self.nominal_sp_offset.is_some() { + self.nominal_sp_offset = Some(self.nominal_sp_offset.unwrap() + offset); + } + } + _ => {} + } + if let Some((to, from)) = inst.is_move() { + let to = to.to_reg(); + self.move_reg(to, from); + } + } +} + +/// Trait used to implement the dataflow analysis' meet (intersect) function +/// onthe `AnalysisInfo` components. For efficiency, this is implemented as a +/// mutation on the LHS, rather than a pure functional operation. +trait IntersectFrom { + fn intersect_from(&mut self, other: &Self) -> IntersectResult; +} + +/// Result of an intersection operation. Indicates whether the mutated LHS +/// (which becomes the intersection result) differs from the original LHS. Also +/// indicates if the value has become "empty" and should be removed from a +/// parent container, if any. +struct IntersectResult { + /// Did the intersection change the LHS input (the one that was mutated into + /// the result)? This is needed to drive the fixpoint loop; when no more + /// changes occur, then we have converted. + changed: bool, + /// Is the resulting value "empty"? This can be used when a container, such + /// as a map, holds values of this (intersection result) type; when + /// `is_empty` is true for the merge of the values at a particular key, we + /// can remove that key from the merged (intersected) result. This is not + /// necessary for analysis correctness but reduces the memory and runtime + /// cost of the fixpoint loop. + is_empty: bool, +} + +impl IntersectFrom for AnalysisInfo { + fn intersect_from(&mut self, other: &Self) -> IntersectResult { + let mut changed = false; + changed |= self + .nominal_sp_offset + .intersect_from(&other.nominal_sp_offset) + .changed; + changed |= self + .label_to_locs + .intersect_from(&other.label_to_locs) + .changed; + changed |= self + .reg_to_label + .intersect_from(&other.reg_to_label) + .changed; + changed |= self + .stack_to_label + .intersect_from(&other.stack_to_label) + .changed; + IntersectResult { + changed, + is_empty: false, + } + } +} + +impl IntersectFrom for HashMap +where + K: Copy + Eq + Hash, + V: IntersectFrom, +{ + /// Intersection for hashmap: remove keys that are not in both inputs; + /// recursively intersect values for keys in common. + fn intersect_from(&mut self, other: &Self) -> IntersectResult { + let mut changed = false; + let mut remove_keys = vec![]; + for k in self.keys() { + if !other.contains_key(k) { + remove_keys.push(*k); + } + } + for k in &remove_keys { + changed = true; + self.remove(k); + } + + remove_keys.clear(); + for k in other.keys() { + if let Some(v) = self.get_mut(k) { + let result = v.intersect_from(other.get(k).unwrap()); + changed |= result.changed; + if result.is_empty { + remove_keys.push(*k); + } + } + } + for k in &remove_keys { + changed = true; + self.remove(k); + } + + IntersectResult { + changed, + is_empty: self.len() == 0, + } + } +} +impl IntersectFrom for HashSet +where + T: Copy + Eq + Hash, +{ + /// Intersection for hashset: just take the set intersection. + fn intersect_from(&mut self, other: &Self) -> IntersectResult { + let mut changed = false; + let mut remove = vec![]; + for val in self.iter() { + if !other.contains(val) { + remove.push(*val); + } + } + for val in remove { + changed = true; + self.remove(&val); + } + + IntersectResult { + changed, + is_empty: self.len() == 0, + } + } +} +impl IntersectFrom for ValueLabel { + // Intersection for labeled value: remove if not equal. This is equivalent + // to a three-level lattice with top, bottom, and unordered set of + // individual labels in between. + fn intersect_from(&mut self, other: &Self) -> IntersectResult { + IntersectResult { + changed: false, + is_empty: *self != *other, + } + } +} +impl IntersectFrom for Option +where + T: Copy + Eq, +{ + /// Intersectino for Option: recursively intersect if both `Some`, else + /// `None`. + fn intersect_from(&mut self, other: &Self) -> IntersectResult { + let mut changed = false; + if !(self.is_some() && other.is_some() && self == other) { + changed = true; + *self = None; + } + IntersectResult { + changed, + is_empty: self.is_none(), + } + } +} + +/// Compute the value-label ranges (locations for program-point ranges for +/// labeled values) from a given `VCode` compilation result. +/// +/// In order to compute this information, we perform a dataflow analysis on the +/// machine code. To do so, and translate the results into a form usable by the +/// debug-info consumers, we need to know two additional things: +/// +/// - The machine-code layout (code offsets) of the instructions. DWARF is +/// encoded in terms of instruction *ends* (and we reason about value +/// locations at program points *after* instructions, to match this), so we +/// take an array `inst_ends`, giving us code offsets for each instruction's +/// end-point. (Note that this is one *past* the last byte; so a 4-byte +/// instruction at offset 0 has an end offset of 4.) +/// +/// - The locations of the labels to which branches will jump. Branches can tell +/// us about their targets in terms of `MachLabel`s, but we don't know where +/// those `MachLabel`s will be placed in the linear array of instructions. We +/// take the array `label_insn_index` to provide this info: for a label with +/// index `l`, `label_insn_index[l]` is the index of the instruction before +/// which that label is bound. +pub(crate) fn compute( + insts: &[I], + inst_ends: &[u32], + label_insn_index: &[u32], +) -> ValueLabelsRanges { + let inst_start = |idx: usize| if idx == 0 { 0 } else { inst_ends[idx - 1] }; + + trace!("compute: insts ="); + for i in 0..insts.len() { + trace!(" #{} end: {} -> {:?}", i, inst_ends[i], insts[i]); + } + trace!("label_insn_index: {:?}", label_insn_index); + + // Info at each block head, indexed by label. + let mut block_starts: HashMap = HashMap::new(); + + // Initialize state at entry. + block_starts.insert(0, AnalysisInfo::new()); + + // Worklist: label indices for basic blocks. + let mut worklist = Vec::new(); + let mut worklist_set = HashSet::new(); + worklist.push(0); + worklist_set.insert(0); + + while !worklist.is_empty() { + let block = worklist.pop().unwrap(); + worklist_set.remove(&block); + + let mut state = block_starts.get(&block).unwrap().clone(); + trace!("at block {} -> state: {:?}", block, state); + // Iterate for each instruction in the block (we break at the first + // terminator we see). + let mut index = label_insn_index[block as usize]; + while index < insts.len() as u32 { + state.step(&insts[index as usize]); + trace!(" -> inst #{}: {:?}", index, insts[index as usize]); + trace!(" --> state: {:?}", state); + + let term = insts[index as usize].is_term(); + if term.is_term() { + for succ in term.get_succs() { + trace!(" SUCCESSOR block {}", succ.get()); + if let Some(succ_state) = block_starts.get_mut(&succ.get()) { + trace!(" orig state: {:?}", succ_state); + if succ_state.intersect_from(&state).changed { + if worklist_set.insert(succ.get()) { + worklist.push(succ.get()); + } + trace!(" (changed)"); + } + trace!(" new state: {:?}", succ_state); + } else { + // First time seeing this block + block_starts.insert(succ.get(), state.clone()); + worklist.push(succ.get()); + worklist_set.insert(succ.get()); + } + } + break; + } + + index += 1; + } + } + + // Now iterate over blocks one last time, collecting + // value-label locations. + + let mut value_labels_ranges: ValueLabelsRanges = HashMap::new(); + for block in 0..label_insn_index.len() { + let start_index = label_insn_index[block]; + let end_index = if block == label_insn_index.len() - 1 { + insts.len() as u32 + } else { + label_insn_index[block + 1] + }; + let block = block as u32; + let mut state = block_starts.get(&block).unwrap().clone(); + for index in start_index..end_index { + let offset = inst_start(index as usize); + let end = inst_ends[index as usize]; + state.step(&insts[index as usize]); + + for (label, locs) in &state.label_to_locs { + trace!(" inst {} has label {:?} -> locs {:?}", index, label, locs); + // Find an appropriate loc: a register if possible, otherwise pick the first stack + // loc. + let reg = locs.iter().cloned().find(|l| l.is_reg()); + let loc = reg.or_else(|| locs.iter().cloned().find(|l| l.is_stack())); + if let Some(loc) = loc { + let loc = LabelValueLoc::from(loc); + let list = value_labels_ranges.entry(*label).or_insert_with(|| vec![]); + // If the existing location list for this value-label is + // either empty, or has an end location that does not extend + // to the current offset, then we have to append a new + // entry. Otherwise, we can extend the current entry. + // + // Note that `end` is one past the end of the instruction; + // it appears that `end` is exclusive, so a mapping valid at + // offset 5 will have start = 5, end = 6. + if list + .last() + .map(|last| last.end <= offset || last.loc != loc) + .unwrap_or(true) + { + list.push(ValueLocRange { + loc, + start: end, + end: end + 1, + }); + } else { + list.last_mut().unwrap().end = end + 1; + } + } + } + } + } + + trace!("ret: {:?}", value_labels_ranges); + value_labels_ranges +} diff --git a/cranelift/codegen/src/machinst/lower.rs b/cranelift/codegen/src/machinst/lower.rs index e35e3b068e8a..89e184fd4ed6 100644 --- a/cranelift/codegen/src/machinst/lower.rs +++ b/cranelift/codegen/src/machinst/lower.rs @@ -13,6 +13,7 @@ use crate::ir::instructions::BranchInfo; use crate::ir::{ ArgumentPurpose, Block, Constant, ConstantData, ExternalName, Function, GlobalValueData, Inst, InstructionData, MemFlags, Opcode, Signature, SourceLoc, Type, Value, ValueDef, + ValueLabelAssignments, ValueLabelStart, }; use crate::machinst::{ writable_value_regs, ABICallee, BlockIndex, BlockLoweringOrder, LoweredBlock, MachLabel, VCode, @@ -24,7 +25,7 @@ use alloc::vec::Vec; use core::convert::TryInto; use log::debug; use regalloc::{Reg, StackmapRequestInfo, Writable}; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; use std::fmt::Debug; /// An "instruction color" partitions CLIF instructions by side-effecting ops. @@ -492,6 +493,14 @@ impl<'func, I: VCodeInst> Lower<'func, I> { } fn gen_retval_setup(&mut self, gen_ret_inst: GenerateReturn) { + // Hack: to keep `vmctx` alive, if it exists, we emit a value label here + // for it if debug info is requested. This ensures that it exists either + // in a register or spillslot throughout the entire function body, and + // allows for a better debugging experience. + if let Some(vmctx_val) = self.f.special_param(ArgumentPurpose::VMContext) { + self.emit_value_label_marks_for_value(vmctx_val); + } + let retval_regs = self.retval_regs.clone(); for (i, regs) in retval_regs.into_iter().enumerate() { let regs = writable_value_regs(regs); @@ -725,6 +734,9 @@ impl<'func, I: VCodeInst> Lower<'func, I> { if has_side_effect || value_needed { debug!("lowering: inst {}: {:?}", inst, self.f.dfg[inst]); backend.lower(self, inst)?; + // Emit value-label markers if needed, to later recover debug + // mappings. + self.emit_value_label_markers_for_inst(inst); } if data.opcode().is_return() { // Return: handle specially, using ABI-appropriate sequence. @@ -744,6 +756,80 @@ impl<'func, I: VCodeInst> Lower<'func, I> { Ok(()) } + fn get_value_labels<'a>(&'a self, val: Value, depth: usize) -> Option<&'a [ValueLabelStart]> { + if let Some(ref values_labels) = self.f.dfg.values_labels { + debug!( + "get_value_labels: val {} -> {} -> {:?}", + val, + self.f.dfg.resolve_aliases(val), + values_labels.get(&self.f.dfg.resolve_aliases(val)) + ); + let val = self.f.dfg.resolve_aliases(val); + match values_labels.get(&val) { + Some(&ValueLabelAssignments::Starts(ref list)) => Some(&list[..]), + Some(&ValueLabelAssignments::Alias { value, .. }) if depth < 10 => { + self.get_value_labels(value, depth + 1) + } + _ => None, + } + } else { + None + } + } + + fn emit_value_label_marks_for_value(&mut self, val: Value) { + let mut markers: SmallVec<[I; 4]> = smallvec![]; + let regs = self.value_regs[val]; + if regs.len() > 1 { + return; + } + let reg = regs.only_reg().unwrap(); + + if let Some(label_starts) = self.get_value_labels(val, 0) { + let labels = label_starts + .iter() + .map(|&ValueLabelStart { label, .. }| label) + .collect::>(); + for label in labels { + debug!( + "value labeling: defines val {:?} -> reg {:?} -> label {:?}", + val, reg, label, + ); + markers.push(I::gen_value_label_marker(label, reg)); + } + } + for marker in markers { + self.emit(marker); + } + } + + fn emit_value_label_markers_for_inst(&mut self, inst: Inst) { + if self.f.dfg.values_labels.is_none() { + return; + } + + debug!( + "value labeling: srcloc {}: inst {}", + self.srcloc(inst), + inst + ); + for &val in self.f.dfg.inst_results(inst) { + self.emit_value_label_marks_for_value(val); + } + } + + fn emit_value_label_markers_for_block_args(&mut self, block: Block) { + if self.f.dfg.values_labels.is_none() { + return; + } + + debug!("value labeling: block {}", block); + for &arg in self.f.dfg.block_params(block) { + self.emit_value_label_marks_for_value(arg); + } + self.finish_ir_inst(SourceLoc::default()); + } + fn finish_ir_inst(&mut self, loc: SourceLoc) { // `bb_insts` is kept in reverse order, so emit the instructions in // reverse order. @@ -885,6 +971,7 @@ impl<'func, I: VCodeInst> Lower<'func, I> { // Original block body. if let Some(bb) = lb.orig_block() { self.lower_clif_block(backend, bb)?; + self.emit_value_label_markers_for_block_args(bb); } // In-edge phi moves. if let Some((pred, inst, succ)) = lb.in_edge() { diff --git a/cranelift/codegen/src/machinst/mod.rs b/cranelift/codegen/src/machinst/mod.rs index 764531d54f15..7ed2661dda50 100644 --- a/cranelift/codegen/src/machinst/mod.rs +++ b/cranelift/codegen/src/machinst/mod.rs @@ -52,45 +52,9 @@ //! | - all symbolic stack references to //! | stackslots and spillslots are resolved //! | to concrete FP-offset mem addresses.) -//! | [block/insn ordering] //! | -//! VCode (machine instructions: -//! | - vcode.final_block_order is filled in. -//! | - new insn sequence from regalloc is -//! | placed back into vcode and block -//! | boundaries are updated.) -//! | [redundant branch/block -//! | removal] -//! | -//! VCode (machine instructions: -//! | - all blocks that were just an -//! | unconditional branch are removed.) -//! | -//! | [branch finalization -//! | (fallthroughs)] -//! | -//! VCode (machine instructions: -//! | - all branches are in lowered one- -//! | target form, but targets are still -//! | block indices.) -//! | -//! | [branch finalization -//! | (offsets)] -//! | -//! VCode (machine instructions: -//! | - all branch offsets from start of -//! | function are known, and all branches -//! | have resolved-offset targets.) -//! | -//! | [MemArg finalization] -//! | -//! VCode (machine instructions: -//! | - all MemArg references to the constant -//! | pool are replaced with offsets. -//! | - all constant-pool data is collected -//! | in the VCode.) -//! | -//! | [binary emission] +//! | [binary emission via MachBuffer +//! | with streaming branch resolution/simplification] //! | //! Vec (machine code!) //! @@ -98,11 +62,11 @@ use crate::binemit::{CodeInfo, CodeOffset, StackMap}; use crate::ir::condcodes::IntCC; -use crate::ir::{Function, SourceLoc, Type}; +use crate::ir::{Function, SourceLoc, Type, ValueLabel}; use crate::isa::unwind::input as unwind_input; use crate::result::CodegenResult; use crate::settings::Flags; - +use crate::value_label::ValueLabelsRanges; use alloc::boxed::Box; use alloc::vec::Vec; use core::fmt::Debug; @@ -111,10 +75,13 @@ use regalloc::RegUsageCollector; use regalloc::{ RealReg, RealRegUniverse, Reg, RegClass, RegUsageMapper, SpillSlot, VirtualReg, Writable, }; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; use std::string::String; use target_lexicon::Triple; +#[cfg(feature = "unwind")] +use crate::isa::unwind::systemv::RegisterMappingError; + pub mod lower; pub use lower::*; pub mod vcode; @@ -137,6 +104,7 @@ pub mod inst_common; pub use inst_common::*; pub mod valueregs; pub use valueregs::*; +pub mod debug; /// A machine instruction. pub trait MachInst: Clone + Debug { @@ -163,6 +131,11 @@ pub trait MachInst: Clone + Debug { true } + /// If this is a load or store to the stack, return that info. + fn stack_op_info(&self) -> Option { + None + } + /// Generate a move. fn gen_move(to_reg: Writable, from_reg: Reg, ty: Type) -> Self; @@ -223,6 +196,17 @@ pub trait MachInst: Clone + Debug { /// be dependent on compilation flags. fn ref_type_regclass(_flags: &Flags) -> RegClass; + /// Does this instruction define a ValueLabel? Returns the `Reg` whose value + /// becomes the new value of the `ValueLabel` after this instruction. + fn defines_value_label(&self) -> Option<(ValueLabel, Reg)> { + None + } + + /// Create a marker instruction that defines a value label. + fn gen_value_label_marker(_label: ValueLabel, _reg: Reg) -> Self { + Self::gen_nop(0) + } + /// A label-use kind: a type that describes the types of label references that /// can occur in an instruction. type LabelUse: MachInstLabelUse; @@ -285,6 +269,35 @@ pub enum MachTerminator<'a> { Indirect(&'a [MachLabel]), } +impl<'a> MachTerminator<'a> { + /// Get the successor labels named in a `MachTerminator`. + pub fn get_succs(&self) -> SmallVec<[MachLabel; 2]> { + let mut ret = smallvec![]; + match self { + &MachTerminator::Uncond(l) => { + ret.push(l); + } + &MachTerminator::Cond(l1, l2) => { + ret.push(l1); + ret.push(l2); + } + &MachTerminator::Indirect(ls) => { + ret.extend(ls.iter().cloned()); + } + _ => {} + } + ret + } + + /// Is this a terminator? + pub fn is_term(&self) -> bool { + match self { + MachTerminator::None => false, + _ => true, + } + } +} + /// A trait describing the ability to encode a MachInst into binary machine code. pub trait MachInstEmit: MachInst { /// Persistent state carried across `emit` invocations. @@ -330,6 +343,8 @@ pub struct MachCompileResult { pub disasm: Option, /// Unwind info. pub unwind_info: Option>, + /// Debug info: value labels to registers/stackslots at code offsets. + pub value_labels_ranges: Option, } impl MachCompileResult { @@ -386,13 +401,17 @@ pub trait MachBackend { Ok(None) } - /// Machine-specific condcode info needed by TargetIsa. /// Creates a new System V Common Information Entry for the ISA. #[cfg(feature = "unwind")] fn create_systemv_cie(&self) -> Option { // By default, an ISA cannot create a System V CIE None } + /// Maps a regalloc::Reg to a DWARF register number. + #[cfg(feature = "unwind")] + fn map_reg_to_dwarf(&self, _: Reg) -> Result { + Err(RegisterMappingError::UnsupportedArchitecture) + } } /// Expected unwind info type. @@ -431,3 +450,15 @@ pub trait UnwindInfoGenerator { context: UnwindInfoContext, ) -> CodegenResult>>; } + +/// Info about an operation that loads or stores from/to the stack. +#[derive(Clone, Copy, Debug)] +pub enum MachInstStackOpInfo { + /// Load from an offset from the nominal stack pointer into the given reg. + LoadNomSPOff(Reg, i64), + /// Store to an offset from the nominal stack pointer from the given reg. + StoreNomSPOff(Reg, i64), + /// Adjustment of nominal-SP up or down. This value is added to subsequent + /// offsets in loads/stores above to produce real-SP offsets. + NomSPAdj(i64), +} diff --git a/cranelift/codegen/src/machinst/vcode.rs b/cranelift/codegen/src/machinst/vcode.rs index c57f018e3547..9fc46d96550b 100644 --- a/cranelift/codegen/src/machinst/vcode.rs +++ b/cranelift/codegen/src/machinst/vcode.rs @@ -21,7 +21,6 @@ use crate::ir::{self, types, Constant, ConstantData, SourceLoc}; use crate::machinst::*; use crate::settings; use crate::timing; - use regalloc::Function as RegallocFunction; use regalloc::Set as RegallocSet; use regalloc::{ @@ -110,11 +109,19 @@ pub struct VCode { /// Ranges for prologue and epilogue instructions. prologue_epilogue_ranges: Option<(InsnRange, Box<[InsnRange]>)>, - /// Instruction end offsets - insts_layout: RefCell<(Vec, u32)>, + /// Do we generate debug info? + generate_debug_info: bool, + + /// Instruction end offsets, instruction indices at each label, and total + /// buffer size. Only present if `generate_debug_info` is set. + insts_layout: RefCell<(Vec, Vec, u32)>, /// Constants. constants: VCodeConstants, + + /// Are any debug value-labels present? If not, we can skip the + /// post-emission analysis. + has_value_labels: bool, } /// A builder for a VCode function body. This builder is designed for the @@ -157,7 +164,13 @@ impl VCodeBuilder { constants: VCodeConstants, ) -> VCodeBuilder { let reftype_class = I::ref_type_regclass(abi.flags()); - let vcode = VCode::new(abi, emit_info, block_order, constants); + let vcode = VCode::new( + abi, + emit_info, + block_order, + constants, + /* generate_debug_info = */ true, + ); let stack_map_info = StackmapRequestInfo { reftype_class, reftyped_vregs: vec![], @@ -242,6 +255,9 @@ impl VCodeBuilder { } } } + if insn.defines_value_label().is_some() { + self.vcode.has_value_labels = true; + } self.vcode.insts.push(insn); self.vcode.srclocs.push(self.cur_srcloc); if is_safepoint { @@ -296,6 +312,7 @@ impl VCode { emit_info: I::Info, block_order: BlockLoweringOrder, constants: VCodeConstants, + generate_debug_info: bool, ) -> VCode { VCode { liveins: abi.liveins(), @@ -314,8 +331,10 @@ impl VCode { safepoint_insns: vec![], safepoint_slots: vec![], prologue_epilogue_ranges: None, - insts_layout: RefCell::new((vec![], 0)), + generate_debug_info, + insts_layout: RefCell::new((vec![], vec![], 0)), constants, + has_value_labels: false, } } @@ -484,7 +503,8 @@ impl VCode { buffer.reserve_labels_for_blocks(self.num_blocks() as BlockIndex); buffer.reserve_labels_for_constants(&self.constants); - let mut insts_layout = vec![0; self.insts.len()]; + let mut inst_ends = vec![0; self.insts.len()]; + let mut label_insn_iix = vec![0; self.num_blocks()]; let mut safepoint_idx = 0; let mut cur_srcloc = None; @@ -500,6 +520,7 @@ impl VCode { let (start, end) = self.block_ranges[block as usize]; buffer.bind_label(MachLabel::from_block(block)); + label_insn_iix[block as usize] = start; for iix in start..end { let srcloc = self.srclocs[iix as usize]; if cur_srcloc != Some(srcloc) { @@ -526,7 +547,19 @@ impl VCode { self.insts[iix as usize].emit(&mut buffer, &self.emit_info, &mut state); - insts_layout[iix as usize] = buffer.cur_offset(); + if self.generate_debug_info { + // Buffer truncation may have happened since last inst append; trim inst-end + // layout info as appropriate. + let l = &mut inst_ends[0..iix as usize]; + for end in l.iter_mut().rev() { + if *end > buffer.cur_offset() { + *end = buffer.cur_offset(); + } else { + break; + } + } + inst_ends[iix as usize] = buffer.cur_offset(); + } } if cur_srcloc.is_some() { @@ -553,7 +586,16 @@ impl VCode { buffer.defer_constant(label, data.alignment(), data.as_slice(), u32::max_value()); } - *self.insts_layout.borrow_mut() = (insts_layout, buffer.cur_offset()); + if self.generate_debug_info { + for end in inst_ends.iter_mut().rev() { + if *end > buffer.cur_offset() { + *end = buffer.cur_offset(); + } else { + break; + } + } + *self.insts_layout.borrow_mut() = (inst_ends, label_insn_iix, buffer.cur_offset()); + } buffer } @@ -567,13 +609,27 @@ impl VCode { let context = UnwindInfoContext { insts: &self.insts, insts_layout: &layout.0, - len: layout.1, + len: layout.2, prologue: prologue.clone(), epilogues, }; I::UnwindInfo::create_unwind_info(context) } + /// Generates value-label ranges. + pub fn value_labels_ranges(&self) -> crate::result::CodegenResult> { + if !self.has_value_labels { + return Ok(None); + } + + let layout = &self.insts_layout.borrow(); + Ok(Some(debug::compute( + &self.insts, + &layout.0[..], + &layout.1[..], + ))) + } + /// Get the IR block for a BlockIndex, if one exists. pub fn bindex_to_bb(&self, block: BlockIndex) -> Option { self.block_order.lowered_order()[block as usize].orig_block() diff --git a/cranelift/codegen/src/value_label.rs b/cranelift/codegen/src/value_label.rs index e3daeb0f7a8c..3d3ca2ea99c0 100644 --- a/cranelift/codegen/src/value_label.rs +++ b/cranelift/codegen/src/value_label.rs @@ -1,13 +1,16 @@ use crate::ir::{Function, SourceLoc, Value, ValueLabel, ValueLabelAssignments, ValueLoc}; use crate::isa::TargetIsa; +use crate::machinst::MachCompileResult; use crate::regalloc::{Context, RegDiversions}; use crate::HashMap; use alloc::collections::BTreeMap; use alloc::vec::Vec; use core::cmp::Ordering; +use core::convert::From; use core::iter::Iterator; use core::ops::Bound::*; use core::ops::Deref; +use regalloc::Reg; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; @@ -17,13 +20,31 @@ use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct ValueLocRange { /// The ValueLoc containing a ValueLabel during this range. - pub loc: ValueLoc, + pub loc: LabelValueLoc, /// The start of the range. It is an offset in the generated code. pub start: u32, /// The end of the range. It is an offset in the generated code. pub end: u32, } +/// The particular location for a value. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub enum LabelValueLoc { + /// Old-backend location: RegUnit, StackSlot, or Unassigned. + ValueLoc(ValueLoc), + /// New-backend Reg. + Reg(Reg), + /// New-backend offset from stack pointer. + SPOffset(i64), +} + +impl From for LabelValueLoc { + fn from(v: ValueLoc) -> Self { + LabelValueLoc::ValueLoc(v) + } +} + /// Resulting map of Value labels and their ranges/locations. pub type ValueLabelsRanges = HashMap>; @@ -86,14 +107,18 @@ where pub fn build_value_labels_ranges( func: &Function, regalloc: &Context, + mach_compile_result: Option<&MachCompileResult>, isa: &dyn TargetIsa, ) -> ValueLabelsRanges where T: From + Deref + Ord + Copy, { - // FIXME(#1523): New-style backend does not yet have debug info. - if isa.get_mach_backend().is_some() { - return HashMap::new(); + if mach_compile_result.is_some() && mach_compile_result.unwrap().value_labels_ranges.is_some() { + return mach_compile_result + .unwrap() + .value_labels_ranges + .clone() + .unwrap(); } let values_labels = build_value_labels_index::(func); @@ -113,7 +138,7 @@ where .entry(label) .or_insert_with(Vec::new) .push(ValueLocRange { - loc, + loc: loc.into(), start: range.0, end: range.1, }); diff --git a/cranelift/codegen/src/write.rs b/cranelift/codegen/src/write.rs index 8d73e2d1e469..d7528beef46a 100644 --- a/cranelift/codegen/src/write.rs +++ b/cranelift/codegen/src/write.rs @@ -11,7 +11,7 @@ use crate::ir::{ }; use crate::isa::{RegInfo, TargetIsa}; use crate::packed_option::ReservedValue; -use crate::value_label::ValueLabelsRanges; +use crate::value_label::{LabelValueLoc, ValueLabelsRanges}; use crate::HashSet; use alloc::string::String; use alloc::vec::Vec; @@ -278,11 +278,13 @@ pub fn write_block_header( writeln!(w, "):") } -fn write_valueloc(w: &mut dyn Write, loc: ValueLoc, regs: &RegInfo) -> fmt::Result { +fn write_valueloc(w: &mut dyn Write, loc: LabelValueLoc, regs: &RegInfo) -> fmt::Result { match loc { - ValueLoc::Reg(r) => write!(w, "{}", regs.display_regunit(r)), - ValueLoc::Stack(ss) => write!(w, "{}", ss), - ValueLoc::Unassigned => write!(w, "?"), + LabelValueLoc::ValueLoc(ValueLoc::Reg(r)) => write!(w, "{}", regs.display_regunit(r)), + LabelValueLoc::ValueLoc(ValueLoc::Stack(ss)) => write!(w, "{}", ss), + LabelValueLoc::ValueLoc(ValueLoc::Unassigned) => write!(w, "?"), + LabelValueLoc::Reg(r) => write!(w, "{:?}", r), + LabelValueLoc::SPOffset(off) => write!(w, "[sp+{}]", off), } } diff --git a/crates/debug/src/transform/expression.rs b/crates/debug/src/transform/expression.rs index 0a286aa61622..57f3cc7a2124 100644 --- a/crates/debug/src/transform/expression.rs +++ b/crates/debug/src/transform/expression.rs @@ -7,7 +7,7 @@ use std::collections::{HashMap, HashSet}; use std::hash::{Hash, Hasher}; use std::rc::Rc; use wasmtime_environ::entity::EntityRef; -use wasmtime_environ::ir::{StackSlots, ValueLabel, ValueLabelsRanges, ValueLoc}; +use wasmtime_environ::ir::{LabelValueLoc, StackSlots, ValueLabel, ValueLabelsRanges, ValueLoc}; use wasmtime_environ::isa::TargetIsa; use wasmtime_environ::wasm::{get_vmctx_value_label, DefinedFuncIndex}; use wasmtime_environ::ModuleMemoryOffset; @@ -131,27 +131,24 @@ impl CompiledExpression { const X86_64_STACK_OFFSET: i64 = 16; fn translate_loc( - loc: ValueLoc, + loc: LabelValueLoc, frame_info: Option<&FunctionFrameInfo>, isa: &dyn TargetIsa, add_stack_value: bool, ) -> Result>> { Ok(match loc { - ValueLoc::Reg(reg) if add_stack_value => { + LabelValueLoc::ValueLoc(ValueLoc::Reg(reg)) => { let machine_reg = isa.map_dwarf_register(reg)?; let mut writer = ExpressionWriter::new(); - writer.write_op_reg(machine_reg)?; - Some(writer.into_vec()) - } - ValueLoc::Reg(reg) => { - assert!(!add_stack_value); - let machine_reg = isa.map_dwarf_register(reg)?; - let mut writer = ExpressionWriter::new(); - writer.write_op_breg(machine_reg)?; - writer.write_sleb128(0)?; + if add_stack_value { + writer.write_op_reg(machine_reg)?; + } else { + writer.write_op_breg(machine_reg)?; + writer.write_sleb128(0)?; + } Some(writer.into_vec()) } - ValueLoc::Stack(ss) => { + LabelValueLoc::ValueLoc(ValueLoc::Stack(ss)) => { if let Some(frame_info) = frame_info { if let Some(ss_offset) = frame_info.stack_slots[ss].offset { let mut writer = ExpressionWriter::new(); @@ -165,6 +162,27 @@ fn translate_loc( } None } + LabelValueLoc::Reg(r) => { + let machine_reg = isa.map_regalloc_reg_to_dwarf(r)?; + let mut writer = ExpressionWriter::new(); + if add_stack_value { + writer.write_op_reg(machine_reg)?; + } else { + writer.write_op_breg(machine_reg)?; + writer.write_sleb128(0)?; + } + Some(writer.into_vec()) + } + LabelValueLoc::SPOffset(off) => { + let mut writer = ExpressionWriter::new(); + writer.write_op_breg(X86_64::RSP.0)?; + writer.write_sleb128(off)?; + if !add_stack_value { + writer.write_op(gimli::constants::DW_OP_deref)?; + } + return Ok(Some(writer.into_vec())); + } + _ => None, }) } @@ -172,13 +190,13 @@ fn translate_loc( fn append_memory_deref( buf: &mut Vec, frame_info: &FunctionFrameInfo, - vmctx_loc: ValueLoc, + vmctx_loc: LabelValueLoc, isa: &dyn TargetIsa, ) -> Result { let mut writer = ExpressionWriter::new(); // FIXME for imported memory match vmctx_loc { - ValueLoc::Reg(vmctx_reg) => { + LabelValueLoc::ValueLoc(ValueLoc::Reg(vmctx_reg)) => { let reg = isa.map_dwarf_register(vmctx_reg)? as u8; writer.write_u8(gimli::constants::DW_OP_breg0.0 + reg)?; let memory_offset = match frame_info.vmctx_memory_offset() { @@ -189,7 +207,7 @@ fn append_memory_deref( }; writer.write_sleb128(memory_offset)?; } - ValueLoc::Stack(ss) => { + LabelValueLoc::ValueLoc(ValueLoc::Stack(ss)) => { if let Some(ss_offset) = frame_info.stack_slots[ss].offset { writer.write_op_breg(X86_64::RBP.0)?; writer.write_sleb128(ss_offset as i64 + X86_64_STACK_OFFSET)?; @@ -207,6 +225,31 @@ fn append_memory_deref( return Ok(false); } } + LabelValueLoc::Reg(r) => { + let reg = isa.map_regalloc_reg_to_dwarf(r)?; + writer.write_op_breg(reg)?; + let memory_offset = match frame_info.vmctx_memory_offset() { + Some(offset) => offset, + None => { + return Ok(false); + } + }; + writer.write_sleb128(memory_offset)?; + } + LabelValueLoc::SPOffset(off) => { + writer.write_op_breg(X86_64::RSP.0)?; + writer.write_sleb128(off)?; + writer.write_op(gimli::constants::DW_OP_deref)?; + writer.write_op(gimli::constants::DW_OP_consts)?; + let memory_offset = match frame_info.vmctx_memory_offset() { + Some(offset) => offset, + None => { + return Ok(false); + } + }; + writer.write_sleb128(memory_offset)?; + writer.write_op(gimli::constants::DW_OP_plus)?; + } _ => { return Ok(false); } @@ -468,7 +511,7 @@ where let _ = code_chunk; // suppresses warning for final flush } }; - }; + } // Find all landing pads by scanning bytes, do not care about // false location at this moment. // Looks hacky but it is fast; does not need to be really exact. @@ -653,7 +696,7 @@ struct CachedValueLabelRange { func_index: DefinedFuncIndex, start: usize, end: usize, - label_location: HashMap, + label_location: HashMap, } struct ValueLabelRangesBuilder<'a, 'b> { @@ -1179,7 +1222,7 @@ mod tests { fn create_mock_value_ranges() -> (ValueLabelsRanges, (ValueLabel, ValueLabel, ValueLabel)) { use std::collections::HashMap; use wasmtime_environ::entity::EntityRef; - use wasmtime_environ::ir::{ValueLoc, ValueLocRange}; + use wasmtime_environ::ir::{LabelValueLoc, ValueLoc, ValueLocRange}; let mut value_ranges = HashMap::new(); let value_0 = ValueLabel::new(0); let value_1 = ValueLabel::new(1); @@ -1187,7 +1230,7 @@ mod tests { value_ranges.insert( value_0, vec![ValueLocRange { - loc: ValueLoc::Unassigned, + loc: LabelValueLoc::ValueLoc(ValueLoc::Unassigned), start: 0, end: 25, }], @@ -1195,7 +1238,7 @@ mod tests { value_ranges.insert( value_1, vec![ValueLocRange { - loc: ValueLoc::Unassigned, + loc: LabelValueLoc::ValueLoc(ValueLoc::Unassigned), start: 5, end: 30, }], @@ -1204,12 +1247,12 @@ mod tests { value_2, vec![ ValueLocRange { - loc: ValueLoc::Unassigned, + loc: LabelValueLoc::ValueLoc(ValueLoc::Unassigned), start: 0, end: 10, }, ValueLocRange { - loc: ValueLoc::Unassigned, + loc: LabelValueLoc::ValueLoc(ValueLoc::Unassigned), start: 20, end: 30, }, diff --git a/crates/environ/src/data_structures.rs b/crates/environ/src/data_structures.rs index 9e1a164675f3..2fc9e9ecd05d 100644 --- a/crates/environ/src/data_structures.rs +++ b/crates/environ/src/data_structures.rs @@ -3,8 +3,8 @@ pub mod ir { pub use cranelift_codegen::binemit::{Reloc, StackMap}; pub use cranelift_codegen::ir::{ - types, AbiParam, ArgumentPurpose, JumpTableOffsets, LibCall, Signature, SourceLoc, - StackSlots, TrapCode, Type, ValueLabel, ValueLoc, + types, AbiParam, ArgumentPurpose, JumpTableOffsets, LabelValueLoc, LibCall, Signature, + SourceLoc, StackSlots, TrapCode, Type, ValueLabel, ValueLoc, }; pub use cranelift_codegen::{ValueLabelsRanges, ValueLocRange}; } diff --git a/tests/all/debug/lldb.rs b/tests/all/debug/lldb.rs index c29e757b315d..7807732700ea 100644 --- a/tests/all/debug/lldb.rs +++ b/tests/all/debug/lldb.rs @@ -137,7 +137,11 @@ check: exited with status #[ignore] #[cfg(all( any(target_os = "linux", target_os = "macos"), - target_pointer_width = "64" + target_pointer_width = "64", + // Ignore test on new backend. The value this is looking for is + // not available at the point that the breakpoint is set when + // compiled by the new backend. + not(feature = "experimental_x64"), ))] pub fn test_debug_dwarf_ptr() -> Result<()> { let output = lldb_with_script( diff --git a/tests/all/debug/translate.rs b/tests/all/debug/translate.rs index e8ceb7bd9ebc..7253989d57fa 100644 --- a/tests/all/debug/translate.rs +++ b/tests/all/debug/translate.rs @@ -114,7 +114,11 @@ check: DW_AT_decl_line (10) #[cfg(all( any(target_os = "linux", target_os = "macos"), target_arch = "x86_64", - target_pointer_width = "64" + target_pointer_width = "64", + // Ignore test on new backend. This is a specific test with hardcoded + // offsets and the new backend compiles the return basic-block at a different + // offset, causing mismatches. + not(feature = "experimental_x64"), ))] fn test_debug_dwarf5_translate_lines() -> Result<()> { check_line_program(