1use std::collections::BTreeMap;
4use std::fmt::Write;
5
6use crate::{
7 Comparison, Constant, ConstantTable, DebugInfo, Declared, Destination, Error, Instruction, Item, Literal,
8 Modifier, Opcode, Program, Register, RegisterType, Relative, Result, Source, SourceLine, Stage,
9 TextureType, Type, read,
10};
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14#[non_exhaustive]
15pub struct Options {
16 pub header: bool,
19 pub numbering: bool,
23 pub offsets: bool,
26 pub hex_literals: bool,
28 pub debug_info: bool,
31}
32
33impl Default for Options {
34 fn default() -> Self {
35 Self {
36 header: true,
37 numbering: false,
38 offsets: false,
39 hex_literals: false,
40 debug_info: true,
41 }
42 }
43}
44
45impl Options {
46 pub fn from_flags(flags: u32) -> Result<Self> {
49 let refuse = |detail: String| Error { word: 0, detail };
50 if flags & 0x01 != 0 {
51 return Err(refuse(
52 "flag 0x01, D3D_DISASM_ENABLE_COLOR_CODE (an HTML listing), is not written".into(),
53 ));
54 }
55 if flags & 0x08 != 0 {
56 return Err(refuse(
57 "flag 0x08, D3D_DISASM_ENABLE_INSTRUCTION_CYCLE, which Microsoft's disassembler refuses too, is not \
58 written"
59 .into(),
60 ));
61 }
62 let unknown = flags & !0xf6;
63 if unknown != 0 {
64 return Err(refuse(format!(
65 "flag bit 0x{:x} is not a D3D_DISASM flag",
66 1u32 << unknown.trailing_zeros()
67 )));
68 }
69 Ok(Self {
70 header: flags & 0x40 == 0,
71 numbering: flags & 0x04 != 0,
72 offsets: flags & 0x20 != 0,
73 hex_literals: flags & 0x80 != 0,
74 debug_info: flags & 0x10 == 0,
75 })
76 }
77}
78
79const LANES: [char; 4] = ['x', 'y', 'z', 'w'];
80
81pub fn disassemble(bytes: &[u8]) -> Result<String> {
85 disassemble_with(bytes, &Options::default())
86}
87
88pub fn disassemble_with(bytes: &[u8], options: &Options) -> Result<String> {
90 let program = read(bytes)?;
91 let mut out = String::new();
92 if let Some(table) = program.constants.as_ref().filter(|_| options.header) {
94 header(&mut out, table);
95 }
96 let _ = writeln!(out, " {}", program.version);
97 let debug = program.debug.as_ref().filter(|_| options.debug_info);
98 let notes = debug.map(variables).unwrap_or_default();
99 let records: BTreeMap<u32, usize> = debug
101 .map(|d| d.lines.iter().enumerate().map(|(n, l)| (l.offset, n)).collect())
102 .unwrap_or_default();
103 let mut source: Option<(u16, u16)> = None;
105 let mut depth: usize = 0;
106 let mut slot: u32 = 0;
108 for item in &program.items {
109 if matches!(item, Item::Comment(_)) {
110 continue;
111 }
112 let record = u32::try_from(item.word().saturating_mul(4))
113 .ok()
114 .and_then(|offset| records.get(&offset))
115 .copied();
116 let line = record.and_then(|n| debug.and_then(|d| d.lines.get(n)));
118 if let (
119 Some(d),
120 Some(SourceLine {
121 line,
122 file: Some(file),
123 ..
124 }),
125 ) = (debug, line)
126 {
127 match source {
128 Some((f, l)) if f == *file => {
129 if line.abs_diff(l) > 2 {
130 let _ = writeln!(out, "\n#line {line}");
131 }
132 }
133 _ => {
134 let path = d.files.get(usize::from(*file)).map_or("", String::as_str);
135 let _ = writeln!(out, "\n#line {line} \"{path}\"");
136 }
137 }
138 source = Some((*file, *line));
139 }
140 let mut prefix = String::new();
143 if options.numbering {
144 match item {
145 Item::Instruction(i) if !i.coissue && !matches!(i.opcode, Opcode::Nop | Opcode::Label) => {
146 let colon = if options.offsets { ' ' } else { ':' };
147 let _ = write!(prefix, "{slot:>4}{colon} ");
148 slot = slot.saturating_add(slots(i, &program).1);
149 }
150 _ => prefix.push_str(" "),
151 }
152 }
153 if options.offsets {
154 let _ = write!(prefix, "0x{:08X}: ", item.word().saturating_mul(4));
155 }
156 let indent = match item {
158 Item::Instruction(i) if i.coissue => " + ",
159 _ => " ",
160 };
161 let line = match item {
162 Item::Comment(_) => continue,
163 Item::Declaration(d) => declaration(d, &program),
164 Item::Definition(d) => definition(&d.literal, d.destination.register, &program, options),
165 Item::Instruction(i) => {
166 if matches!(
167 i.opcode,
168 Opcode::Else | Opcode::EndIf | Opcode::EndLoop | Opcode::EndRep
169 ) {
170 depth = depth.saturating_sub(1);
171 }
172 let text = instruction(i, &program);
173 let line = format!("{}{text}", " ".repeat(depth));
174 if matches!(
175 i.opcode,
176 Opcode::If | Opcode::IfC | Opcode::Else | Opcode::Loop | Opcode::Rep
177 ) {
178 depth = depth.saturating_add(1);
179 }
180 line
181 }
182 };
183 let _ = write!(out, "{prefix}{indent}{}", line.trim_end());
184 if let Some(written) = record.and_then(|n| notes.get(n)).filter(|w| !w.is_empty()) {
185 let _ = write!(out, " // {}", written.join(", "));
186 }
187 out.push('\n');
188 }
189 out.push('\n');
190 footer(&mut out, &program);
191 Ok(out)
192}
193
194fn variables(debug: &DebugInfo) -> Vec<Vec<String>> {
199 let mut notes = vec![Vec::new(); debug.lines.len()];
200 for v in &debug.variables {
201 let name = match &v.scope {
202 Some(scope) if *scope == debug.entry => v.name.clone(),
203 Some(scope) => format!("{scope}::{}", v.name),
204 None => format!("::{}", v.name),
205 };
206 for l in &v.locations {
207 let components: Vec<String> = l.components.iter().flatten().map(u16::to_string).collect();
208 if let Some(n) = usize::try_from(l.instruction).ok().and_then(|n| notes.get_mut(n)) {
209 n.push(format!("{name}<{}>", components.join(",")));
210 }
211 }
212 }
213 notes
214}
215
216fn type_kind(kind: u16) -> &'static str {
218 match kind {
219 0 => "void",
220 1 => "bool",
221 2 => "int",
222 3 => "float",
223 4 => "string",
224 5 => "texture",
225 6 => "texture1D",
226 7 => "texture2D",
227 8 => "texture3D",
228 9 => "textureCUBE",
229 10 => "sampler",
230 11 => "sampler1D",
231 12 => "sampler2D",
232 13 => "sampler3D",
233 14 => "samplerCUBE",
234 _ => "unknown",
235 }
236}
237
238fn type_name(ty: &Type) -> String {
241 let kind = type_kind(ty.kind);
242 match ty.class {
243 1 => format!("{kind}{}", ty.columns),
244 2 => format!("row_major {kind}{}x{}", ty.rows, ty.columns),
245 3 => format!("{kind}{}x{}", ty.rows, ty.columns),
246 _ => kind.to_owned(),
247 }
248}
249
250fn bank(set: u16) -> &'static str {
252 match set {
253 0 => "b",
254 1 => "i",
255 3 => "s",
256 _ => "c",
257 }
258}
259
260fn header(out: &mut String, table: &ConstantTable) {
263 let _ = writeln!(out, "//\n// Generated by {}", table.creator);
264 let mut constants: Vec<&Constant> = table.constants.iter().collect();
265 if constants.is_empty() {
266 return;
267 }
268 constants.sort_by(|a, b| a.name.as_bytes().cmp(b.name.as_bytes()));
269 out.push_str("//\n// Parameters:\n//\n");
270 let mut parameters = constants.clone();
272 parameters.dedup_by(|a, b| a.name == b.name);
273 for c in ¶meters {
274 parameter(out, 3, &c.ty, &c.name);
275 }
276 out.push_str("//\n//\n// Registers:\n//\n");
277 constants.sort_by_key(|c| (c.register_set, c.register));
278 let width = constants.iter().map(|c| c.name.len()).max().unwrap_or(0).max(12);
280 let _ = writeln!(out, "// {:<width$} Reg Size", "Name");
281 let _ = writeln!(out, "// {} ----- ----", "-".repeat(width));
282 for c in &constants {
283 let register = format!("{}{}", bank(c.register_set), c.register);
284 let _ = writeln!(out, "// {:<width$} {register:<5} {:>4}", c.name, c.count);
285 }
286 out.push_str("//\n\n");
287}
288
289fn parameter(out: &mut String, indent: usize, ty: &Type, name: &str) {
302 let pad = " ".repeat(indent);
303 let array = if ty.elements > 1 {
304 format!("[{}]", ty.elements)
305 } else {
306 String::new()
307 };
308 if ty.class == 5 {
309 let blank = format!("//{pad}\n");
310 if !out.ends_with(&blank) {
311 out.push_str(&blank);
312 }
313 let _ = write!(out, "//{pad}struct\n//{pad}{{\n");
314 for member in &ty.members {
315 parameter(out, indent.saturating_add(4), &member.ty, &member.name);
316 }
317 let _ = write!(out, "//\n//{pad}}} {name}{array};\n//{pad}\n");
318 } else {
319 let _ = writeln!(out, "//{pad}{} {name}{array};", type_name(ty));
320 }
321}
322
323fn register(r: Register, program: &Program) -> String {
324 let n = r.number;
325 let vertex3 = program.version.stage == Stage::Vertex && program.version.major == 3;
326 let indexed = |names: &[&str]| -> String {
327 usize::try_from(n)
328 .ok()
329 .and_then(|i| names.get(i))
330 .map_or_else(|| format!("?{n}"), |s| (*s).to_owned())
331 };
332 match r.kind {
333 RegisterType::Temp => format!("r{n}"),
334 RegisterType::Input => format!("v{n}"),
335 RegisterType::Const => format!("c{n}"),
336 RegisterType::Const2 => format!("c{}", n.saturating_add(2048)),
337 RegisterType::Const3 => format!("c{}", n.saturating_add(4096)),
338 RegisterType::Const4 => format!("c{}", n.saturating_add(6144)),
339 RegisterType::Address if program.version.stage == Stage::Pixel => format!("t{n}"),
340 RegisterType::Address => "a0".into(),
341 RegisterType::RasterizerOut => indexed(&["oPos", "oFog", "oPts"]),
342 RegisterType::AttributeOut => format!("oD{n}"),
343 RegisterType::Output if vertex3 => format!("o{n}"),
344 RegisterType::Output => format!("oT{n}"),
345 RegisterType::ConstInt => format!("i{n}"),
346 RegisterType::ColorOut => format!("oC{n}"),
347 RegisterType::DepthOut => "oDepth".into(),
348 RegisterType::Sampler => format!("s{n}"),
349 RegisterType::ConstBool => format!("b{n}"),
350 RegisterType::Loop => "aL".into(),
351 RegisterType::Misc => indexed(&["vPos", "vFace"]),
352 RegisterType::Label => format!("l{n}"),
353 RegisterType::Predicate => format!("p{n}"),
354 RegisterType::Texture => format!("t{n}"),
355 }
356}
357
358fn lane(l: u8) -> char {
359 LANES.get(usize::from(l)).copied().unwrap_or('?')
360}
361
362fn swizzle(s: [u8; 4]) -> String {
364 match s {
365 [0, 1, 2, 3] => String::new(),
366 [a, b, c, d] if a == b && b == c && c == d => format!(".{}", lane(a)),
367 _ => format!(".{}", s.iter().map(|&l| lane(l)).collect::<String>()),
368 }
369}
370
371fn relative(r: Option<Relative>, program: &Program) -> String {
372 r.map_or(String::new(), |r| {
373 format!("[{}{}]", register(r.register, program), swizzle(r.swizzle))
374 })
375}
376
377fn source(s: &Source, program: &Program) -> String {
378 let name = format!(
379 "{}{}",
380 register(s.register, program),
381 relative(s.relative, program)
382 );
383 let body = match s.modifier {
384 Modifier::None | Modifier::Negate | Modifier::Not => name,
385 Modifier::Bias | Modifier::BiasNegate => format!("{name}_bias"),
386 Modifier::Sign | Modifier::SignNegate => format!("{name}_bx2"),
387 Modifier::Complement => format!("1-{name}"),
388 Modifier::X2 | Modifier::X2Negate => format!("{name}_x2"),
389 Modifier::DivideZ => format!("{name}_dz"),
390 Modifier::DivideW => format!("{name}_dw"),
391 Modifier::Abs | Modifier::AbsNegate => format!("{name}_abs"),
392 };
393 let sign = match s.modifier {
394 Modifier::Negate
395 | Modifier::BiasNegate
396 | Modifier::SignNegate
397 | Modifier::X2Negate
398 | Modifier::AbsNegate => "-",
399 Modifier::Not => "!",
400 _ => "",
401 };
402 let swizzled = if s.register.kind == RegisterType::Sampler {
403 String::new()
404 } else {
405 swizzle(s.swizzle)
406 };
407 format!("{sign}{body}{swizzled}")
408}
409
410fn destination(d: &Destination, program: &Program) -> String {
411 let mask = if d.mask == 15 {
412 String::new()
413 } else {
414 let lanes: String = (0..4u8).filter(|n| d.mask & (1 << n) != 0).map(lane).collect();
415 format!(".{lanes}")
416 };
417 format!(
418 "{}{}{mask}",
419 register(d.register, program),
420 relative(d.relative, program)
421 )
422}
423
424fn suffixes(d: Option<&Destination>) -> String {
426 let Some(d) = d else { return String::new() };
427 let mut s = match d.shift {
428 1 => "_x2",
429 2 => "_x4",
430 3 => "_x8",
431 -1 => "_d2",
432 -2 => "_d4",
433 -3 => "_d8",
434 _ => "",
435 }
436 .to_owned();
437 if d.saturate {
438 s.push_str("_sat");
439 }
440 if d.partial_precision {
441 s.push_str("_pp");
442 }
443 if d.centroid {
444 s.push_str("_centroid");
445 }
446 s
447}
448
449fn mnemonic(i: &Instruction, program: &Program) -> String {
450 let v = program.version;
451 let from14 = v.stage == Stage::Pixel && (v.major > 1 || v.minor == 4);
452 let comparison = i.comparison().map_or("", |c| match c {
453 Comparison::Greater => "_gt",
454 Comparison::Equal => "_eq",
455 Comparison::GreaterEqual => "_ge",
456 Comparison::Less => "_lt",
457 Comparison::NotEqual => "_ne",
458 Comparison::LessEqual => "_le",
459 });
460 match i.opcode {
461 Opcode::Tex if from14 => match i.control {
462 0 => "texld",
463 1 => "texldp",
464 _ => "texldb",
465 }
466 .to_owned(),
467 Opcode::TexCoord if from14 => "texcrd".into(),
468 Opcode::IfC => format!("if{comparison}"),
469 Opcode::BreakC => format!("break{comparison}"),
470 Opcode::SetP => format!("setp{comparison}"),
471 other => other.name().into(),
472 }
473}
474
475fn instruction(i: &Instruction, program: &Program) -> String {
476 if i.opcode == Opcode::BreakP {
477 return breakp(i, program);
478 }
479 let mut args = Vec::new();
480 if let Some(d) = &i.destination {
481 args.push(destination(d, program));
482 }
483 args.extend(i.sources.iter().map(|s| source(s, program)));
484 let predicate = i
485 .predicate
486 .as_ref()
487 .map_or(String::new(), |p| format!("({}) ", source(p, program)));
488 format!(
489 "{predicate}{}{} {}",
490 mnemonic(i, program),
491 suffixes(i.destination.as_ref()),
492 args.join(", ")
493 )
494}
495
496fn breakp(i: &Instruction, program: &Program) -> String {
502 let Some(p) = i.sources.first() else {
503 return "break".to_owned();
504 };
505 let bits = p.swizzle.iter().enumerate().fold(0u8, |bits, (lane, from)| {
506 let at = u32::try_from(lane).map_or(8, |l| l.saturating_mul(2));
507 bits | (from & 3).checked_shl(at).unwrap_or(0)
508 });
509 let shift = i8::try_from(p.modifier.code()).map_or(0, |m| if m >= 8 { m.wrapping_sub(16) } else { m });
511 let read = Destination {
512 word: p.word,
513 register: p.register,
514 mask: bits & 15,
515 saturate: bits & 16 != 0,
516 partial_precision: bits & 32 != 0,
517 centroid: bits & 64 != 0,
518 shift,
519 relative: None,
520 };
521 format!("break{} {}", suffixes(Some(&read)), destination(&read, program))
522}
523
524const USAGES: [&str; 14] = [
526 "position",
527 "blendweight",
528 "blendindices",
529 "normal",
530 "psize",
531 "texcoord",
532 "tangent",
533 "binormal",
534 "tessfactor",
535 "positiont",
536 "color",
537 "fog",
538 "depth",
539 "sample",
540];
541
542fn declaration(d: &crate::Declaration, program: &Program) -> String {
543 let what = match d.declared {
544 Declared::Sampler(TextureType::D2) => "_2d".to_owned(),
545 Declared::Sampler(TextureType::Cube) => "_cube".to_owned(),
546 Declared::Sampler(TextureType::Volume) => "_volume".to_owned(),
547 Declared::Semantic { .. }
548 if matches!(
549 d.destination.register.kind,
550 RegisterType::Texture | RegisterType::Misc
551 ) || (program.version.stage == Stage::Pixel && program.version.major == 2) =>
552 {
553 String::new()
554 }
555 Declared::Semantic { usage, index } => {
556 let index = if index == 0 {
557 String::new()
558 } else {
559 index.to_string()
560 };
561 let usage = USAGES.get(usize::from(usage)).copied().unwrap_or("unknown");
562 format!("_{usage}{index}")
563 }
564 };
565 format!(
566 "dcl{what}{} {}",
567 suffixes(Some(&d.destination)),
568 destination(&d.destination, program)
569 )
570}
571
572fn definition(literal: &Literal, register_: Register, program: &Program, options: &Options) -> String {
573 let (op, values) = match literal {
574 Literal::Float(f) => (
575 "def",
576 f.iter()
577 .map(|&w| {
578 if options.hex_literals {
579 format!("0x{w:08x}")
580 } else {
581 general(f32::from_bits(w))
582 }
583 })
584 .collect::<Vec<_>>()
585 .join(", "),
586 ),
587 Literal::Integer(i) => (
588 "defi",
589 i.iter().map(ToString::to_string).collect::<Vec<_>>().join(", "),
590 ),
591 Literal::Bool(b) => ("defb", (if *b != 0 { "true" } else { "false" }).to_owned()),
592 };
593 format!("{op} {}, {values}", register(register_, program))
594}
595
596fn general(value: f32) -> String {
599 let value = f64::from(value);
600 if value == 0.0 {
601 return if value.is_sign_negative() { "-0" } else { "0" }.to_owned();
602 }
603 if !value.is_finite() {
604 return format!("{value}");
605 }
606 let scientific = format!("{value:.8e}");
607 let (mantissa, exponent) = scientific.split_once('e').unwrap_or((&scientific, "0"));
608 let exponent: i32 = exponent.parse().unwrap_or(0);
609 let trim = |text: &str| -> String {
610 if text.contains('.') {
611 text.trim_end_matches('0').trim_end_matches('.').to_owned()
612 } else {
613 text.to_owned()
614 }
615 };
616 if !(-4..9).contains(&exponent) {
617 let sign = if exponent < 0 { '-' } else { '+' };
618 return format!("{}e{sign}{:03}", trim(mantissa), exponent.unsigned_abs());
619 }
620 let decimals = usize::try_from(8i32.saturating_sub(exponent)).unwrap_or(0);
621 trim(&format!("{value:.decimals$}"))
622}
623
624fn slots(i: &Instruction, program: &Program) -> (u32, u32) {
629 if i.coissue || i.opcode == Opcode::Nop {
631 return (0, 0);
632 }
633 let v = program.version;
634 let pixel = v.stage == Stage::Pixel;
635 let pixel1 = pixel && v.major == 1;
636 let vertex1 = !pixel && v.major == 1;
637 let (texture, arithmetic): (u32, u32) = match i.opcode {
638 Opcode::Tex => (1, 0),
639 Opcode::TexCoord
642 | Opcode::TexBem
643 | Opcode::TexBemL
644 | Opcode::TexReg2Ar
645 | Opcode::TexReg2Gb
646 | Opcode::TexReg2Rgb
647 | Opcode::TexM3x2Pad
648 | Opcode::TexM3x2Tex
649 | Opcode::TexM3x2Depth
650 | Opcode::TexM3x3Pad
651 | Opcode::TexM3x3Tex
652 | Opcode::TexM3x3Spec
653 | Opcode::TexM3x3VSpec
654 | Opcode::TexM3x3
655 | Opcode::TexDp3
656 | Opcode::TexDp3Tex
657 if pixel1 =>
658 {
659 (1, 0)
660 }
661 Opcode::TexLdl => (2, 0),
662 Opcode::TexLdd => (1, 2),
663 Opcode::Exp | Opcode::Log if vertex1 => (0, 10),
665 Opcode::Frc if vertex1 => (0, 3),
666 Opcode::SinCos => (0, 8),
667 Opcode::M4x4 | Opcode::M3x4 => (0, 4),
668 Opcode::Nrm | Opcode::Pow | Opcode::M4x3 | Opcode::M3x3 | Opcode::Sgn => (0, 3),
669 Opcode::If
670 | Opcode::IfC
671 | Opcode::BreakC
672 | Opcode::BreakP
673 | Opcode::CallNz
674 | Opcode::Rep
675 | Opcode::Loop => (0, 3),
676 Opcode::EndRep
677 | Opcode::EndLoop
678 | Opcode::Call
679 | Opcode::M3x2
680 | Opcode::Crs
681 | Opcode::Dsx
682 | Opcode::Dsy => (0, 2),
683 Opcode::Dp2Add if pixel => (0, 2),
684 Opcode::Label | Opcode::Phase => (0, 0),
685 _ => (0, 1),
686 };
687 let predicate = u32::from(i.predicate.is_some());
689 (texture, arithmetic.saturating_add(predicate))
690}
691
692fn footer(out: &mut String, program: &Program) {
694 let (texture, arithmetic) = program.instructions().fold((0u32, 0u32), |(t, a), i| {
695 let (ti, ai) = slots(i, program);
696 (t.saturating_add(ti), a.saturating_add(ai))
697 });
698 let total = texture.saturating_add(arithmetic);
699 let slots = if total == 1 { "slot" } else { "slots" };
700 let _ = write!(out, "// approximately {total} instruction {slots} used");
701 if texture > 0 {
702 let _ = write!(out, " ({texture} texture, {arithmetic} arithmetic)");
703 }
704 out.push('\n');
705}