Browse Source

Add some support for imported memories to generated DWARF (#8740)

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.
pull/8693/head
Alex Crichton 5 months ago
committed by GitHub
parent
commit
fcf1054b0c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      crates/cranelift/src/compiler.rs
  2. 11
      crates/cranelift/src/debug.rs
  3. 110
      crates/cranelift/src/debug/transform/expression.rs
  4. 2
      crates/cranelift/src/debug/transform/utils.rs
  5. 5
      crates/test-programs/artifacts/build.rs
  6. 4
      crates/test-programs/build.rs
  7. 1
      crates/test-programs/src/bin/dwarf_imported_memory.rs
  8. 6
      crates/test-programs/src/bin/dwarf_simple.rs
  9. 86
      tests/all/debug/lldb.rs
  10. 3
      tests/all/debug/satisfy_memory_import.wat

6
crates/cranelift/src/compiler.rs

@ -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

11
crates/cranelift/src/debug.rs

@ -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};

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

@ -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 {
@ -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 {
@ -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)]
@ -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)?;

2
crates/cranelift/src/debug/transform/utils.rs

@ -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 => (),

5
crates/test-programs/artifacts/build.rs

@ -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:?}");
@ -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
@ -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() {

4
crates/test-programs/build.rs

@ -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
crates/test-programs/src/bin/dwarf_imported_memory.rs

@ -0,0 +1 @@
include!("./dwarf_simple.rs");

6
crates/test-programs/src/bin/dwarf_simple.rs

@ -0,0 +1,6 @@
fn main() {
let mut a = 100;
a += 10;
let b = a + 7;
println!("{b}");
}

86
tests/all/debug/lldb.rs

@ -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<()> {
@ -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
"#,
)?;
@ -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
tests/all/debug/satisfy_memory_import.wat

@ -0,0 +1,3 @@
(module
(memory (export "memory") 100)
)
Loading…
Cancel
Save