1#![deny(
21 clippy::arithmetic_side_effects,
22 clippy::indexing_slicing,
23 clippy::cast_possible_truncation
24)]
25#![deny(missing_docs)]
26#![deny(clippy::disallowed_methods, clippy::disallowed_types)]
27pub mod bitstream;
28pub mod module;
29pub mod psv;
30pub mod rdat;
31pub mod reflection;
32
33pub use module::Module;
34
35#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
38#[error("DXIL at bit {bit}: {detail}")]
39pub struct Error {
40 pub bit: usize,
42 pub detail: String,
44}
45
46pub type Result<T> = std::result::Result<T, Error>;
48
49pub(crate) fn error(bit: usize, detail: impl Into<String>) -> Error {
50 Error {
51 bit,
52 detail: detail.into(),
53 }
54}
55
56#[derive(Clone, Copy, Debug)]
58pub struct Part<'a> {
59 pub program_version: u32,
61 pub dxil_version: u32,
63 pub bitcode: &'a [u8],
65}
66
67pub fn parts(container: &[u8]) -> Result<Vec<dxcontainer::Chunk<'_>>> {
69 dxcontainer::chunks(container).map_err(|e| error(e.offset.saturating_mul(8), e.detail))
70}
71
72pub fn program(data: &[u8]) -> Result<Part<'_>> {
74 let view = primase_foundations::bytes::View::new(data);
75 let word = |at: usize| -> Result<u32> {
76 view.array::<4>(at)
77 .map(u32::from_le_bytes)
78 .map_err(|e| error(at.saturating_mul(8), e.to_string()))
79 };
80 let program_version = word(0)?;
81 let dwords = word(4)? as usize;
82 if dwords.checked_mul(4) != Some(data.len()) {
83 return Err(error(32, "program size disagrees with the part"));
84 }
85 if view.slice(8, 4).map_err(|e| error(64, e.to_string()))? != b"DXIL" {
86 return Err(error(64, "program header magic"));
87 }
88 let dxil_version = word(12)?;
89 let offset = word(16)? as usize;
91 let size = word(20)? as usize;
92 let bitcode = offset
93 .checked_add(8)
94 .and_then(|start| view.slice(start, size).ok())
95 .ok_or_else(|| error(128, "bitcode outside the part"))?;
96 Ok(Part {
97 program_version,
98 dxil_version,
99 bitcode,
100 })
101}
102
103pub fn part(container: &[u8]) -> Result<Part<'_>> {
105 let chunks = parts(container)?;
106 let data = chunks
107 .iter()
108 .find(|c| &c.kind == b"DXIL")
109 .ok_or_else(|| error(0, "no DXIL part"))?
110 .data;
111 program(data)
112}
113
114pub fn module(chunks: &[dxcontainer::Chunk<'_>], kind: &[u8; 4]) -> Result<Option<Module>> {
116 let Some(c) = chunks.iter().find(|c| &c.kind == kind) else {
117 return Ok(None);
118 };
119 read_module(&program(c.data)?).map(Some)
120}
121
122fn read_module(part: &Part<'_>) -> Result<Module> {
123 let blocks = bitstream::read(part.bitcode, *b"BC\xc0\xde")?;
124 Module::read(&blocks)
125}
126
127pub fn operations(container: &[u8]) -> Result<(u32, std::collections::BTreeMap<String, usize>)> {
131 use module::Op;
132 let chunks = parts(container)?;
133 let part = program(
134 chunks
135 .iter()
136 .find(|c| &c.kind == b"DXIL")
137 .ok_or_else(|| error(0, "no DXIL part"))?
138 .data,
139 )?;
140 let module = read_module(&part)?;
141 let mut out = std::collections::BTreeMap::new();
142 for f in &module.functions {
143 for inst in &f.instructions {
144 let Op::Call { function, args, .. } = &inst.op else {
145 continue;
146 };
147 let Some(name) = module.name(*function).filter(|n| n.starts_with("dx.op.")) else {
148 continue;
149 };
150 let code = args.first().and_then(|&a| {
151 let value = match a.checked_sub(f.first) {
152 None => module.values.get(a),
153 Some(local) => f.locals.get(local),
154 };
155 match value {
156 Some(module::Value::Constant(module::Constant::Integer(i))) => Some(*i),
157 _ => None,
158 }
159 });
160 let key = match code {
161 Some(code) => format!("{name} (op {code})"),
162 None => name.to_owned(),
163 };
164 out.entry(key)
165 .and_modify(|n: &mut usize| *n = n.saturating_add(1))
166 .or_insert(1);
167 }
168 }
169 Ok((part.program_version >> 16, out))
170}