Browse Source

Support referencing stack slots in the DWARF debug info (#6960)

* Add a test

* Disable test

* Add support for specifying stack locations in debug info

Always emit SysV-style CFI unwind info if we need debug info,
and reference it in the debug info using DW_OP_call_frame_cfa.

* Add toolchain comment to the test

* Add a comment and assert
pull/6971/head
SingleAccretion 1 year ago
committed by GitHub
parent
commit
7b16eccd9b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      cranelift/codegen/src/isa/aarch64/abi.rs
  2. 4
      cranelift/codegen/src/isa/aarch64/mod.rs
  3. 4
      cranelift/codegen/src/isa/mod.rs
  4. 9
      cranelift/codegen/src/isa/riscv64/abi.rs
  5. 4
      cranelift/codegen/src/isa/riscv64/mod.rs
  6. 4
      cranelift/codegen/src/isa/s390x/abi.rs
  7. 4
      cranelift/codegen/src/isa/s390x/mod.rs
  8. 19
      cranelift/codegen/src/isa/unwind.rs
  9. 6
      cranelift/codegen/src/isa/unwind/systemv.rs
  10. 8
      cranelift/codegen/src/isa/x64/abi.rs
  11. 4
      cranelift/codegen/src/isa/x64/mod.rs
  12. 45
      cranelift/codegen/src/machinst/abi.rs
  13. 28
      cranelift/codegen/src/machinst/mod.rs
  14. 32
      cranelift/codegen/src/machinst/vcode.rs
  15. 6
      cranelift/codegen/src/value_label.rs
  16. 11
      crates/cranelift-shared/src/compiled_function.rs
  17. 33
      crates/cranelift/src/compiler.rs
  18. 10
      crates/cranelift/src/debug/transform/attr.rs
  19. 18
      crates/cranelift/src/debug/transform/expression.rs
  20. 16
      crates/cranelift/src/debug/write_debuginfo.rs
  21. 49
      tests/all/debug/lldb.rs
  22. 16
      tests/all/debug/testsuite/spilled_frame_base.c
  23. BIN
      tests/all/debug/testsuite/spilled_frame_base.wasm

8
cranelift/codegen/src/isa/aarch64/abi.rs

@ -584,7 +584,7 @@ impl ABIMachineSpec for AArch64MachineDeps {
insts insts
} }
fn gen_prologue_frame_setup(flags: &settings::Flags) -> SmallInstVec<Inst> { fn gen_prologue_frame_setup(flags: &settings::Flags) -> (SmallInstVec<Inst>, u32) {
let mut insts = SmallVec::new(); let mut insts = SmallVec::new();
// stp fp (x29), lr (x30), [sp, #-16]! // stp fp (x29), lr (x30), [sp, #-16]!
@ -597,10 +597,11 @@ impl ABIMachineSpec for AArch64MachineDeps {
flags: MemFlags::trusted(), flags: MemFlags::trusted(),
}); });
let setup_area_size = 16; // FP, LR
if flags.unwind_info() { if flags.unwind_info() {
insts.push(Inst::Unwind { insts.push(Inst::Unwind {
inst: UnwindInst::PushFrameRegs { inst: UnwindInst::PushFrameRegs {
offset_upward_to_caller_sp: 16, // FP, LR offset_upward_to_caller_sp: setup_area_size,
}, },
}); });
} }
@ -617,7 +618,8 @@ impl ABIMachineSpec for AArch64MachineDeps {
shift12: false, shift12: false,
}, },
}); });
insts
(insts, setup_area_size)
} }
fn gen_epilogue_frame_restore(_: &settings::Flags) -> SmallInstVec<Inst> { fn gen_epilogue_frame_restore(_: &settings::Flags) -> SmallInstVec<Inst> {

4
cranelift/codegen/src/isa/aarch64/mod.rs

@ -132,10 +132,10 @@ impl TargetIsa for AArch64Backend {
fn emit_unwind_info( fn emit_unwind_info(
&self, &self,
result: &CompiledCode, result: &CompiledCode,
kind: crate::machinst::UnwindInfoKind, kind: crate::isa::unwind::UnwindInfoKind,
) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> { ) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> {
use crate::isa::unwind::UnwindInfo; use crate::isa::unwind::UnwindInfo;
use crate::machinst::UnwindInfoKind; use crate::isa::unwind::UnwindInfoKind;
Ok(match kind { Ok(match kind {
UnwindInfoKind::SystemV => { UnwindInfoKind::SystemV => {
let mapper = self::inst::unwind::systemv::RegisterMapper; let mapper = self::inst::unwind::systemv::RegisterMapper;

4
cranelift/codegen/src/isa/mod.rs

@ -49,8 +49,8 @@ pub use crate::isa::call_conv::CallConv;
use crate::flowgraph; use crate::flowgraph;
use crate::ir::{self, Function, Type}; use crate::ir::{self, Function, Type};
#[cfg(feature = "unwind")] #[cfg(feature = "unwind")]
use crate::isa::unwind::systemv::RegisterMappingError; use crate::isa::unwind::{systemv::RegisterMappingError, UnwindInfoKind};
use crate::machinst::{CompiledCode, CompiledCodeStencil, TextSectionBuilder, UnwindInfoKind}; use crate::machinst::{CompiledCode, CompiledCodeStencil, TextSectionBuilder};
use crate::settings; use crate::settings;
use crate::settings::SetResult; use crate::settings::SetResult;
use crate::CodegenResult; use crate::CodegenResult;

9
cranelift/codegen/src/isa/riscv64/abi.rs

@ -341,7 +341,7 @@ impl ABIMachineSpec for Riscv64MachineDeps {
} }
} }
fn gen_prologue_frame_setup(flags: &settings::Flags) -> SmallInstVec<Inst> { fn gen_prologue_frame_setup(flags: &settings::Flags) -> (SmallInstVec<Self::I>, u32) {
// add sp,sp,-16 ;; alloc stack space for fp. // add sp,sp,-16 ;; alloc stack space for fp.
// sd ra,8(sp) ;; save ra. // sd ra,8(sp) ;; save ra.
// sd fp,0(sp) ;; store old fp. // sd fp,0(sp) ;; store old fp.
@ -358,10 +358,12 @@ impl ABIMachineSpec for Riscv64MachineDeps {
fp_reg(), fp_reg(),
I64, I64,
)); ));
let setup_area_size = 16; // FP, LR
if flags.unwind_info() { if flags.unwind_info() {
insts.push(Inst::Unwind { insts.push(Inst::Unwind {
inst: UnwindInst::PushFrameRegs { inst: UnwindInst::PushFrameRegs {
offset_upward_to_caller_sp: 16, // FP, LR offset_upward_to_caller_sp: setup_area_size,
}, },
}); });
} }
@ -370,7 +372,8 @@ impl ABIMachineSpec for Riscv64MachineDeps {
rm: stack_reg(), rm: stack_reg(),
ty: I64, ty: I64,
}); });
insts
(insts, setup_area_size)
} }
/// reverse of gen_prologue_frame_setup. /// reverse of gen_prologue_frame_setup.
fn gen_epilogue_frame_restore(_: &settings::Flags) -> SmallInstVec<Inst> { fn gen_epilogue_frame_restore(_: &settings::Flags) -> SmallInstVec<Inst> {

4
cranelift/codegen/src/isa/riscv64/mod.rs

@ -127,10 +127,10 @@ impl TargetIsa for Riscv64Backend {
fn emit_unwind_info( fn emit_unwind_info(
&self, &self,
result: &CompiledCode, result: &CompiledCode,
kind: crate::machinst::UnwindInfoKind, kind: crate::isa::unwind::UnwindInfoKind,
) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> { ) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> {
use crate::isa::unwind::UnwindInfo; use crate::isa::unwind::UnwindInfo;
use crate::machinst::UnwindInfoKind; use crate::isa::unwind::UnwindInfoKind;
Ok(match kind { Ok(match kind {
UnwindInfoKind::SystemV => { UnwindInfoKind::SystemV => {
let mapper = self::inst::unwind::systemv::RegisterMapper; let mapper = self::inst::unwind::systemv::RegisterMapper;

4
cranelift/codegen/src/isa/s390x/abi.rs

@ -556,8 +556,8 @@ impl ABIMachineSpec for S390xMachineDeps {
} }
} }
fn gen_prologue_frame_setup(_flags: &settings::Flags) -> SmallInstVec<Inst> { fn gen_prologue_frame_setup(_flags: &settings::Flags) -> (SmallInstVec<Inst>, u32) {
SmallVec::new() (SmallVec::new(), 0)
} }
fn gen_epilogue_frame_restore(_flags: &settings::Flags) -> SmallInstVec<Inst> { fn gen_epilogue_frame_restore(_flags: &settings::Flags) -> SmallInstVec<Inst> {

4
cranelift/codegen/src/isa/s390x/mod.rs

@ -129,10 +129,10 @@ impl TargetIsa for S390xBackend {
fn emit_unwind_info( fn emit_unwind_info(
&self, &self,
result: &CompiledCode, result: &CompiledCode,
kind: crate::machinst::UnwindInfoKind, kind: crate::isa::unwind::UnwindInfoKind,
) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> { ) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> {
use crate::isa::unwind::UnwindInfo; use crate::isa::unwind::UnwindInfo;
use crate::machinst::UnwindInfoKind; use crate::isa::unwind::UnwindInfoKind;
Ok(match kind { Ok(match kind {
UnwindInfoKind::SystemV => { UnwindInfoKind::SystemV => {
let mapper = self::inst::unwind::systemv::RegisterMapper; let mapper = self::inst::unwind::systemv::RegisterMapper;

19
cranelift/codegen/src/isa/unwind.rs

@ -11,6 +11,23 @@ pub mod systemv;
#[cfg(feature = "unwind")] #[cfg(feature = "unwind")]
pub mod winx64; pub mod winx64;
/// CFA-based unwind information used on SystemV.
pub type CfaUnwindInfo = systemv::UnwindInfo;
/// Expected unwind info type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UnwindInfoKind {
/// No unwind info.
None,
/// SystemV CIE/FDE unwind info.
#[cfg(feature = "unwind")]
SystemV,
/// Windows X64 Unwind info
#[cfg(feature = "unwind")]
Windows,
}
/// Represents unwind information for a single function. /// Represents unwind information for a single function.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
@ -21,7 +38,7 @@ pub enum UnwindInfo {
WindowsX64(winx64::UnwindInfo), WindowsX64(winx64::UnwindInfo),
/// System V ABI unwind information. /// System V ABI unwind information.
#[cfg(feature = "unwind")] #[cfg(feature = "unwind")]
SystemV(systemv::UnwindInfo), SystemV(CfaUnwindInfo),
} }
/// Unwind pseudoinstruction used in VCode backends: represents that /// Unwind pseudoinstruction used in VCode backends: represents that

6
cranelift/codegen/src/isa/unwind/systemv.rs

@ -161,6 +161,12 @@ pub struct UnwindInfo {
len: u32, len: u32,
} }
/// Offset from the caller's SP to CFA as we define it.
pub(crate) fn caller_sp_to_cfa_offset() -> u32 {
// Currently we define them to always be equal.
0
}
pub(crate) fn create_unwind_info_from_insts<MR: RegisterMapper<Reg>>( pub(crate) fn create_unwind_info_from_insts<MR: RegisterMapper<Reg>>(
insts: &[(CodeOffset, UnwindInst)], insts: &[(CodeOffset, UnwindInst)],
code_len: usize, code_len: usize,

8
cranelift/codegen/src/isa/x64/abi.rs

@ -444,7 +444,7 @@ impl ABIMachineSpec for X64ABIMachineSpec {
} }
} }
fn gen_prologue_frame_setup(flags: &settings::Flags) -> SmallInstVec<Self::I> { fn gen_prologue_frame_setup(flags: &settings::Flags) -> (SmallInstVec<Self::I>, u32) {
let r_rsp = regs::rsp(); let r_rsp = regs::rsp();
let r_rbp = regs::rbp(); let r_rbp = regs::rbp();
let w_rbp = Writable::from_reg(r_rbp); let w_rbp = Writable::from_reg(r_rbp);
@ -453,10 +453,11 @@ impl ABIMachineSpec for X64ABIMachineSpec {
// RSP before the call will be 0 % 16. So here, it is 8 % 16. // RSP before the call will be 0 % 16. So here, it is 8 % 16.
insts.push(Inst::push64(RegMemImm::reg(r_rbp))); insts.push(Inst::push64(RegMemImm::reg(r_rbp)));
let setup_area_size = 16; // RBP, return address
if flags.unwind_info() { if flags.unwind_info() {
insts.push(Inst::Unwind { insts.push(Inst::Unwind {
inst: UnwindInst::PushFrameRegs { inst: UnwindInst::PushFrameRegs {
offset_upward_to_caller_sp: 16, // RBP, return address offset_upward_to_caller_sp: setup_area_size,
}, },
}); });
} }
@ -464,7 +465,8 @@ impl ABIMachineSpec for X64ABIMachineSpec {
// `mov %rsp, %rbp` // `mov %rsp, %rbp`
// RSP is now 0 % 16 // RSP is now 0 % 16
insts.push(Inst::mov_r_r(OperandSize::Size64, r_rsp, w_rbp)); insts.push(Inst::mov_r_r(OperandSize::Size64, r_rsp, w_rbp));
insts
(insts, setup_area_size)
} }
fn gen_epilogue_frame_restore(_: &settings::Flags) -> SmallInstVec<Self::I> { fn gen_epilogue_frame_restore(_: &settings::Flags) -> SmallInstVec<Self::I> {

4
cranelift/codegen/src/isa/x64/mod.rs

@ -123,10 +123,10 @@ impl TargetIsa for X64Backend {
fn emit_unwind_info( fn emit_unwind_info(
&self, &self,
result: &CompiledCode, result: &CompiledCode,
kind: crate::machinst::UnwindInfoKind, kind: crate::isa::unwind::UnwindInfoKind,
) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> { ) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> {
use crate::isa::unwind::UnwindInfo; use crate::isa::unwind::UnwindInfo;
use crate::machinst::UnwindInfoKind; use crate::isa::unwind::UnwindInfoKind;
Ok(match kind { Ok(match kind {
UnwindInfoKind::SystemV => { UnwindInfoKind::SystemV => {
let mapper = self::inst::unwind::systemv::RegisterMapper; let mapper = self::inst::unwind::systemv::RegisterMapper;

45
cranelift/codegen/src/machinst/abi.rs

@ -522,8 +522,8 @@ pub trait ABIMachineSpec {
/// Generate the usual frame-setup sequence for this architecture: e.g., /// Generate the usual frame-setup sequence for this architecture: e.g.,
/// `push rbp / mov rbp, rsp` on x86-64, or `stp fp, lr, [sp, #-16]!` on /// `push rbp / mov rbp, rsp` on x86-64, or `stp fp, lr, [sp, #-16]!` on
/// AArch64. /// AArch64. Return generated instructions and stack size of the setup area.
fn gen_prologue_frame_setup(flags: &settings::Flags) -> SmallInstVec<Self::I>; fn gen_prologue_frame_setup(flags: &settings::Flags) -> (SmallInstVec<Self::I>, u32);
/// Generate the usual frame-restore sequence for this architecture. /// Generate the usual frame-restore sequence for this architecture.
fn gen_epilogue_frame_restore(flags: &settings::Flags) -> SmallInstVec<Self::I>; fn gen_epilogue_frame_restore(flags: &settings::Flags) -> SmallInstVec<Self::I>;
@ -1021,6 +1021,9 @@ pub struct Callee<M: ABIMachineSpec> {
/// Storage allocated for the fixed part of the stack frame. This is /// Storage allocated for the fixed part of the stack frame. This is
/// usually the same as the total frame size below. /// usually the same as the total frame size below.
fixed_frame_storage_size: u32, fixed_frame_storage_size: u32,
/// Size of the area between the FP as defined in the prolog and caller's SP.
/// It will usually contain the saved FP/LR pair.
frame_setup_area_size: u32,
/// "Total frame size", as defined by "distance between FP and nominal SP". /// "Total frame size", as defined by "distance between FP and nominal SP".
/// Some items are pushed below nominal SP, so the function may actually use /// Some items are pushed below nominal SP, so the function may actually use
/// more stack than this would otherwise imply. It is simply the initial /// more stack than this would otherwise imply. It is simply the initial
@ -1185,6 +1188,7 @@ impl<M: ABIMachineSpec> Callee<M> {
clobbered: vec![], clobbered: vec![],
spillslots: None, spillslots: None,
fixed_frame_storage_size: 0, fixed_frame_storage_size: 0,
frame_setup_area_size: 0,
total_frame_size: None, total_frame_size: None,
ret_area_ptr: None, ret_area_ptr: None,
arg_temp_reg: vec![], arg_temp_reg: vec![],
@ -1760,10 +1764,7 @@ impl<M: ABIMachineSpec> Callee<M> {
ty: Type, ty: Type,
into_regs: ValueRegs<Writable<Reg>>, into_regs: ValueRegs<Writable<Reg>>,
) -> SmallInstVec<M::I> { ) -> SmallInstVec<M::I> {
// Offset from beginning of spillslot area, which is at nominal SP + stackslots_size. let sp_off = self.get_spillslot_offset(slot);
let islot = slot.index() as i64;
let spill_off = islot * M::word_bytes() as i64;
let sp_off = self.stackslots_size as i64 + spill_off;
trace!("load_spillslot: slot {:?} -> sp_off {}", slot, sp_off); trace!("load_spillslot: slot {:?} -> sp_off {}", slot, sp_off);
gen_load_stack_multi::<M>(StackAMode::NominalSPOffset(sp_off, ty), into_regs, ty) gen_load_stack_multi::<M>(StackAMode::NominalSPOffset(sp_off, ty), into_regs, ty)
@ -1776,10 +1777,7 @@ impl<M: ABIMachineSpec> Callee<M> {
ty: Type, ty: Type,
from_regs: ValueRegs<Reg>, from_regs: ValueRegs<Reg>,
) -> SmallInstVec<M::I> { ) -> SmallInstVec<M::I> {
// Offset from beginning of spillslot area, which is at nominal SP + stackslots_size. let sp_off = self.get_spillslot_offset(slot);
let islot = slot.index() as i64;
let spill_off = islot * M::word_bytes() as i64;
let sp_off = self.stackslots_size as i64 + spill_off;
trace!("store_spillslot: slot {:?} -> sp_off {}", slot, sp_off); trace!("store_spillslot: slot {:?} -> sp_off {}", slot, sp_off);
gen_store_stack_multi::<M>(StackAMode::NominalSPOffset(sp_off, ty), from_regs, ty) gen_store_stack_multi::<M>(StackAMode::NominalSPOffset(sp_off, ty), from_regs, ty)
@ -1890,8 +1888,9 @@ impl<M: ABIMachineSpec> Callee<M> {
); );
if self.setup_frame { if self.setup_frame {
// set up frame let (setup_insts, setup_area_size) = M::gen_prologue_frame_setup(&self.flags);
insts.extend(M::gen_prologue_frame_setup(&self.flags).into_iter()); insts.extend(setup_insts.into_iter());
self.frame_setup_area_size = setup_area_size;
} }
// Leaf functions with zero stack don't need a stack check if one's // Leaf functions with zero stack don't need a stack check if one's
@ -1992,14 +1991,20 @@ impl<M: ABIMachineSpec> Callee<M> {
} }
/// Returns the full frame size for the given function, after prologue /// Returns the full frame size for the given function, after prologue
/// emission has run. This comprises the spill slots and stack-storage slots /// emission has run. This comprises the spill slots and stack-storage
/// (but not storage for clobbered callee-save registers, arguments pushed /// slots as well as storage for clobbered callee-save registers, but
/// at callsites within this function, or other ephemeral pushes). /// not arguments arguments pushed at callsites within this function,
/// or other ephemeral pushes.
pub fn frame_size(&self) -> u32 { pub fn frame_size(&self) -> u32 {
self.total_frame_size self.total_frame_size
.expect("frame size not computed before prologue generation") .expect("frame size not computed before prologue generation")
} }
/// Returns offset from the nominal SP to caller's SP.
pub fn nominal_sp_to_caller_sp_offset(&self) -> u32 {
self.frame_size() + self.frame_setup_area_size
}
/// Returns the size of arguments expected on the stack. /// Returns the size of arguments expected on the stack.
pub fn stack_args_size(&self, sigs: &SigSet) -> u32 { pub fn stack_args_size(&self, sigs: &SigSet) -> u32 {
sigs[self.sig].sized_stack_arg_space sigs[self.sig].sized_stack_arg_space
@ -2020,6 +2025,16 @@ impl<M: ABIMachineSpec> Callee<M> {
M::get_number_of_spillslots_for_value(rc, max, &self.isa_flags) M::get_number_of_spillslots_for_value(rc, max, &self.isa_flags)
} }
/// Get the spill slot offset relative to nominal SP.
pub fn get_spillslot_offset(&self, slot: SpillSlot) -> i64 {
// Offset from beginning of spillslot area, which is at nominal SP + stackslots_size.
let islot = slot.index() as i64;
let spill_off = islot * M::word_bytes() as i64;
let sp_off = self.stackslots_size as i64 + spill_off;
sp_off
}
/// Generate a spill. /// Generate a spill.
pub fn gen_spill(&self, to_slot: SpillSlot, from_reg: RealReg) -> M::I { pub fn gen_spill(&self, to_slot: SpillSlot, from_reg: RealReg) -> M::I {
let ty = M::I::canonical_type_for_rc(Reg::from(from_reg).class()); let ty = M::I::canonical_type_for_rc(Reg::from(from_reg).class());

28
cranelift/codegen/src/machinst/mod.rs

@ -482,10 +482,24 @@ impl CompiledCode {
&self, &self,
isa: &dyn crate::isa::TargetIsa, isa: &dyn crate::isa::TargetIsa,
) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> { ) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> {
use crate::isa::unwind::UnwindInfoKind;
let unwind_info_kind = match isa.triple().operating_system { let unwind_info_kind = match isa.triple().operating_system {
target_lexicon::OperatingSystem::Windows => UnwindInfoKind::Windows, target_lexicon::OperatingSystem::Windows => UnwindInfoKind::Windows,
_ => UnwindInfoKind::SystemV, _ => UnwindInfoKind::SystemV,
}; };
self.create_unwind_info_of_kind(isa, unwind_info_kind)
}
/// Creates unwind information for the function using the supplied
/// "kind". Supports cross-OS (but not cross-arch) generation.
///
/// Returns `None` if the function has no unwind information.
#[cfg(feature = "unwind")]
pub fn create_unwind_info_of_kind(
&self,
isa: &dyn crate::isa::TargetIsa,
unwind_info_kind: crate::isa::unwind::UnwindInfoKind,
) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> {
isa.emit_unwind_info(self, unwind_info_kind) isa.emit_unwind_info(self, unwind_info_kind)
} }
} }
@ -537,17 +551,3 @@ pub trait TextSectionBuilder {
/// the bytes of the text section. /// the bytes of the text section.
fn finish(&mut self, ctrl_plane: &mut ControlPlane) -> Vec<u8>; fn finish(&mut self, ctrl_plane: &mut ControlPlane) -> Vec<u8>;
} }
/// Expected unwind info type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UnwindInfoKind {
/// No unwind info.
None,
/// SystemV CIE/FDE unwind info.
#[cfg(feature = "unwind")]
SystemV,
/// Windows X64 Unwind info
#[cfg(feature = "unwind")]
Windows,
}

32
cranelift/codegen/src/machinst/vcode.rs

@ -1191,16 +1191,13 @@ impl<I: VCodeInst> VCode<I> {
let loc = if let Some(preg) = alloc.as_reg() { let loc = if let Some(preg) = alloc.as_reg() {
LabelValueLoc::Reg(Reg::from(preg)) LabelValueLoc::Reg(Reg::from(preg))
} else { } else {
// We can't translate spillslot locations at the let slot = alloc.as_stack().unwrap();
// moment because ValueLabelLoc requires an let sp_offset = self.abi.get_spillslot_offset(slot);
// instantaneous SP offset, and this can *change* let sp_to_caller_sp_offset = self.abi.nominal_sp_to_caller_sp_offset();
// within the range we have here because of callsites let caller_sp_to_cfa_offset =
// adjusting SP temporarily. To avoid the complexity crate::isa::unwind::systemv::caller_sp_to_cfa_offset();
// of accurately plumbing through nominal-SP let cfa_to_sp_offset = -((sp_to_caller_sp_offset + caller_sp_to_cfa_offset) as i64);
// adjustment sites, we just omit debug info for LabelValueLoc::CFAOffset(cfa_to_sp_offset + sp_offset)
// values that are spilled. Not ideal, but debug info
// is best-effort.
continue;
}; };
// ValueLocRanges are recorded by *instruction-end // ValueLocRanges are recorded by *instruction-end
@ -1220,6 +1217,21 @@ impl<I: VCodeInst> VCode<I> {
// byte further to be sure to include it. // byte further to be sure to include it.
let end = to_offset + 1; let end = to_offset + 1;
// Coalesce adjacent ranges that for the same location
// to minimize output size here and for the consumers.
if let Some(last_loc_range) = ranges.last_mut() {
if last_loc_range.loc == loc && last_loc_range.end == start {
trace!(
"Extending debug range for VL{} in {:?} to {}",
label,
loc,
end
);
last_loc_range.end = end;
continue;
}
}
trace!( trace!(
"Recording debug range for VL{} in {:?}: [Inst {}..Inst {}) [{}..{})", "Recording debug range for VL{} in {:?}: [Inst {}..Inst {}) [{}..{})",
label, label,

6
cranelift/codegen/src/value_label.rs

@ -22,10 +22,10 @@ pub struct ValueLocRange {
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum LabelValueLoc { pub enum LabelValueLoc {
/// New-backend Reg. /// Register.
Reg(Reg), Reg(Reg),
/// New-backend offset from stack pointer. /// Offset from the Canonical Frame Address (aka CFA).
SPOffset(i64), CFAOffset(i64),
} }
/// Resulting map of Value labels and their ranges/locations. /// Resulting map of Value labels and their ranges/locations.

11
crates/cranelift-shared/src/compiled_function.rs

@ -1,7 +1,7 @@
use crate::{mach_reloc_to_reloc, mach_trap_to_trap, Relocation}; use crate::{mach_reloc_to_reloc, mach_trap_to_trap, Relocation};
use cranelift_codegen::{ use cranelift_codegen::{
ir, ir::UserExternalNameRef, isa::unwind::UnwindInfo, Final, MachBufferFinalized, MachSrcLoc, ir, ir::UserExternalNameRef, isa::unwind::CfaUnwindInfo, isa::unwind::UnwindInfo, Final,
ValueLabelsRanges, MachBufferFinalized, MachSrcLoc, ValueLabelsRanges,
}; };
use wasmtime_environ::{FilePos, InstructionAddressMap, TrapInformation}; use wasmtime_environ::{FilePos, InstructionAddressMap, TrapInformation};
@ -46,6 +46,8 @@ pub struct CompiledFunctionMetadata {
pub address_map: FunctionAddressMap, pub address_map: FunctionAddressMap,
/// The unwind information. /// The unwind information.
pub unwind_info: Option<UnwindInfo>, pub unwind_info: Option<UnwindInfo>,
/// CFA-based unwind information for DWARF debugging support.
pub cfa_unwind_info: Option<CfaUnwindInfo>,
/// Mapping of value labels and their locations. /// Mapping of value labels and their locations.
pub value_labels_ranges: ValueLabelsRanges, pub value_labels_ranges: ValueLabelsRanges,
/// Allocated stack slots. /// Allocated stack slots.
@ -154,6 +156,11 @@ where
self.metadata.unwind_info = Some(unwind); self.metadata.unwind_info = Some(unwind);
} }
/// Set the CFA-based unwind info in the function's metadata.
pub fn set_cfa_unwind_info(&mut self, unwind: CfaUnwindInfo) {
self.metadata.cfa_unwind_info = Some(unwind);
}
/// Set the sized stack slots. /// Set the sized stack slots.
pub fn set_sized_stack_slots(&mut self, slots: ir::StackSlots) { pub fn set_sized_stack_slots(&mut self, slots: ir::StackSlots) {
self.metadata.sized_stack_slots = slots; self.metadata.sized_stack_slots = slots;

33
crates/cranelift/src/compiler.rs

@ -6,7 +6,10 @@ use anyhow::{Context as _, Result};
use cranelift_codegen::ir::{ use cranelift_codegen::ir::{
self, InstBuilder, MemFlags, UserExternalName, UserExternalNameRef, UserFuncName, Value, self, InstBuilder, MemFlags, UserExternalName, UserExternalNameRef, UserFuncName, Value,
}; };
use cranelift_codegen::isa::{OwnedTargetIsa, TargetIsa}; use cranelift_codegen::isa::{
unwind::{UnwindInfo, UnwindInfoKind},
OwnedTargetIsa, TargetIsa,
};
use cranelift_codegen::print_errors::pretty_error; use cranelift_codegen::print_errors::pretty_error;
use cranelift_codegen::Context; use cranelift_codegen::Context;
use cranelift_codegen::{CompiledCode, MachStackMap}; use cranelift_codegen::{CompiledCode, MachStackMap};
@ -1052,13 +1055,6 @@ impl FunctionCompiler<'_> {
); );
} }
if body_and_tunables
.map(|(_, t)| t.generate_native_debuginfo)
.unwrap_or(false)
{
compiled_function.set_value_labels_ranges(compiled_code.value_labels_ranges.clone());
}
if isa.flags().unwind_info() { if isa.flags().unwind_info() {
let unwind = compiled_code let unwind = compiled_code
.create_unwind_info(isa) .create_unwind_info(isa)
@ -1069,6 +1065,27 @@ impl FunctionCompiler<'_> {
} }
} }
if body_and_tunables
.map(|(_, t)| t.generate_native_debuginfo)
.unwrap_or(false)
{
compiled_function.set_value_labels_ranges(compiled_code.value_labels_ranges.clone());
// DWARF debugging needs the CFA-based unwind information even on Windows.
if !matches!(
compiled_function.metadata().unwind_info,
Some(UnwindInfo::SystemV(_))
) {
let cfa_unwind = compiled_code
.create_unwind_info_of_kind(isa, UnwindInfoKind::SystemV)
.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?;
if let Some(UnwindInfo::SystemV(cfa_unwind_info)) = cfa_unwind {
compiled_function.set_cfa_unwind_info(cfa_unwind_info);
}
}
}
let stack_maps = mach_stack_maps_to_stack_maps(compiled_code.buffer.stack_maps()); let stack_maps = mach_stack_maps_to_stack_maps(compiled_code.buffer.stack_maps());
compiled_function compiled_function
.set_sized_stack_slots(std::mem::take(&mut context.func.sized_stack_slots)); .set_sized_stack_slots(std::mem::take(&mut context.func.sized_stack_slots));

10
crates/cranelift/src/debug/transform/attr.rs

@ -82,9 +82,6 @@ where
AttributeValue::RangeListsRef(_) if attr.name() == gimli::DW_AT_ranges => { AttributeValue::RangeListsRef(_) if attr.name() == gimli::DW_AT_ranges => {
continue; continue;
} }
AttributeValue::Exprloc(_) if attr.name() == gimli::DW_AT_frame_base => {
continue;
}
AttributeValue::DebugAddrBase(_) | AttributeValue::DebugStrOffsetsBase(_) => { AttributeValue::DebugAddrBase(_) | AttributeValue::DebugStrOffsetsBase(_) => {
continue; continue;
} }
@ -203,6 +200,13 @@ where
let list_id = out_unit.locations.add(write::LocationList(result.unwrap())); let list_id = out_unit.locations.add(write::LocationList(result.unwrap()));
write::AttributeValue::LocationListRef(list_id) write::AttributeValue::LocationListRef(list_id)
} }
AttributeValue::Exprloc(_) if attr.name() == gimli::DW_AT_frame_base => {
// We do not really "rewrite" the frame base so much as replace it outright.
// References to it through the DW_OP_fbreg opcode will be expanded below.
let mut cfa = write::Expression::new();
cfa.op(gimli::DW_OP_call_frame_cfa);
write::AttributeValue::Exprloc(cfa)
}
AttributeValue::Exprloc(ref expr) => { AttributeValue::Exprloc(ref expr) => {
let frame_base = let frame_base =
if let FileAttributeContext::Children { frame_base, .. } = file_context { if let FileAttributeContext::Children { frame_base, .. } = file_context {

18
crates/cranelift/src/debug/transform/expression.rs

@ -6,7 +6,7 @@ use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::LabelValueLoc; use cranelift_codegen::LabelValueLoc;
use cranelift_codegen::ValueLabelsRanges; use cranelift_codegen::ValueLabelsRanges;
use cranelift_wasm::get_vmctx_value_label; use cranelift_wasm::get_vmctx_value_label;
use gimli::{self, write, Expression, Operation, Reader, ReaderOffset, X86_64}; use gimli::{self, write, Expression, Operation, Reader, ReaderOffset};
use std::cmp::PartialEq; use std::cmp::PartialEq;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
@ -146,9 +146,9 @@ fn translate_loc(
} }
Some(writer.into_vec()) Some(writer.into_vec())
} }
LabelValueLoc::SPOffset(off) => { LabelValueLoc::CFAOffset(off) => {
let mut writer = ExpressionWriter::new(); let mut writer = ExpressionWriter::new();
writer.write_op_breg(X86_64::RSP.0)?; writer.write_op(gimli::constants::DW_OP_fbreg)?;
writer.write_sleb128(off)?; writer.write_sleb128(off)?;
if !add_stack_value { if !add_stack_value {
writer.write_op(gimli::constants::DW_OP_deref)?; writer.write_op(gimli::constants::DW_OP_deref)?;
@ -178,8 +178,8 @@ fn append_memory_deref(
}; };
writer.write_sleb128(memory_offset)?; writer.write_sleb128(memory_offset)?;
} }
LabelValueLoc::SPOffset(off) => { LabelValueLoc::CFAOffset(off) => {
writer.write_op_breg(X86_64::RSP.0)?; writer.write_op(gimli::constants::DW_OP_fbreg)?;
writer.write_sleb128(off)?; writer.write_sleb128(off)?;
writer.write_op(gimli::constants::DW_OP_deref)?; writer.write_op(gimli::constants::DW_OP_deref)?;
writer.write_op(gimli::constants::DW_OP_consts)?; writer.write_op(gimli::constants::DW_OP_consts)?;
@ -1169,7 +1169,7 @@ mod tests {
value_ranges.insert( value_ranges.insert(
value_0, value_0,
vec![ValueLocRange { vec![ValueLocRange {
loc: LabelValueLoc::SPOffset(0), loc: LabelValueLoc::CFAOffset(0),
start: 0, start: 0,
end: 25, end: 25,
}], }],
@ -1177,7 +1177,7 @@ mod tests {
value_ranges.insert( value_ranges.insert(
value_1, value_1,
vec![ValueLocRange { vec![ValueLocRange {
loc: LabelValueLoc::SPOffset(0), loc: LabelValueLoc::CFAOffset(0),
start: 5, start: 5,
end: 30, end: 30,
}], }],
@ -1186,12 +1186,12 @@ mod tests {
value_2, value_2,
vec![ vec![
ValueLocRange { ValueLocRange {
loc: LabelValueLoc::SPOffset(0), loc: LabelValueLoc::CFAOffset(0),
start: 0, start: 0,
end: 10, end: 10,
}, },
ValueLocRange { ValueLocRange {
loc: LabelValueLoc::SPOffset(0), loc: LabelValueLoc::CFAOffset(0),
start: 20, start: 20,
end: 30, end: 30,
}, },

16
crates/cranelift/src/debug/write_debuginfo.rs

@ -2,7 +2,10 @@ pub use crate::debug::transform::transform_dwarf;
use crate::debug::ModuleMemoryOffset; use crate::debug::ModuleMemoryOffset;
use crate::CompiledFunctionsMetadata; use crate::CompiledFunctionsMetadata;
use cranelift_codegen::ir::Endianness; use cranelift_codegen::ir::Endianness;
use cranelift_codegen::isa::{unwind::UnwindInfo, TargetIsa}; use cranelift_codegen::isa::{
unwind::{CfaUnwindInfo, UnwindInfo},
TargetIsa,
};
use cranelift_entity::EntityRef; use cranelift_entity::EntityRef;
use gimli::write::{Address, Dwarf, EndianVec, FrameTable, Result, Sections, Writer}; use gimli::write::{Address, Dwarf, EndianVec, FrameTable, Result, Sections, Writer};
use gimli::{RunTimeEndian, SectionId}; use gimli::{RunTimeEndian, SectionId};
@ -146,7 +149,18 @@ fn create_frame_table<'a>(
let cie_id = table.add_cie(isa.create_systemv_cie()?); let cie_id = table.add_cie(isa.create_systemv_cie()?);
for (i, metadata) in funcs { for (i, metadata) in funcs {
// The CFA-based unwind info will either be natively present, or we
// have generated it and placed into the "cfa_unwind_info" auxiliary
// field. We shouldn't emit both, though, it'd be wasteful.
let mut unwind_info: Option<&CfaUnwindInfo> = None;
if let Some(UnwindInfo::SystemV(info)) = &metadata.unwind_info { if let Some(UnwindInfo::SystemV(info)) = &metadata.unwind_info {
debug_assert!(metadata.cfa_unwind_info.is_none());
unwind_info = Some(info);
} else if let Some(info) = &metadata.cfa_unwind_info {
unwind_info = Some(info);
}
if let Some(info) = unwind_info {
table.add_fde( table.add_fde(
cie_id, cie_id,
info.to_fde(Address::Symbol { info.to_fde(Address::Symbol {

49
tests/all/debug/lldb.rs

@ -201,3 +201,52 @@ check: exited with status
)?; )?;
Ok(()) Ok(())
} }
#[test]
#[ignore]
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
target_pointer_width = "64"
))]
pub fn test_spilled_frame_base_is_accessible() -> Result<()> {
let output = lldb_with_script(
&[
"--disable-cache",
"-g",
"--opt-level",
"0",
"tests/all/debug/testsuite/spilled_frame_base.wasm",
],
r#"b spilled_frame_base.c:8
r
fr v i
n
fr v i
n
fr v i
n
fr v i
n
fr v i
n
fr v i
c
"#,
)?;
// Check that if the frame base (shadow frame pointer) local
// is spilled, we can still read locals that reference it.
check_lldb_output(
&output,
r#"
check: i = 0
check: i = 1
check: i = 1
check: i = 1
check: i = 1
check: i = 1
check: exited with status
"#,
)?;
Ok(())
}

16
tests/all/debug/testsuite/spilled_frame_base.c

@ -0,0 +1,16 @@
// Originally built using WASI SDK 20.0, "clang spilled_frame_base.c -o spilled_frame_base.wasm -g -target wasm32-wasi"
void func_11(int x, int y, int z, int a, int b, int c, int d, int e, int f, int g, int h) { }
void func_12(int x, int y, int z, int a, int b, int c, int d, int e, int f, int g, int h, int i) { }
void func_13(int x, int y, int z, int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) { }
void func_14(int x, int y, int z, int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k) { }
int main() {
int i = 1;
func_11(55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65);
func_12(66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77);
func_13(78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90);
func_14(91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104);
return i + i;
}

BIN
tests/all/debug/testsuite/spilled_frame_base.wasm

Binary file not shown.
Loading…
Cancel
Save