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 let mut first = true;
110 for item in &program.items {
111 if matches!(item, Item::Comment(_)) {
112 continue;
113 }
114 let record = u32::try_from(item.word().saturating_mul(4))
115 .ok()
116 .and_then(|offset| records.get(&offset))
117 .copied();
118 let line = record.and_then(|n| debug.and_then(|d| d.lines.get(n)));
120 if let (
121 Some(d),
122 Some(SourceLine {
123 line,
124 file: Some(file),
125 ..
126 }),
127 ) = (debug, line)
128 {
129 match source {
130 Some((f, l)) if f == *file => {
131 if line.abs_diff(l) > 2 {
132 let _ = writeln!(out, "\n#line {line}");
133 }
134 }
135 _ => {
136 let path = d.files.get(usize::from(*file)).map_or("", String::as_str);
137 let _ = writeln!(out, "\n#line {line} \"{path}\"");
138 }
139 }
140 source = Some((*file, *line));
141 }
142 let mut prefix = String::new();
145 if options.numbering {
146 match item {
147 Item::Instruction(i) if !i.coissue && !matches!(i.opcode, Opcode::Nop | Opcode::Label) => {
148 let colon = if options.offsets { ' ' } else { ':' };
149 let _ = write!(prefix, "{slot:>4}{colon} ");
150 slot = slot.saturating_add(slots(i, false, &program).1);
151 }
152 Item::Instruction(i) => {
153 slot = slot.saturating_add(slots(i, first, &program).1);
154 prefix.push_str(" ");
155 }
156 _ => prefix.push_str(" "),
157 }
158 }
159 if matches!(item, Item::Instruction(_)) {
160 first = false;
161 }
162 if options.offsets {
163 let _ = write!(prefix, "0x{:08X}: ", item.word().saturating_mul(4));
164 }
165 let indent = match item {
167 Item::Instruction(i) if i.coissue => " + ",
168 _ => " ",
169 };
170 let line = match item {
171 Item::Comment(_) => continue,
172 Item::Declaration(d) => format!("{}{}", " ".repeat(depth), declaration(d, &program)),
174 Item::Definition(d) => format!(
175 "{}{}",
176 " ".repeat(depth),
177 definition(&d.literal, d.destination.register, &program, options)
178 ),
179 Item::Instruction(i) => {
180 if matches!(
181 i.opcode,
182 Opcode::Else | Opcode::EndIf | Opcode::EndLoop | Opcode::EndRep
183 ) {
184 depth = depth.saturating_sub(1);
185 }
186 let text = instruction(i, &program);
187 let line = format!("{}{text}", " ".repeat(depth));
188 if matches!(
189 i.opcode,
190 Opcode::If | Opcode::IfC | Opcode::Else | Opcode::Loop | Opcode::Rep
191 ) {
192 depth = depth.saturating_add(1);
193 }
194 line
195 }
196 };
197 let _ = write!(out, "{prefix}{indent}{}", line.trim_end());
198 if let Some(written) = record.and_then(|n| notes.get(n)).filter(|w| !w.is_empty()) {
199 let _ = write!(out, " // {}", written.join(", "));
200 }
201 out.push('\n');
202 }
203 out.push('\n');
204 footer(&mut out, &program);
205 Ok(out)
206}
207
208fn variables(debug: &DebugInfo) -> Vec<Vec<String>> {
213 let mut notes = vec![Vec::new(); debug.lines.len()];
214 for v in &debug.variables {
215 let name = match &v.scope {
216 Some(scope) if *scope == debug.entry => v.name.clone(),
217 Some(scope) => format!("{scope}::{}", v.name),
218 None => format!("::{}", v.name),
219 };
220 for l in &v.locations {
221 let components: Vec<String> = l.components.iter().flatten().map(u16::to_string).collect();
222 if let Some(n) = usize::try_from(l.instruction).ok().and_then(|n| notes.get_mut(n)) {
223 n.push(format!("{name}<{}>", components.join(",")));
224 }
225 }
226 }
227 notes
228}
229
230fn type_kind(kind: u16) -> &'static str {
233 match kind {
234 0 => "void",
235 1 => "bool",
236 2 => "int",
237 3 => "float",
238 4 => "string",
239 5 => "Texture",
240 6 => "Texture1D",
241 7 => "Texture2D",
242 8 => "Texture3D",
243 9 => "TextureCube",
244 10 => "sampler",
245 11 => "sampler1D",
246 12 => "sampler2D",
247 13 => "sampler3D",
248 14 => "samplerCUBE",
249 15 => "PixelShader",
250 16 => "VertexShader",
251 17 => "pixelfragment",
252 18 => "vertexfragment",
253 19 => "uint",
254 20 => "byte",
255 21 => "GeometryShader",
256 22 => "RasterizerState",
257 23 => "DepthStencilState",
258 24 => "BlendState",
259 25 => "Buffer",
260 28 => "Texture1DArray",
261 29 => "Texture2DArray",
262 30 => "RenderTargetView",
263 31 => "DepthStencilView",
264 32 => "Texture2DMS",
265 33 => "Texture2DMSArray",
266 34 => "TextureCubeArray",
267 35 => "HullShader",
268 36 => "DomainShader",
269 37 => "interface",
270 38 => "ComputeShader",
271 39 => "double",
272 52 => "min8float",
273 53 => "min10float",
274 54 => "min16float",
275 55 => "min12int",
276 56 => "min16int",
277 57 => "min16uint",
278 _ => "UNKNOWN",
279 }
280}
281
282fn type_name(ty: &Type) -> String {
285 let kind = type_kind(ty.kind);
286 match ty.class {
287 0 | 4 => kind.to_owned(),
288 1 => format!("{kind}{}", ty.columns),
289 2 => format!("row_major {kind}{}x{}", ty.rows, ty.columns),
290 3 => format!("{kind}{}x{}", ty.rows, ty.columns),
291 _ => String::new(),
292 }
293}
294
295fn bank(set: u16) -> &'static str {
297 match set {
298 0 => "b",
299 1 => "i",
300 3 => "s",
301 _ => "c",
302 }
303}
304
305fn header(out: &mut String, table: &ConstantTable) {
308 let _ = writeln!(out, "//\n// Generated by {}", table.creator);
309 let mut constants: Vec<&Constant> = table.constants.iter().collect();
310 if constants.is_empty() {
311 return;
312 }
313 constants.sort_by(|a, b| a.name.as_bytes().cmp(b.name.as_bytes()));
314 out.push_str("//\n// Parameters:\n//\n");
315 let mut parameters = constants.clone();
317 parameters.dedup_by(|a, b| a.name == b.name);
318 let parameters: Vec<(&Type, &str)> = parameters.iter().map(|c| (&c.ty, c.name.as_str())).collect();
319 list(out, 3, ¶meters);
320 out.push_str("//\n//\n// Registers:\n//\n");
321 constants.sort_by_key(|c| (c.register_set, c.register));
322 let width = constants.iter().map(|c| c.name.len()).max().unwrap_or(0).max(12);
324 let _ = writeln!(out, "// {:<width$} Reg Size", "Name");
325 let _ = writeln!(out, "// {} ----- ----", "-".repeat(width));
326 for c in &constants {
327 let register = format!("{}{}", bank(c.register_set), c.register);
328 let _ = writeln!(out, "// {:<width$} {register:<5} {:>4}", c.name, c.count);
329 }
330 out.push_str("//\n\n");
331}
332
333fn list(out: &mut String, indent: usize, items: &[(&Type, &str)]) {
345 let pad = " ".repeat(indent);
346 let mut previous: Option<&Type> = None;
347 for &(ty, name) in items {
348 if previous.is_some_and(|p| p.class == 5 || ty.class == 5) {
349 let _ = writeln!(out, "//{pad}");
350 }
351 previous = Some(ty);
352 let array = if ty.elements > 1 {
353 format!("[{}]", ty.elements)
354 } else {
355 String::new()
356 };
357 if ty.class == 5 {
358 let _ = write!(out, "//{pad}struct\n//{pad}{{\n");
359 let members: Vec<(&Type, &str)> = ty.members.iter().map(|m| (&m.ty, m.name.as_str())).collect();
360 list(out, indent.saturating_add(4), &members);
361 let _ = writeln!(out, "//\n//{pad}}} {name}{array};");
362 } else {
363 let _ = writeln!(out, "//{pad}{} {name}{array};", type_name(ty));
364 }
365 }
366}
367
368fn register(r: Register, program: &Program) -> String {
369 let n = r.number;
370 let sm3 = program.version.major == 3;
372 let indexed = |names: &[&str]| -> String {
373 usize::try_from(n)
374 .ok()
375 .and_then(|i| names.get(i))
376 .map_or_else(|| format!("?{n}"), |s| (*s).to_owned())
377 };
378 match r.kind {
379 RegisterType::Temp => format!("r{n}"),
380 RegisterType::Input => format!("v{n}"),
381 RegisterType::Const => format!("c{n}"),
382 RegisterType::Const2 => format!("c{}", n.saturating_add(2048)),
383 RegisterType::Const3 => format!("c{}", n.saturating_add(4096)),
384 RegisterType::Const4 => format!("c{}", n.saturating_add(6144)),
385 RegisterType::Address if program.version.stage == Stage::Pixel => format!("t{n}"),
386 RegisterType::Address => "a0".into(),
387 RegisterType::RasterizerOut => indexed(&["oPos", "oFog", "oPts"]),
388 RegisterType::AttributeOut => format!("oD{n}"),
389 RegisterType::Output if sm3 => format!("o{n}"),
390 RegisterType::Output => format!("oT{n}"),
391 RegisterType::ConstInt => format!("i{n}"),
392 RegisterType::ColorOut => format!("oC{n}"),
393 RegisterType::DepthOut => "oDepth".into(),
394 RegisterType::Sampler => format!("s{n}"),
395 RegisterType::ConstBool => format!("b{n}"),
396 RegisterType::Loop => "aL".into(),
397 RegisterType::Misc => indexed(&["vPos", "vFace"]),
398 RegisterType::Label => format!("l{n}"),
399 RegisterType::Predicate => format!("p{n}"),
400 RegisterType::Texture => format!("t{n}"),
401 }
402}
403
404fn lane(l: u8) -> char {
405 LANES.get(usize::from(l)).copied().unwrap_or('?')
406}
407
408fn swizzle(s: [u8; 4]) -> String {
410 match s {
411 [0, 1, 2, 3] => String::new(),
412 [a, b, c, d] if a == b && b == c && c == d => format!(".{}", lane(a)),
413 _ => format!(".{}", s.iter().map(|&l| lane(l)).collect::<String>()),
414 }
415}
416
417fn relative(r: Option<Relative>, program: &Program) -> String {
418 r.map_or(String::new(), |r| {
419 format!("[{}{}]", register(r.register, program), swizzle(r.swizzle))
420 })
421}
422
423fn source(s: &Source, program: &Program) -> String {
424 let name = format!(
425 "{}{}",
426 register(s.register, program),
427 relative(s.relative, program)
428 );
429 let body = match s.modifier {
430 Modifier::None | Modifier::Negate | Modifier::Not => name,
431 Modifier::Bias | Modifier::BiasNegate => format!("{name}_bias"),
432 Modifier::Sign | Modifier::SignNegate => format!("{name}_bx2"),
433 Modifier::Complement => format!("1-{name}"),
434 Modifier::X2 | Modifier::X2Negate => format!("{name}_x2"),
435 Modifier::DivideZ => format!("{name}_dz"),
436 Modifier::DivideW => format!("{name}_dw"),
437 Modifier::Abs | Modifier::AbsNegate => format!("{name}_abs"),
438 };
439 let sign = match s.modifier {
440 Modifier::Negate
441 | Modifier::BiasNegate
442 | Modifier::SignNegate
443 | Modifier::X2Negate
444 | Modifier::AbsNegate => "-",
445 Modifier::Not => "!",
446 _ => "",
447 };
448 let swizzled = if s.register.kind == RegisterType::Sampler {
449 String::new()
450 } else {
451 swizzle(s.swizzle)
452 };
453 format!("{sign}{body}{swizzled}")
454}
455
456fn destination(d: &Destination, program: &Program) -> String {
457 let mask = if d.mask == 15 {
458 String::new()
459 } else {
460 let lanes: String = (0..4u8).filter(|n| d.mask & (1 << n) != 0).map(lane).collect();
461 format!(".{lanes}")
462 };
463 format!(
464 "{}{}{mask}",
465 register(d.register, program),
466 relative(d.relative, program)
467 )
468}
469
470fn suffixes(d: Option<&Destination>) -> String {
472 let Some(d) = d else { return String::new() };
473 let mut s = match d.shift {
474 1 => "_x2",
475 2 => "_x4",
476 3 => "_x8",
477 -1 => "_d2",
478 -2 => "_d4",
479 -3 => "_d8",
480 _ => "",
481 }
482 .to_owned();
483 if d.saturate {
484 s.push_str("_sat");
485 }
486 if d.partial_precision {
487 s.push_str("_pp");
488 }
489 if d.centroid {
490 s.push_str("_centroid");
491 }
492 s
493}
494
495fn mnemonic(i: &Instruction, program: &Program) -> String {
496 let v = program.version;
497 let from14 = v.major > 1 || (v.stage == Stage::Pixel && v.minor == 4);
499 let comparison = i.comparison().map_or("", |c| match c {
500 Comparison::Greater => "_gt",
501 Comparison::Equal => "_eq",
502 Comparison::GreaterEqual => "_ge",
503 Comparison::Less => "_lt",
504 Comparison::NotEqual => "_ne",
505 Comparison::LessEqual => "_le",
506 });
507 match i.opcode {
508 Opcode::Tex if from14 => match i.control {
509 0 => "texld",
510 1 => "texldp",
511 _ => "texldb",
512 }
513 .to_owned(),
514 Opcode::TexCoord if from14 => "texcrd".into(),
515 Opcode::IfC => format!("if{comparison}"),
516 Opcode::BreakC => format!("break{comparison}"),
517 Opcode::SetP => format!("setp{comparison}"),
518 other => other.name().into(),
519 }
520}
521
522fn instruction(i: &Instruction, program: &Program) -> String {
523 if i.opcode == Opcode::BreakP {
524 return breakp(i, program);
525 }
526 let mut args = Vec::new();
527 if let Some(d) = &i.destination {
528 args.push(destination(d, program));
529 }
530 args.extend(i.sources.iter().map(|s| source(s, program)));
531 let predicate = i
532 .predicate
533 .as_ref()
534 .map_or(String::new(), |p| format!("({}) ", source(p, program)));
535 format!(
536 "{predicate}{}{} {}",
537 mnemonic(i, program),
538 suffixes(i.destination.as_ref()),
539 args.join(", ")
540 )
541}
542
543fn breakp(i: &Instruction, program: &Program) -> String {
549 let Some(p) = i.sources.first() else {
550 return "break".to_owned();
551 };
552 let bits = p.swizzle.iter().enumerate().fold(0u8, |bits, (lane, from)| {
553 let at = u32::try_from(lane).map_or(8, |l| l.saturating_mul(2));
554 bits | (from & 3).checked_shl(at).unwrap_or(0)
555 });
556 let shift = i8::try_from(p.modifier.code()).map_or(0, |m| if m >= 8 { m.wrapping_sub(16) } else { m });
558 let read = Destination {
559 word: p.word,
560 register: p.register,
561 mask: bits & 15,
562 saturate: bits & 16 != 0,
563 partial_precision: bits & 32 != 0,
564 centroid: bits & 64 != 0,
565 shift,
566 relative: None,
567 };
568 format!("break{} {}", suffixes(Some(&read)), destination(&read, program))
569}
570
571const USAGES: [&str; 14] = [
573 "position",
574 "blendweight",
575 "blendindices",
576 "normal",
577 "psize",
578 "texcoord",
579 "tangent",
580 "binormal",
581 "tessfactor",
582 "positiont",
583 "color",
584 "fog",
585 "depth",
586 "sample",
587];
588
589fn declaration(d: &crate::Declaration, program: &Program) -> String {
590 let what = match d.declared {
591 Declared::Sampler(TextureType::D2) => "_2d".to_owned(),
592 Declared::Sampler(TextureType::Cube) => "_cube".to_owned(),
593 Declared::Sampler(TextureType::Volume) => "_volume".to_owned(),
594 Declared::Semantic { usage: 0, index: 0 } if program.version.stage == Stage::Pixel => String::new(),
596 Declared::Semantic { usage, index } => {
597 let index = if index == 0 {
598 String::new()
599 } else {
600 index.to_string()
601 };
602 let usage = USAGES.get(usize::from(usage)).copied().unwrap_or("unknown");
603 format!("_{usage}{index}")
604 }
605 };
606 format!(
607 "dcl{what}{} {}",
608 suffixes(Some(&d.destination)),
609 destination(&d.destination, program)
610 )
611}
612
613fn definition(literal: &Literal, register_: Register, program: &Program, options: &Options) -> String {
614 let (op, values) = match literal {
615 Literal::Float(f) => (
616 "def",
617 f.iter()
618 .map(|&w| {
619 if options.hex_literals {
620 format!("0x{w:08x}")
621 } else {
622 general(f32::from_bits(w))
623 }
624 })
625 .collect::<Vec<_>>()
626 .join(", "),
627 ),
628 Literal::Integer(i) => (
629 "defi",
630 i.iter().map(ToString::to_string).collect::<Vec<_>>().join(", "),
631 ),
632 Literal::Bool(b) => ("defb", (if *b != 0 { "true" } else { "false" }).to_owned()),
633 };
634 format!("{op} {}, {values}", register(register_, program))
635}
636
637fn general(value: f32) -> String {
640 let value = f64::from(value);
641 if value == 0.0 {
642 return if value.is_sign_negative() { "-0" } else { "0" }.to_owned();
643 }
644 if !value.is_finite() {
645 return format!("{value}");
646 }
647 let scientific = format!("{value:.8e}");
648 let (mantissa, exponent) = scientific.split_once('e').unwrap_or((&scientific, "0"));
649 let exponent: i32 = exponent.parse().unwrap_or(0);
650 let trim = |text: &str| -> String {
651 if text.contains('.') {
652 text.trim_end_matches('0').trim_end_matches('.').to_owned()
653 } else {
654 text.to_owned()
655 }
656 };
657 if !(-4..9).contains(&exponent) {
658 let sign = if exponent < 0 { '-' } else { '+' };
659 return format!("{}e{sign}{:03}", trim(mantissa), exponent.unsigned_abs());
660 }
661 let decimals = usize::try_from(8i32.saturating_sub(exponent)).unwrap_or(0);
662 trim(&format!("{value:.decimals$}"))
663}
664
665fn slots(i: &Instruction, first: bool, program: &Program) -> (u32, u32) {
670 if (i.coissue && !first) || i.opcode == Opcode::Nop {
673 return (0, 0);
674 }
675 let v = program.version;
676 let pixel1 = v.stage == Stage::Pixel && v.major == 1;
677 let vertex1 = v.stage == Stage::Vertex && v.major == 1;
678 let (texture, arithmetic): (u32, u32) = match i.opcode {
679 Opcode::Tex => (1, 0),
680 Opcode::TexCoord
683 | Opcode::TexBem
684 | Opcode::TexBemL
685 | Opcode::TexReg2Ar
686 | Opcode::TexReg2Gb
687 | Opcode::TexReg2Rgb
688 | Opcode::TexM3x2Pad
689 | Opcode::TexM3x2Tex
690 | Opcode::TexM3x2Depth
691 | Opcode::TexM3x3Pad
692 | Opcode::TexM3x3Tex
693 | Opcode::TexM3x3Spec
694 | Opcode::TexM3x3VSpec
695 | Opcode::TexM3x3
696 | Opcode::TexDp3
697 | Opcode::TexDp3Tex => (1, 0),
698 Opcode::TexLdl => (2, 0),
699 Opcode::TexLdd => (1, 2),
700 Opcode::Exp | Opcode::Log if vertex1 => (0, 10),
702 Opcode::Frc if vertex1 => (0, 3),
703 Opcode::SinCos => (0, 8),
704 Opcode::M4x4 | Opcode::M3x4 => (0, 4),
705 Opcode::Nrm | Opcode::Pow | Opcode::M4x3 | Opcode::M3x3 | Opcode::Sgn => (0, 3),
706 Opcode::If
707 | Opcode::IfC
708 | Opcode::BreakC
709 | Opcode::BreakP
710 | Opcode::CallNz
711 | Opcode::Rep
712 | Opcode::Loop => (0, 3),
713 Opcode::EndRep
714 | Opcode::EndLoop
715 | Opcode::Call
716 | Opcode::M3x2
717 | Opcode::Crs
718 | Opcode::Dsx
719 | Opcode::Dsy
720 | Opcode::Dp2Add
721 | Opcode::Bem => (0, 2),
722 Opcode::Label | Opcode::Phase => (0, 0),
723 _ => (0, 1),
724 };
725 if pixel1 {
727 return (texture, arithmetic.min(1));
728 }
729 let predicate = u32::from(i.predicate.is_some());
731 (texture, arithmetic.saturating_add(predicate))
732}
733
734fn footer(out: &mut String, program: &Program) {
736 let (texture, arithmetic) = program
737 .instructions()
738 .enumerate()
739 .fold((0u32, 0u32), |(t, a), (n, i)| {
740 let (ti, ai) = slots(i, n == 0, program);
741 (t.saturating_add(ti), a.saturating_add(ai))
742 });
743 let total = texture.saturating_add(arithmetic);
744 let slots = if total == 1 { "slot" } else { "slots" };
745 let _ = write!(out, "// approximately {total} instruction {slots} used");
746 if texture > 0 {
747 let _ = write!(out, " ({texture} texture, {arithmetic} arithmetic)");
748 }
749 out.push('\n');
750}