#![allow(clippy::unwrap_used, clippy::indexing_slicing)]
use dxil_reader::module::{DebugNode, Metadata, Module, Op, Value};
fn read(name: &str) -> (Module, String) {
let tests = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests");
let bytes = std::fs::read(tests.join("fixtures").join(format!("{name}.dxil"))).unwrap();
let chunks = dxil_reader::parts(&bytes).unwrap();
let m = dxil_reader::module(&chunks, b"ILDB").unwrap().unwrap();
let listing = std::fs::read_to_string(tests.join("disasm/listings").join(format!("{name}.txt"))).unwrap();
(m, listing)
}
fn instruction_lines(listing: &str) -> Vec<&str> {
let mut out = Vec::new();
let mut inside = false;
for line in listing.lines() {
if line.starts_with("define ") {
inside = true;
} else if line == "}" {
inside = false;
} else if inside && line.starts_with(" ") {
out.push(line);
}
}
out
}
fn field<'a>(line: &'a str, key: &str) -> Option<&'a str> {
let rest = &line[line.find(key)? + key.len()..];
Some(rest.split([' ', '"']).next().unwrap_or(rest))
}
fn string(m: &Module, id: Option<usize>) -> String {
match id.and_then(|id| m.metadata.get(id)) {
Some(Metadata::String(b)) => String::from_utf8_lossy(b).into_owned(),
_ => String::new(),
}
}
#[test]
fn locations_and_debug_intrinsics_are_the_listings() {
let mut located = 0;
let mut intrinsics = 0;
for name in [
"debug_info.vs",
"debug_info.ps",
"debug_info.ps.od",
"debug_info.gs",
"debug_info.hs",
"debug_info.ds",
"debug_info.cs",
"debug_info.cs.od",
"debug_info.lib",
] {
let (m, listing) = read(name);
let lines = instruction_lines(&listing);
let instructions: Vec<_> = m
.functions
.iter()
.flat_map(|f| f.instructions.iter().map(move |i| (f, i)))
.collect();
assert_eq!(instructions.len(), lines.len(), "{name}: instructions");
for ((f, inst), line) in instructions.into_iter().zip(lines) {
let callee = match &inst.op {
Op::Call { function, .. } => m.name(*function),
_ => None,
};
if let Some(var) = line.split(" ; var:\"").nth(1) {
let Op::Call { args, .. } = &inst.op else {
panic!("{name}: {line}")
};
let at = if callee == Some("llvm.dbg.value") { 2 } else { 1 };
let node = |a: usize| match &f.locals[args[a] - f.first] {
Value::Metadata(md) => &m.metadata[*md],
other => panic!("{name}: argument {a} is {other:?}"),
};
let Metadata::Debug {
node: DebugNode::LocalVariable { name: variable, .. },
..
} = node(at)
else {
panic!("{name}: {line}")
};
assert_eq!(
string(&m, *variable),
var.split('"').next().unwrap(),
"{name}: {line}"
);
assert!(
matches!(
node(at + 1),
Metadata::Debug {
node: DebugNode::Expression(_),
..
}
),
"{name}: {line}"
);
let Value::Metadata(value) = &f.locals[args[0] - f.first] else {
panic!("{name}: {line}")
};
let described = match value.checked_sub(f.first_metadata) {
Some(own) => &f.metadata[own],
None => &m.metadata[*value],
};
assert!(matches!(described, Metadata::Value { .. }), "{name}: {line}");
intrinsics += 1;
} else if let Some(l) = field(line, "; line:") {
let loc = inst
.debug_loc
.unwrap_or_else(|| panic!("{name}: no location for {line}"));
assert_eq!(loc.line.to_string(), l, "{name}: {line}");
assert_eq!(
loc.column.to_string(),
field(line, " col:").unwrap(),
"{name}: {line}"
);
located += 1;
} else if !line.contains("!dbg") {
assert!(
inst.debug_loc.is_none_or(|l| l.scope.is_none() || l.line == 0),
"{name}: {line}"
);
}
}
}
assert_eq!((located, intrinsics), (573, 261));
}