Merge pull request #2565 from cfallin/debug-value-labels

Detailed debug-info (DWARF) support in new backends (initially x64).
This commit is contained in:
Chris Fallin
2021-01-22 17:22:13 -08:00
committed by GitHub
23 changed files with 958 additions and 94 deletions

View File

@@ -292,6 +292,14 @@ jobs:
env: env:
RUST_BACKTRACE: 1 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 # Build and test lightbeam. Note that
# Lightbeam tests fail right now, but we don't want to block on that. # Lightbeam tests fail right now, but we don't want to block on that.
- run: cargo build --package lightbeam - run: cargo build --package lightbeam

View File

@@ -74,7 +74,7 @@ all-arch = [
] ]
# For dependent crates that want to serialize some parts of cranelift # 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 # Allow snapshotting regalloc test cases. Useful only to report bad register
# allocation failures, or for regalloc.rs developers. # allocation failures, or for regalloc.rs developers.

View File

@@ -473,6 +473,7 @@ impl Context {
Ok(build_value_labels_ranges::<ComparableSourceLoc>( Ok(build_value_labels_ranges::<ComparableSourceLoc>(
&self.func, &self.func,
&self.regalloc, &self.regalloc,
self.mach_compile_result.as_ref(),
isa, isa,
)) ))
} }

View File

@@ -58,6 +58,7 @@ pub use crate::ir::table::TableData;
pub use crate::ir::trapcode::TrapCode; pub use crate::ir::trapcode::TrapCode;
pub use crate::ir::types::Type; pub use crate::ir::types::Type;
pub use crate::ir::valueloc::{ArgumentLoc, ValueLoc}; pub use crate::ir::valueloc::{ArgumentLoc, ValueLoc};
pub use crate::value_label::LabelValueLoc;
pub use cranelift_codegen_shared::condcodes; pub use cranelift_codegen_shared::condcodes;
use crate::binemit; use crate::binemit;

View File

@@ -2371,6 +2371,9 @@ impl MachInstEmit for Inst {
sink.bind_label(jump_around_label); sink.bind_label(jump_around_label);
} }
} }
&Inst::ValueLabelMarker { .. } => {
// Nothing; this is only used to compute debug info.
}
} }
let end_off = sink.cur_offset(); let end_off = sink.cur_offset();

View File

@@ -7,7 +7,7 @@ use crate::binemit::CodeOffset;
use crate::ir::types::{ use crate::ir::types::{
B1, B128, B16, B32, B64, B8, F32, F64, FFLAGS, I128, I16, I32, I64, I8, I8X16, IFLAGS, R32, R64, 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::isa::CallConv;
use crate::machinst::*; use crate::machinst::*;
use crate::{settings, CodegenError, CodegenResult}; use crate::{settings, CodegenError, CodegenResult};
@@ -1210,6 +1210,12 @@ pub enum Inst {
/// The needed space before the next deadline. /// The needed space before the next deadline.
needed_space: CodeOffset, 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 { 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); memarg_regs(mem, collector);
} }
&Inst::VirtualSPOffsetAdj { .. } => {} &Inst::VirtualSPOffsetAdj { .. } => {}
&Inst::ValueLabelMarker { reg, .. } => {
collector.add_use(reg);
}
&Inst::EmitIsland { .. } => {} &Inst::EmitIsland { .. } => {}
} }
} }
@@ -2767,6 +2776,9 @@ fn aarch64_map_regs<RUM: RegUsageMapper>(inst: &mut Inst, mapper: &RUM) {
} }
&mut Inst::VirtualSPOffsetAdj { .. } => {} &mut Inst::VirtualSPOffsetAdj { .. } => {}
&mut Inst::EmitIsland { .. } => {} &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 { fn ref_type_regclass(_: &settings::Flags) -> RegClass {
RegClass::I64 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) format!("virtual_sp_offset_adjust {}", offset)
} }
&Inst::EmitIsland { needed_space } => format!("emit_island {}", needed_space), &Inst::EmitIsland { needed_space } => format!("emit_island {}", needed_space),
&Inst::ValueLabelMarker { label, reg } => {
format!("value_label {:?}, {}", label, reg.show_rru(mb_rru))
}
} }
} }
} }

View File

@@ -79,6 +79,7 @@ impl MachBackend for AArch64Backend {
frame_size, frame_size,
disasm, disasm,
unwind_info, unwind_info,
value_labels_ranges: None,
}) })
} }

View File

@@ -74,6 +74,7 @@ impl MachBackend for Arm32Backend {
frame_size, frame_size,
disasm, disasm,
unwind_info: None, unwind_info: None,
value_labels_ranges: None,
}) })
} }

View File

@@ -325,6 +325,12 @@ pub trait TargetIsa: fmt::Display + Send + Sync {
Err(RegisterMappingError::UnsupportedArchitecture) 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<u16, RegisterMappingError> {
Err(RegisterMappingError::UnsupportedArchitecture)
}
/// Returns an iterator over legal encodings for the instruction. /// Returns an iterator over legal encodings for the instruction.
fn legal_encodings<'a>( fn legal_encodings<'a>(
&'a self, &'a self,

View File

@@ -3029,6 +3029,10 @@ pub(crate) fn emit(
sink.put1(0xff); sink.put1(0xff);
sink.put1(0x17); sink.put1(0x17);
} }
Inst::ValueLabelMarker { .. } => {
// Nothing; this is only used to compute debug info.
}
} }
state.clear_post_insn(); state.clear_post_insn();

