Skip to content

Commit

Permalink
Add some support for imported memories to generated DWARF (#8740)
Browse files Browse the repository at this point in the history
This is more-or-less a prerequisite for #8652 and extends the generated
dwarf with expressions to not only dereference owned memories but
additionally imported memories which involve some extra address
calculations to be emitted in the dwarf.
  • Loading branch information
alexcrichton authored Jun 5, 2024
1 parent ca405bb commit fcf1054
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 56 deletions.
6 changes: 5 additions & 1 deletion crates/cranelift/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,11 @@ impl wasmtime_environ::Compiler for Compiler {
);

let memory_offset = if ofs.num_imported_memories > 0 {
ModuleMemoryOffset::Imported(ofs.vmctx_vmmemory_import(MemoryIndex::new(0)))
ModuleMemoryOffset::Imported {
offset_to_vm_memory_definition: ofs.vmctx_vmmemory_import(MemoryIndex::new(0))
+ u32::from(ofs.vmmemory_import_from()),
offset_to_memory_base: ofs.ptr.vmmemory_definition_base().into(),
}
} else if ofs.num_defined_memories > 0 {
// The addition of shared memory makes the following assumption,
// "owned memory index = 0", possibly false. If the first memory
Expand Down
11 changes: 9 additions & 2 deletions crates/cranelift/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ pub enum ModuleMemoryOffset {
None,
/// Offset to the defined memory.
Defined(u32),
/// Offset to the imported memory.
Imported(#[allow(dead_code)] u32),
/// This memory is imported.
Imported {
/// Offset, in bytes, to the `*mut VMMemoryDefinition` structure within
/// `VMContext`.
offset_to_vm_memory_definition: u32,
/// Offset, in bytes within `VMMemoryDefinition` where the `base` field
/// lies.
offset_to_memory_base: u32,
},
}

pub use write_debuginfo::{emit_dwarf, DwarfSectionRelocTarget};
Expand Down
110 changes: 65 additions & 45 deletions crates/cranelift/src/debug/transform/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,25 @@ pub struct FunctionFrameInfo<'a> {
pub memory_offset: ModuleMemoryOffset,
}

impl<'a> FunctionFrameInfo<'a> {
fn vmctx_memory_offset(&self) -> Option<i64> {
match self.memory_offset {
ModuleMemoryOffset::Defined(x) => Some(x as i64),
ModuleMemoryOffset::Imported(_) => {
// TODO implement memory offset for imported memory
None
}
ModuleMemoryOffset::None => None,
}
}
}

struct ExpressionWriter(write::EndianVec<gimli::RunTimeEndian>);

enum VmctxBase {
Reg(u16),
OnStack,
}

impl ExpressionWriter {
pub fn new() -> Self {
fn new() -> Self {
let endian = gimli::RunTimeEndian::Little;
let writer = write::EndianVec::new(endian);
ExpressionWriter(writer)
}

pub fn write_op(&mut self, op: gimli::DwOp) -> write::Result<()> {
fn write_op(&mut self, op: gimli::DwOp) -> write::Result<()> {
self.write_u8(op.0 as u8)
}

pub fn write_op_reg(&mut self, reg: u16) -> write::Result<()> {
fn write_op_reg(&mut self, reg: u16) -> write::Result<()> {
if reg < 32 {
self.write_u8(gimli::constants::DW_OP_reg0.0 as u8 + reg as u8)
} else {
Expand All @@ -56,7 +48,7 @@ impl ExpressionWriter {
}
}

pub fn write_op_breg(&mut self, reg: u16) -> write::Result<()> {
fn write_op_breg(&mut self, reg: u16) -> write::Result<()> {
if reg < 32 {
self.write_u8(gimli::constants::DW_OP_breg0.0 as u8 + reg as u8)
} else {
Expand All @@ -65,25 +57,71 @@ impl ExpressionWriter {
}
}

pub fn write_u8(&mut self, b: u8) -> write::Result<()> {
fn write_u8(&mut self, b: u8) -> write::Result<()> {
write::Writer::write_u8(&mut self.0, b)
}

pub fn write_u32(&mut self, b: u32) -> write::Result<()> {
fn write_u32(&mut self, b: u32) -> write::Result<()> {
write::Writer::write_u32(&mut self.0, b)
}

pub fn write_uleb128(&mut self, i: u64) -> write::Result<()> {
fn write_uleb128(&mut self, i: u64) -> write::Result<()> {
write::Writer::write_uleb128(&mut self.0, i)
}

pub fn write_sleb128(&mut self, i: i64) -> write::Result<()> {
fn write_sleb128(&mut self, i: i64) -> write::Result<()> {
write::Writer::write_sleb128(&mut self.0, i)
}

pub fn into_vec(self) -> Vec<u8> {
fn into_vec(self) -> Vec<u8> {
self.0.into_vec()
}

fn gen_address_of_memory_base_pointer(
&mut self,
vmctx: VmctxBase,
memory_base: &ModuleMemoryOffset,
) -> write::Result<()> {
match *memory_base {
ModuleMemoryOffset::Defined(offset) => match vmctx {
VmctxBase::Reg(reg) => {
self.write_op_breg(reg)?;
self.write_sleb128(offset.into())?;
}
VmctxBase::OnStack => {
self.write_op(gimli::constants::DW_OP_consts)?;
self.write_sleb128(offset.into())?;
self.write_op(gimli::constants::DW_OP_plus)?;
}
},
ModuleMemoryOffset::Imported {
offset_to_vm_memory_definition,
offset_to_memory_base,
} => {
match vmctx {
VmctxBase::Reg(reg) => {
self.write_op_breg(reg)?;
self.write_sleb128(offset_to_vm_memory_definition.into())?;
}
VmctxBase::OnStack => {
if offset_to_vm_memory_definition > 0 {
self.write_op(gimli::constants::DW_OP_consts)?;
self.write_sleb128(offset_to_vm_memory_definition.into())?;
}
self.write_op(gimli::constants::DW_OP_plus)?;
}
}
self.write_op(gimli::constants::DW_OP_deref)?;
if offset_to_memory_base > 0 {
self.write_op(gimli::constants::DW_OP_consts)?;
self.write_sleb128(offset_to_memory_base.into())?;
self.write_op(gimli::constants::DW_OP_plus)?;
}
}
ModuleMemoryOffset::None => return Err(write::Error::InvalidAttributeValue),
}
Ok(())
}
}

#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -166,34 +204,16 @@ fn append_memory_deref(
isa: &dyn TargetIsa,
) -> Result<bool> {
let mut writer = ExpressionWriter::new();
// FIXME for imported memory
match vmctx_loc {
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)?;
}
let vmctx_base = match vmctx_loc {
LabelValueLoc::Reg(r) => VmctxBase::Reg(isa.map_regalloc_reg_to_dwarf(r)?),
LabelValueLoc::CFAOffset(off) => {
writer.write_op(gimli::constants::DW_OP_fbreg)?;
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)?;
VmctxBase::OnStack
}
}
};
writer.gen_address_of_memory_base_pointer(vmctx_base, &frame_info.memory_offset)?;
writer.write_op(gimli::constants::DW_OP_deref)?;
writer.write_op(gimli::constants::DW_OP_swap)?;
writer.write_op(gimli::constants::DW_OP_const4u)?;
Expand Down
2 changes: 1 addition & 1 deletion crates/cranelift/src/debug/transform/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub(crate) fn add_internal_types(
gimli::DW_AT_data_member_location = write::AttributeValue::Udata(memory_offset as u64)
});
}
ModuleMemoryOffset::Imported(_) => {
ModuleMemoryOffset::Imported { .. } => {
// TODO implement convenience pointer to and additional types for VMMemoryImport.
}
ModuleMemoryOffset::None => (),
Expand Down
5 changes: 3 additions & 2 deletions crates/test-programs/artifacts/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fn build_and_generate_tests() {
.arg("--target=wasm32-wasi")
.arg("--package=test-programs")
.env("CARGO_TARGET_DIR", &out_dir)
.env("CARGO_PROFILE_DEV_DEBUG", "1")
.env("CARGO_PROFILE_DEV_DEBUG", "2")
.env("RUSTFLAGS", rustflags())
.env_remove("CARGO_ENCODED_RUSTFLAGS");
eprintln!("running: {cmd:?}");
Expand Down Expand Up @@ -75,6 +75,7 @@ fn build_and_generate_tests() {
s if s.starts_with("api_") => "api",
s if s.starts_with("nn_") => "nn",
s if s.starts_with("piped_") => "piped",
s if s.starts_with("dwarf_") => "dwarf",
// If you're reading this because you hit this panic, either add it
// to a test suite above or add a new "suite". The purpose of the
// categorization above is to have a static assertion that tests
Expand All @@ -89,7 +90,7 @@ fn build_and_generate_tests() {
}

// Generate a component from each test.
if kind == "nn" {
if kind == "nn" || target == "dwarf_imported_memory" {
continue;
}
let adapter = match target.as_str() {
Expand Down
4 changes: 4 additions & 0 deletions crates/test-programs/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() {
println!("cargo:rustc-link-arg-bin=dwarf_imported_memory=--import-memory");
println!("cargo:rustc-link-arg-bin=dwarf_imported_memory=--export-memory");
}
1 change: 1 addition & 0 deletions crates/test-programs/src/bin/dwarf_imported_memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include!("./dwarf_simple.rs");
6 changes: 6 additions & 0 deletions crates/test-programs/src/bin/dwarf_simple.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
let mut a = 100;
a += 10;
let b = a + 7;
println!("{b}");
}
86 changes: 81 additions & 5 deletions tests/all/debug/lldb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,18 @@ fn lldb_with_script(args: &[&str], script: &str) -> Result<String> {
cmd.args(args);

let output = cmd.output().expect("success");
let stdout = String::from_utf8(output.stdout)?;
let stderr = String::from_utf8(output.stderr)?;
if !output.status.success() {
bail!(
"failed to execute {:?}: {}",
cmd,
String::from_utf8_lossy(&output.stderr),
"failed to execute {cmd:?}:\n\
--- stderr ---\n\
{stderr}\n\
--- stdout ---\n\
{stdout}",
);
}
Ok(String::from_utf8(output.stdout)?)
Ok(stdout)
}

fn check_lldb_output(output: &str, directives: &str) -> Result<()> {
Expand Down Expand Up @@ -166,7 +170,7 @@ check: Breakpoint 1: no locations (pending)
check: stop reason = breakpoint 1.1
check: frame #0
sameln: norm(n=(__ptr =
check: = 27
check: 27
check: resuming
"#,
)?;
Expand Down Expand Up @@ -295,3 +299,75 @@ check: exited with status = 0
)?;
Ok(())
}

#[cfg(all(
any(target_os = "linux", target_os = "macos"),
target_pointer_width = "64"
))]
mod test_programs {
use super::{check_lldb_output, lldb_with_script};
use anyhow::Result;
use test_programs_artifacts::*;

macro_rules! assert_test_exists {
($name:ident) => {
#[allow(unused_imports)]
use self::$name as _;
};
}
foreach_dwarf!(assert_test_exists);

fn test_dwarf_simple(wasm: &str, extra_args: &[&str]) -> Result<()> {
println!("testing {wasm:?}");
let mut args = vec!["-Ccache=n", "-Oopt-level=0", "-Ddebug-info"];
args.extend(extra_args);
args.push(wasm);
let output = lldb_with_script(
&args,
r#"
breakpoint set --file dwarf_simple.rs --line 3
breakpoint set --file dwarf_simple.rs --line 5
r
fr v
c
fr v
c"#,
)?;

check_lldb_output(
&output,
r#"
check: Breakpoint 1: no locations (pending)
check: Unable to resolve breakpoint to any actual locations.
check: 1 location added to breakpoint 1
check: stop reason = breakpoint 1.1
check: dwarf_simple.rs:3
check: a = 100
check: dwarf_simple.rs:5
check: a = 110
check: b = 117
check: resuming
check: exited with status = 0
"#,
)?;
Ok(())
}

#[test]
#[ignore]
fn dwarf_simple() -> Result<()> {
for wasm in [DWARF_SIMPLE] {
test_dwarf_simple(wasm, &[])?;
}
Ok(())
}

#[test]
#[ignore]
fn dwarf_imported_memory() -> Result<()> {
test_dwarf_simple(
DWARF_IMPORTED_MEMORY,
&["--preload=env=./tests/all/debug/satisfy_memory_import.wat"],
)
}
}
3 changes: 3 additions & 0 deletions tests/all/debug/satisfy_memory_import.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(module
(memory (export "memory") 100)
)

0 comments on commit fcf1054

Please sign in to comment.