View File

@@ -1,7 +1,7 @@
//! This module defines x86_64-specific machine instruction types. //! This module defines x86_64-specific machine instruction types.
use crate::binemit::{CodeOffset, StackMap}; 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::abi::X64ABIMachineSpec;
use crate::isa::x64::settings as x64_settings; use crate::isa::x64::settings as x64_settings;
use crate::isa::CallConv; use crate::isa::CallConv;
@@ -484,6 +484,9 @@ pub enum Inst {
/// A Mach-O TLS symbol access. Returns address of the TLS /// A Mach-O TLS symbol access. Returns address of the TLS
/// symbol in rax. /// symbol in rax.
MachOTlsGetAddr { symbol: ExternalName }, 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 { pub(crate) fn low32_will_sign_extend_to_64(x: u64) -> bool {
@@ -544,7 +547,8 @@ impl Inst {
| Inst::XmmMinMaxSeq { .. } | Inst::XmmMinMaxSeq { .. }
| Inst::XmmUninitializedValue { .. } | Inst::XmmUninitializedValue { .. }
| Inst::ElfTlsGetAddr { .. } | Inst::ElfTlsGetAddr { .. }
| Inst::MachOTlsGetAddr { .. } => None, | Inst::MachOTlsGetAddr { .. }
| Inst::ValueLabelMarker { .. } => None,
// These use dynamic SSE opcodes. // These use dynamic SSE opcodes.
Inst::GprToXmm { op, .. } Inst::GprToXmm { op, .. }
@@ -1800,6 +1804,10 @@ impl PrettyPrint for Inst {
Inst::MachOTlsGetAddr { ref symbol } => { Inst::MachOTlsGetAddr { ref symbol } => {
format!("macho_tls_get_addr {:?}", 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); collector.add_def(reg);
} }
} }
Inst::ValueLabelMarker { reg, .. } => {
collector.add_use(*reg);
}
} }
} }
@@ -2446,6 +2458,8 @@ fn x64_map_regs<RUM: RegUsageMapper>(inst: &mut Inst, mapper: &RUM) {
dst.map_uses(mapper); dst.map_uses(mapper);
} }
Inst::ValueLabelMarker { ref mut reg, .. } => map_use(mapper, reg),
Inst::Ret Inst::Ret
| Inst::EpiloguePlaceholder | Inst::EpiloguePlaceholder
| Inst::JmpKnown { .. } | Inst::JmpKnown { .. }
@@ -2536,6 +2550,25 @@ impl MachInst for Inst {
} }
} }
fn stack_op_info(&self) -> Option<MachInstStackOpInfo> {
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<Reg>, src_reg: Reg, ty: Type) -> Inst { fn gen_move(dst_reg: Writable<Reg>, src_reg: Reg, ty: Type) -> Inst {
let rc_dst = dst_reg.to_reg().get_class(); let rc_dst = dst_reg.to_reg().get_class();
let rc_src = src_reg.get_class(); let rc_src = src_reg.get_class();
@@ -2710,6 +2743,17 @@ impl MachInst for Inst {
RegClass::I64 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; type LabelUse = LabelUse;
} }

View File

@@ -4,13 +4,14 @@ use self::inst::EmitInfo;
use super::TargetIsa; use super::TargetIsa;
use crate::ir::{condcodes::IntCC, Function}; 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::x64::{inst::regs::create_reg_universe_systemv, settings as x64_settings};
use crate::isa::Builder as IsaBuilder; use crate::isa::Builder as IsaBuilder;
use crate::machinst::{compile, MachBackend, MachCompileResult, TargetIsaAdapter, VCode}; use crate::machinst::{compile, MachBackend, MachCompileResult, TargetIsaAdapter, VCode};
use crate::result::CodegenResult; use crate::result::CodegenResult;
use crate::settings::{self as shared_settings, Flags}; use crate::settings::{self as shared_settings, Flags};
use alloc::boxed::Box; use alloc::boxed::Box;
use regalloc::{PrettyPrint, RealRegUniverse}; use regalloc::{PrettyPrint, RealRegUniverse, Reg};
use target_lexicon::Triple; use target_lexicon::Triple;
mod abi; mod abi;
@@ -60,6 +61,7 @@ impl MachBackend for X64Backend {
let buffer = buffer.finish(); let buffer = buffer.finish();
let frame_size = vcode.frame_size(); let frame_size = vcode.frame_size();
let unwind_info = vcode.unwind_info()?; let unwind_info = vcode.unwind_info()?;
let value_labels_ranges = vcode.value_labels_ranges()?;
let disasm = if want_disasm { let disasm = if want_disasm {
Some(vcode.show_rru(Some(&create_reg_universe_systemv(flags)))) Some(vcode.show_rru(Some(&create_reg_universe_systemv(flags))))
@@ -72,6 +74,7 @@ impl MachBackend for X64Backend {
frame_size, frame_size,
disasm, disasm,
unwind_info, unwind_info,
value_labels_ranges,
}) })
} }
@@ -127,6 +130,11 @@ impl MachBackend for X64Backend {
fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> { fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {
Some(inst::unwind::systemv::create_cie()) Some(inst::unwind::systemv::create_cie())
} }
#[cfg(feature = "unwind")]
fn map_reg_to_dwarf(&self, reg: Reg) -> Result<u16, RegisterMappingError> {
inst::unwind::systemv::map_reg(reg).map(|reg| reg.0)
}
} }
/// Create a new `isa::Builder`. /// Create a new `isa::Builder`.

View File

@@ -10,6 +10,9 @@ use crate::settings::Flags;
#[cfg(feature = "testing_hooks")] #[cfg(feature = "testing_hooks")]
use crate::regalloc::RegDiversions; use crate::regalloc::RegDiversions;
#[cfg(feature = "unwind")]
use crate::isa::unwind::systemv::RegisterMappingError;
use core::any::Any; use core::any::Any;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt; use std::fmt;
@@ -134,6 +137,11 @@ impl TargetIsa for TargetIsaAdapter {
self.backend.create_systemv_cie() self.backend.create_systemv_cie()
} }
#[cfg(feature = "unwind")]
fn map_regalloc_reg_to_dwarf(&self, r: Reg) -> Result<u16, RegisterMappingError> {
self.backend.map_reg_to_dwarf(r)
}
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
self as &dyn Any self as &dyn Any
} }

View File

@@ -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<ValueLoc> 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<i64>,
/// Forward map from labeled values to sets of locations.
label_to_locs: HashMap<ValueLabel, HashSet<ValueLoc>>,
/// Reverse map for each register indicating the value it holds, if any.
reg_to_label: HashMap<Reg, ValueLabel>,
/// Reverse map for each stack offset indicating the value it holds, if any.
stack_to_label: HashMap<i64, ValueLabel>,
}
/// Get the registers written (mod'd or def'd) by a machine instruction.
fn get_inst_writes<M: MachInst>(m: &M) -> Vec<Reg> {
// 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(&reg) {
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(&reg) {
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<M: MachInst>(&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<K, V> IntersectFrom for HashMap<K, V>
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<T> IntersectFrom for HashSet<T>
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<T> IntersectFrom for Option<T>
where
T: Copy + Eq,
{
/// Intersectino for Option<T>: 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<I: VCodeInst>(
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<u32, AnalysisInfo> = 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
}

View File

@@ -13,6 +13,7 @@ use crate::ir::instructions::BranchInfo;
use crate::ir::{ use crate::ir::{
ArgumentPurpose, Block, Constant, ConstantData, ExternalName, Function, GlobalValueData, Inst, ArgumentPurpose, Block, Constant, ConstantData, ExternalName, Function, GlobalValueData, Inst,
InstructionData, MemFlags, Opcode, Signature, SourceLoc, Type, Value, ValueDef, InstructionData, MemFlags, Opcode, Signature, SourceLoc, Type, Value, ValueDef,
ValueLabelAssignments, ValueLabelStart,
}; };
use crate::machinst::{ use crate::machinst::{
writable_value_regs, ABICallee, BlockIndex, BlockLoweringOrder, LoweredBlock, MachLabel, VCode, writable_value_regs, ABICallee, BlockIndex, BlockLoweringOrder, LoweredBlock, MachLabel, VCode,
@@ -24,7 +25,7 @@ use alloc::vec::Vec;
use core::convert::TryInto; use core::convert::TryInto;
use log::debug; use log::debug;
use regalloc::{Reg, StackmapRequestInfo, Writable}; use regalloc::{Reg, StackmapRequestInfo, Writable};
use smallvec::SmallVec; use smallvec::{smallvec, SmallVec};
use std::fmt::Debug; use std::fmt::Debug;
/// An "instruction color" partitions CLIF instructions by side-effecting ops. /// 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) { 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(); let retval_regs = self.retval_regs.clone();
for (i, regs) in retval_regs.into_iter().enumerate() { for (i, regs) in retval_regs.into_iter().enumerate() {
let regs = writable_value_regs(regs); let regs = writable_value_regs(regs);
@@ -725,6 +734,9 @@ impl<'func, I: VCodeInst> Lower<'func, I> {
if has_side_effect || value_needed { if has_side_effect || value_needed {
debug!("lowering: inst {}: {:?}", inst, self.f.dfg[inst]); debug!("lowering: inst {}: {:?}", inst, self.f.dfg[inst]);
backend.lower(self, 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() { if data.opcode().is_return() {
// Return: handle specially, using ABI-appropriate sequence. // Return: handle specially, using ABI-appropriate sequence.
@@ -744,6 +756,80 @@ impl<'func, I: VCodeInst> Lower<'func, I> {
Ok(()) 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::<FxHashSet<_>>();
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) { fn finish_ir_inst(&mut self, loc: SourceLoc) {
// `bb_insts` is kept in reverse order, so emit the instructions in // `bb_insts` is kept in reverse order, so emit the instructions in
// reverse order. // reverse order.
@@ -885,6 +971,7 @@ impl<'func, I: VCodeInst> Lower<'func, I> {
// Original block body. // Original block body.
if let Some(bb) = lb.orig_block() { if let Some(bb) = lb.orig_block() {
self.lower_clif_block(backend, bb)?; self.lower_clif_block(backend, bb)?;
self.emit_value_label_markers_for_block_args(bb);
} }
// In-edge phi moves. // In-edge phi moves.
if let Some((pred, inst, succ)) = lb.in_edge() { if let Some((pred, inst, succ)) = lb.in_edge() {

View File

@@ -52,45 +52,9 @@
//! | - all symbolic stack references to //! | - all symbolic stack references to
//! | stackslots and spillslots are resolved //! | stackslots and spillslots are resolved
//! | to concrete FP-offset mem addresses.) //! | to concrete FP-offset mem addresses.)
//! | [block/insn ordering]
//! | //! |
//! VCode<arch_backend::Inst> (machine instructions: //! | [binary emission via MachBuffer
//! | - vcode.final_block_order is filled in. //! | with streaming branch resolution/simplification]
//! | - new insn sequence from regalloc is
//! | placed back into vcode and block
//! | boundaries are updated.)
//! | [redundant branch/block
//! | removal]
//! |
//! VCode<arch_backend::Inst> (machine instructions:
//! | - all blocks that were just an
//! | unconditional branch are removed.)
//! |
//! | [branch finalization
//! | (fallthroughs)]
//! |
//! VCode<arch_backend::Inst> (machine instructions:
//! | - all branches are in lowered one-
//! | target form, but targets are still
//! | block indices.)
//! |
//! | [branch finalization
//! | (offsets)]
//! |
//! VCode<arch_backend::Inst> (machine instructions:
//! | - all branch offsets from start of
//! | function are known, and all branches
//! | have resolved-offset targets.)
//! |
//! | [MemArg finalization]
//! |
//! VCode<arch_backend::Inst> (machine instructions:
//! | - all MemArg references to the constant
//! | pool are replaced with offsets.
//! | - all constant-pool data is collected
//! | in the VCode.)
//! |
//! | [binary emission]
//! | //! |
//! Vec<u8> (machine code!) //! Vec<u8> (machine code!)
//! //!
@@ -98,11 +62,11 @@
use crate::binemit::{CodeInfo, CodeOffset, StackMap}; use crate::binemit::{CodeInfo, CodeOffset, StackMap};
use crate::ir::condcodes::IntCC; 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::isa::unwind::input as unwind_input;
use crate::result::CodegenResult; use crate::result::CodegenResult;
use crate::settings::Flags; use crate::settings::Flags;
use crate::value_label::ValueLabelsRanges;
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::vec::Vec; use alloc::vec::Vec;
use core::fmt::Debug; use core::fmt::Debug;
@@ -111,10 +75,13 @@ use regalloc::RegUsageCollector;
use regalloc::{ use regalloc::{
RealReg, RealRegUniverse, Reg, RegClass, RegUsageMapper, SpillSlot, VirtualReg, Writable, RealReg, RealRegUniverse, Reg, RegClass, RegUsageMapper, SpillSlot, VirtualReg, Writable,
}; };
use smallvec::SmallVec; use smallvec::{smallvec, SmallVec};
use std::string::String; use std::string::String;
use target_lexicon::Triple; use target_lexicon::Triple;
#[cfg(feature = "unwind")]
use crate::isa::unwind::systemv::RegisterMappingError;
pub mod lower; pub mod lower;
pub use lower::*; pub use lower::*;
pub mod vcode; pub mod vcode;
@@ -137,6 +104,7 @@ pub mod inst_common;
pub use inst_common::*; pub use inst_common::*;
pub mod valueregs; pub mod valueregs;
pub use valueregs::*; pub use valueregs::*;
pub mod debug;
/// A machine instruction. /// A machine instruction.
pub trait MachInst: Clone + Debug { pub trait MachInst: Clone + Debug {
@@ -163,6 +131,11 @@ pub trait MachInst: Clone + Debug {
true true
} }
/// If this is a load or store to the stack, return that info.
fn stack_op_info(&self) -> Option<MachInstStackOpInfo> {
None
}
/// Generate a move. /// Generate a move.
fn gen_move(to_reg: Writable<Reg>, from_reg: Reg, ty: Type) -> Self; fn gen_move(to_reg: Writable<Reg>, from_reg: Reg, ty: Type) -> Self;
@@ -223,6 +196,17 @@ pub trait MachInst: Clone + Debug {
/// be dependent on compilation flags. /// be dependent on compilation flags.
fn ref_type_regclass(_flags: &Flags) -> RegClass; 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 /// A label-use kind: a type that describes the types of label references that
/// can occur in an instruction. /// can occur in an instruction.
type LabelUse: MachInstLabelUse; type LabelUse: MachInstLabelUse;
@@ -285,6 +269,35 @@ pub enum MachTerminator<'a> {
Indirect(&'a [MachLabel]), 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. /// A trait describing the ability to encode a MachInst into binary machine code.
pub trait MachInstEmit: MachInst { pub trait MachInstEmit: MachInst {
/// Persistent state carried across `emit` invocations. /// Persistent state carried across `emit` invocations.
@@ -330,6 +343,8 @@ pub struct MachCompileResult {
pub disasm: Option<String>, pub disasm: Option<String>,
/// Unwind info. /// Unwind info.
pub unwind_info: Option<unwind_input::UnwindInfo<Reg>>, pub unwind_info: Option<unwind_input::UnwindInfo<Reg>>,
/// Debug info: value labels to registers/stackslots at code offsets.
pub value_labels_ranges: Option<ValueLabelsRanges>,
} }
impl MachCompileResult { impl MachCompileResult {
@@ -386,13 +401,17 @@ pub trait MachBackend {
Ok(None) Ok(None)
} }
/// Machine-specific condcode info needed by TargetIsa.
/// Creates a new System V Common Information Entry for the ISA. /// Creates a new System V Common Information Entry for the ISA.
#[cfg(feature = "unwind")] #[cfg(feature = "unwind")]
fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> { fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {
// By default, an ISA cannot create a System V CIE // By default, an ISA cannot create a System V CIE
None None
} }
/// Maps a regalloc::Reg to a DWARF register number.
#[cfg(feature = "unwind")]
fn map_reg_to_dwarf(&self, _: Reg) -> Result<u16, RegisterMappingError> {
Err(RegisterMappingError::UnsupportedArchitecture)
}
} }
/// Expected unwind info type. /// Expected unwind info type.
@@ -431,3 +450,15 @@ pub trait UnwindInfoGenerator<I: MachInstEmit> {
context: UnwindInfoContext<I>, context: UnwindInfoContext<I>,
) -> CodegenResult<Option<unwind_input::UnwindInfo<Reg>>>; ) -> CodegenResult<Option<unwind_input::UnwindInfo<Reg>>>;
} }
/// 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),
}

View File

@@ -21,7 +21,6 @@ use crate::ir::{self, types, Constant, ConstantData, SourceLoc};
use crate::machinst::*; use crate::machinst::*;
use crate::settings; use crate::settings;
use crate::timing; use crate::timing;
use regalloc::Function as RegallocFunction; use regalloc::Function as RegallocFunction;
use regalloc::Set as RegallocSet; use regalloc::Set as RegallocSet;
use regalloc::{ use regalloc::{
@@ -110,11 +109,19 @@ pub struct VCode<I: VCodeInst> {
/// Ranges for prologue and epilogue instructions. /// Ranges for prologue and epilogue instructions.
prologue_epilogue_ranges: Option<(InsnRange, Box<[InsnRange]>)>, prologue_epilogue_ranges: Option<(InsnRange, Box<[InsnRange]>)>,
/// Instruction end offsets /// Do we generate debug info?
insts_layout: RefCell<(Vec<u32>, u32)>, 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<u32>, Vec<u32>, u32)>,
/// Constants. /// Constants.
constants: VCodeConstants, 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 /// A builder for a VCode function body. This builder is designed for the
@@ -157,7 +164,13 @@ impl<I: VCodeInst> VCodeBuilder<I> {
constants: VCodeConstants, constants: VCodeConstants,
) -> VCodeBuilder<I> { ) -> VCodeBuilder<I> {
let reftype_class = I::ref_type_regclass(abi.flags()); 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 { let stack_map_info = StackmapRequestInfo {
reftype_class, reftype_class,
reftyped_vregs: vec![], reftyped_vregs: vec![],
@@ -242,6 +255,9 @@ impl<I: VCodeInst> VCodeBuilder<I> {
} }
} }
} }
if insn.defines_value_label().is_some() {
self.vcode.has_value_labels = true;
}
self.vcode.insts.push(insn); self.vcode.insts.push(insn);
self.vcode.srclocs.push(self.cur_srcloc); self.vcode.srclocs.push(self.cur_srcloc);
if is_safepoint { if is_safepoint {
@@ -296,6 +312,7 @@ impl<I: VCodeInst> VCode<I> {
emit_info: I::Info, emit_info: I::Info,
block_order: BlockLoweringOrder, block_order: BlockLoweringOrder,
constants: VCodeConstants, constants: VCodeConstants,
generate_debug_info: bool,
) -> VCode<I> { ) -> VCode<I> {
VCode { VCode {
liveins: abi.liveins(), liveins: abi.liveins(),
@@ -314,8 +331,10 @@ impl<I: VCodeInst> VCode<I> {
safepoint_insns: vec![], safepoint_insns: vec![],
safepoint_slots: vec![], safepoint_slots: vec![],
prologue_epilogue_ranges: None, prologue_epilogue_ranges: None,
insts_layout: RefCell::new((vec![], 0)), generate_debug_info,
insts_layout: RefCell::new((vec![], vec![], 0)),
constants, constants,
has_value_labels: false,
} }
} }
@@ -484,7 +503,8 @@ impl<I: VCodeInst> VCode<I> {
buffer.reserve_labels_for_blocks(self.num_blocks() as BlockIndex); buffer.reserve_labels_for_blocks(self.num_blocks() as BlockIndex);
buffer.reserve_labels_for_constants(&self.constants); 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 safepoint_idx = 0;
let mut cur_srcloc = None; let mut cur_srcloc = None;
@@ -500,6 +520,7 @@ impl<I: VCodeInst> VCode<I> {
let (start, end) = self.block_ranges[block as usize]; let (start, end) = self.block_ranges[block as usize];
buffer.bind_label(MachLabel::from_block(block)); buffer.bind_label(MachLabel::from_block(block));
label_insn_iix[block as usize] = start;
for iix in start..end { for iix in start..end {
let srcloc = self.srclocs[iix as usize]; let srcloc = self.srclocs[iix as usize];
if cur_srcloc != Some(srcloc) { if cur_srcloc != Some(srcloc) {
@@ -526,7 +547,19 @@ impl<I: VCodeInst> VCode<I> {
self.insts[iix as usize].emit(&mut buffer, &self.emit_info, &mut state); 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() { if cur_srcloc.is_some() {
@@ -553,7 +586,16 @@ impl<I: VCodeInst> VCode<I> {
buffer.defer_constant(label, data.alignment(), data.as_slice(), u32::max_value()); 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 buffer
} }
@@ -567,13 +609,27 @@ impl<I: VCodeInst> VCode<I> {
let context = UnwindInfoContext { let context = UnwindInfoContext {
insts: &self.insts, insts: &self.insts,
insts_layout: &layout.0, insts_layout: &layout.0,
len: layout.1, len: layout.2,
prologue: prologue.clone(), prologue: prologue.clone(),
epilogues, epilogues,
}; };
I::UnwindInfo::create_unwind_info(context) I::UnwindInfo::create_unwind_info(context)
} }
/// Generates value-label ranges.
pub fn value_labels_ranges(&self) -> crate::result::CodegenResult<Option<ValueLabelsRanges>> {
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. /// Get the IR block for a BlockIndex, if one exists.
pub fn bindex_to_bb(&self, block: BlockIndex) -> Option<ir::Block> { pub fn bindex_to_bb(&self, block: BlockIndex) -> Option<ir::Block> {
self.block_order.lowered_order()[block as usize].orig_block() self.block_order.lowered_order()[block as usize].orig_block()

View File

@@ -1,13 +1,16 @@
use crate::ir::{Function, SourceLoc, Value, ValueLabel, ValueLabelAssignments, ValueLoc}; use crate::ir::{Function, SourceLoc, Value, ValueLabel, ValueLabelAssignments, ValueLoc};
use crate::isa::TargetIsa; use crate::isa::TargetIsa;
use crate::machinst::MachCompileResult;
use crate::regalloc::{Context, RegDiversions}; use crate::regalloc::{Context, RegDiversions};
use crate::HashMap; use crate::HashMap;
use alloc::collections::BTreeMap; use alloc::collections::BTreeMap;
use alloc::vec::Vec; use alloc::vec::Vec;
use core::cmp::Ordering; use core::cmp::Ordering;
use core::convert::From;
use core::iter::Iterator; use core::iter::Iterator;
use core::ops::Bound::*; use core::ops::Bound::*;
use core::ops::Deref; use core::ops::Deref;
use regalloc::Reg;
#[cfg(feature = "enable-serde")] #[cfg(feature = "enable-serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -17,13 +20,31 @@ use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct ValueLocRange { pub struct ValueLocRange {
/// The ValueLoc containing a ValueLabel during this range. /// 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. /// The start of the range. It is an offset in the generated code.
pub start: u32, pub start: u32,
/// The end of the range. It is an offset in the generated code. /// The end of the range. It is an offset in the generated code.
pub end: u32, 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<ValueLoc> for LabelValueLoc {
fn from(v: ValueLoc) -> Self {
LabelValueLoc::ValueLoc(v)
}
}
/// Resulting map of Value labels and their ranges/locations. /// Resulting map of Value labels and their ranges/locations.
pub type ValueLabelsRanges = HashMap<ValueLabel, Vec<ValueLocRange>>; pub type ValueLabelsRanges = HashMap<ValueLabel, Vec<ValueLocRange>>;
@@ -86,14 +107,18 @@ where
pub fn build_value_labels_ranges<T>( pub fn build_value_labels_ranges<T>(
func: &Function, func: &Function,
regalloc: &Context, regalloc: &Context,
mach_compile_result: Option<&MachCompileResult>,
isa: &dyn TargetIsa, isa: &dyn TargetIsa,
) -> ValueLabelsRanges ) -> ValueLabelsRanges
where where
T: From<SourceLoc> + Deref<Target = SourceLoc> + Ord + Copy, T: From<SourceLoc> + Deref<Target = SourceLoc> + Ord + Copy,
{ {
// FIXME(#1523): New-style backend does not yet have debug info. if mach_compile_result.is_some() && mach_compile_result.unwrap().value_labels_ranges.is_some() {
if isa.get_mach_backend().is_some() { return mach_compile_result
return HashMap::new(); .unwrap()
.value_labels_ranges
.clone()
.unwrap();
} }
let values_labels = build_value_labels_index::<T>(func); let values_labels = build_value_labels_index::<T>(func);
@@ -113,7 +138,7 @@ where
.entry(label) .entry(label)
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(ValueLocRange { .push(ValueLocRange {
loc, loc: loc.into(),
start: range.0, start: range.0,
end: range.1, end: range.1,
}); });

View File

@@ -11,7 +11,7 @@ use crate::ir::{
}; };
use crate::isa::{RegInfo, TargetIsa}; use crate::isa::{RegInfo, TargetIsa};
use crate::packed_option::ReservedValue; use crate::packed_option::ReservedValue;
use crate::value_label::ValueLabelsRanges; use crate::value_label::{LabelValueLoc, ValueLabelsRanges};
use crate::HashSet; use crate::HashSet;
use alloc::string::String; use alloc::string::String;
use alloc::vec::Vec; use alloc::vec::Vec;
@@ -278,11 +278,13 @@ pub fn write_block_header(
writeln!(w, "):") 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 { match loc {
ValueLoc::Reg(r) => write!(w, "{}", regs.display_regunit(r)), LabelValueLoc::ValueLoc(ValueLoc::Reg(r)) => write!(w, "{}", regs.display_regunit(r)),
ValueLoc::Stack(ss) => write!(w, "{}", ss), LabelValueLoc::ValueLoc(ValueLoc::Stack(ss)) => write!(w, "{}", ss),
ValueLoc::Unassigned => write!(w, "?"), LabelValueLoc::ValueLoc(ValueLoc::Unassigned) => write!(w, "?"),
LabelValueLoc::Reg(r) => write!(w, "{:?}", r),
LabelValueLoc::SPOffset(off) => write!(w, "[sp+{}]", off),
} }
} }

View File

@@ -7,7 +7,7 @@ use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::rc::Rc; use std::rc::Rc;
use wasmtime_environ::entity::EntityRef; 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::isa::TargetIsa;
use wasmtime_environ::wasm::{get_vmctx_value_label, DefinedFuncIndex}; use wasmtime_environ::wasm::{get_vmctx_value_label, DefinedFuncIndex};
use wasmtime_environ::ModuleMemoryOffset; use wasmtime_environ::ModuleMemoryOffset;
@@ -131,27 +131,24 @@ impl CompiledExpression {
const X86_64_STACK_OFFSET: i64 = 16; const X86_64_STACK_OFFSET: i64 = 16;
fn translate_loc( fn translate_loc(
loc: ValueLoc, loc: LabelValueLoc,
frame_info: Option<&FunctionFrameInfo>, frame_info: Option<&FunctionFrameInfo>,
isa: &dyn TargetIsa, isa: &dyn TargetIsa,
add_stack_value: bool, add_stack_value: bool,
) -> Result<Option<Vec<u8>>> { ) -> Result<Option<Vec<u8>>> {
Ok(match loc { Ok(match loc {
ValueLoc::Reg(reg) if add_stack_value => { LabelValueLoc::ValueLoc(ValueLoc::Reg(reg)) => {
let machine_reg = isa.map_dwarf_register(reg)?; let machine_reg = isa.map_dwarf_register(reg)?;
let mut writer = ExpressionWriter::new(); let mut writer = ExpressionWriter::new();
if add_stack_value {
writer.write_op_reg(machine_reg)?; writer.write_op_reg(machine_reg)?;
Some(writer.into_vec()) } else {
}
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_op_breg(machine_reg)?;
writer.write_sleb128(0)?; writer.write_sleb128(0)?;
}
Some(writer.into_vec()) Some(writer.into_vec())
} }
ValueLoc::Stack(ss) => { LabelValueLoc::ValueLoc(ValueLoc::Stack(ss)) => {
if let Some(frame_info) = frame_info { if let Some(frame_info) = frame_info {
if let Some(ss_offset) = frame_info.stack_slots[ss].offset { if let Some(ss_offset) = frame_info.stack_slots[ss].offset {
let mut writer = ExpressionWriter::new(); let mut writer = ExpressionWriter::new();
@@ -165,6 +162,27 @@ fn translate_loc(
} }
None 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, _ => None,
}) })
} }
@@ -172,13 +190,13 @@ fn translate_loc(
fn append_memory_deref( fn append_memory_deref(
buf: &mut Vec<u8>, buf: &mut Vec<u8>,
frame_info: &FunctionFrameInfo, frame_info: &FunctionFrameInfo,
vmctx_loc: ValueLoc, vmctx_loc: LabelValueLoc,
isa: &dyn TargetIsa, isa: &dyn TargetIsa,
) -> Result<bool> { ) -> Result<bool> {
let mut writer = ExpressionWriter::new(); let mut writer = ExpressionWriter::new();
// FIXME for imported memory // FIXME for imported memory
match vmctx_loc { match vmctx_loc {
ValueLoc::Reg(vmctx_reg) => { LabelValueLoc::ValueLoc(ValueLoc::Reg(vmctx_reg)) => {
let reg = isa.map_dwarf_register(vmctx_reg)? as u8; let reg = isa.map_dwarf_register(vmctx_reg)? as u8;
writer.write_u8(gimli::constants::DW_OP_breg0.0 + reg)?; writer.write_u8(gimli::constants::DW_OP_breg0.0 + reg)?;
let memory_offset = match frame_info.vmctx_memory_offset() { let memory_offset = match frame_info.vmctx_memory_offset() {
@@ -189,7 +207,7 @@ fn append_memory_deref(
}; };
writer.write_sleb128(memory_offset)?; writer.write_sleb128(memory_offset)?;
} }
ValueLoc::Stack(ss) => { LabelValueLoc::ValueLoc(ValueLoc::Stack(ss)) => {
if let Some(ss_offset) = frame_info.stack_slots[ss].offset { if let Some(ss_offset) = frame_info.stack_slots[ss].offset {
writer.write_op_breg(X86_64::RBP.0)?; writer.write_op_breg(X86_64::RBP.0)?;
writer.write_sleb128(ss_offset as i64 + X86_64_STACK_OFFSET)?; writer.write_sleb128(ss_offset as i64 + X86_64_STACK_OFFSET)?;
@@ -207,6 +225,31 @@ fn append_memory_deref(
return Ok(false); 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); return Ok(false);
} }
@@ -468,7 +511,7 @@ where
let _ = code_chunk; // suppresses warning for final flush let _ = code_chunk; // suppresses warning for final flush
} }
}; };
}; }
// Find all landing pads by scanning bytes, do not care about // Find all landing pads by scanning bytes, do not care about
// false location at this moment. // false location at this moment.
// Looks hacky but it is fast; does not need to be really exact. // Looks hacky but it is fast; does not need to be really exact.
@@ -653,7 +696,7 @@ struct CachedValueLabelRange {
func_index: DefinedFuncIndex, func_index: DefinedFuncIndex,
start: usize, start: usize,
end: usize, end: usize,
label_location: HashMap<ValueLabel, ValueLoc>, label_location: HashMap<ValueLabel, LabelValueLoc>,
} }
struct ValueLabelRangesBuilder<'a, 'b> { struct ValueLabelRangesBuilder<'a, 'b> {
@@ -1179,7 +1222,7 @@ mod tests {
fn create_mock_value_ranges() -> (ValueLabelsRanges, (ValueLabel, ValueLabel, ValueLabel)) { fn create_mock_value_ranges() -> (ValueLabelsRanges, (ValueLabel, ValueLabel, ValueLabel)) {
use std::collections::HashMap; use std::collections::HashMap;
use wasmtime_environ::entity::EntityRef; 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 mut value_ranges = HashMap::new();
let value_0 = ValueLabel::new(0); let value_0 = ValueLabel::new(0);
let value_1 = ValueLabel::new(1); let value_1 = ValueLabel::new(1);
@@ -1187,7 +1230,7 @@ mod tests {
value_ranges.insert( value_ranges.insert(
value_0, value_0,
vec![ValueLocRange { vec![ValueLocRange {
loc: ValueLoc::Unassigned, loc: LabelValueLoc::ValueLoc(ValueLoc::Unassigned),
start: 0, start: 0,
end: 25, end: 25,
}], }],
@@ -1195,7 +1238,7 @@ mod tests {
value_ranges.insert( value_ranges.insert(
value_1, value_1,
vec![ValueLocRange { vec![ValueLocRange {
loc: ValueLoc::Unassigned, loc: LabelValueLoc::ValueLoc(ValueLoc::Unassigned),
start: 5, start: 5,
end: 30, end: 30,
}], }],
@@ -1204,12 +1247,12 @@ mod tests {
value_2, value_2,
vec![ vec![
ValueLocRange { ValueLocRange {
loc: ValueLoc::Unassigned, loc: LabelValueLoc::ValueLoc(ValueLoc::Unassigned),
start: 0, start: 0,
end: 10, end: 10,
}, },
ValueLocRange { ValueLocRange {
loc: ValueLoc::Unassigned, loc: LabelValueLoc::ValueLoc(ValueLoc::Unassigned),
start: 20, start: 20,
end: 30, end: 30,
}, },

View File

@@ -3,8 +3,8 @@
pub mod ir { pub mod ir {
pub use cranelift_codegen::binemit::{Reloc, StackMap}; pub use cranelift_codegen::binemit::{Reloc, StackMap};
pub use cranelift_codegen::ir::{ pub use cranelift_codegen::ir::{
types, AbiParam, ArgumentPurpose, JumpTableOffsets, LibCall, Signature, SourceLoc, types, AbiParam, ArgumentPurpose, JumpTableOffsets, LabelValueLoc, LibCall, Signature,
StackSlots, TrapCode, Type, ValueLabel, ValueLoc, SourceLoc, StackSlots, TrapCode, Type, ValueLabel, ValueLoc,
}; };
pub use cranelift_codegen::{ValueLabelsRanges, ValueLocRange}; pub use cranelift_codegen::{ValueLabelsRanges, ValueLocRange};
} }

View File

@@ -137,7 +137,11 @@ check: exited with status
#[ignore] #[ignore]
#[cfg(all( #[cfg(all(
any(target_os = "linux", target_os = "macos"), 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<()> { pub fn test_debug_dwarf_ptr() -> Result<()> {
let output = lldb_with_script( let output = lldb_with_script(

View File

@@ -114,7 +114,11 @@ check: DW_AT_decl_line (10)
#[cfg(all( #[cfg(all(
any(target_os = "linux", target_os = "macos"), any(target_os = "linux", target_os = "macos"),
target_arch = "x86_64", 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<()> { fn test_debug_dwarf5_translate_lines() -> Result<()> {
check_line_program( check_line_program(