1use crate::opcodes::NAMES;
8use crate::{Result, error};
9
10fn low8(v: u32) -> u8 {
12 let [byte, ..] = v.to_le_bytes();
13 byte
14}
15
16fn low16(v: u32) -> u16 {
18 let [a, b, ..] = v.to_le_bytes();
19 u16::from_le_bytes([a, b])
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum Register {
25 Temp,
27 Input,
29 Output,
31 IndexableTemp,
33 Constant,
35 Immediate,
37 Texture,
39 Sampler,
41 Null,
43 Uav,
45 ImmediateBuffer,
47 PrimitiveId,
49 InputCoverage,
51 OutputDepth,
53 OutputDepthGreaterEqual,
55 OutputDepthLessEqual,
57 OutputCoverage,
59 OutputStencilRef,
61 GroupSharedMemory,
63 ThreadId,
65 ThreadGroupId,
67 ThreadIdInGroup,
69 ThreadIdInGroupFlattened,
71 OutputControlPointId,
73 ForkInstanceId,
75 JoinInstanceId,
77 InputControlPoint,
79 OutputControlPoint,
81 InputPatchConstant,
83 InputDomainPoint,
85 Stream,
87 GsInstanceId,
89}
90impl Register {
91 fn dimensions(self) -> u32 {
93 match self {
94 Register::Immediate
95 | Register::Null
96 | Register::PrimitiveId
97 | Register::InputCoverage
98 | Register::OutputDepth
99 | Register::OutputDepthGreaterEqual
100 | Register::OutputDepthLessEqual
101 | Register::OutputCoverage
102 | Register::OutputStencilRef
103 | Register::ThreadId
104 | Register::ThreadGroupId
105 | Register::ThreadIdInGroup
106 | Register::ThreadIdInGroupFlattened
107 | Register::OutputControlPointId
108 | Register::ForkInstanceId
109 | Register::JoinInstanceId
110 | Register::InputDomainPoint
111 | Register::GsInstanceId => 0,
112 Register::Constant
113 | Register::IndexableTemp
114 | Register::InputControlPoint
115 | Register::OutputControlPoint => 2,
116 _ => 1,
117 }
118 }
119 fn from_type(n: u32) -> Option<Self> {
120 Some(match n {
121 0 => Register::Temp,
122 1 => Register::Input,
123 2 => Register::Output,
124 3 => Register::IndexableTemp,
125 4 => Register::Immediate,
126 6 => Register::Sampler,
127 7 => Register::Texture,
128 8 => Register::Constant,
129 9 => Register::ImmediateBuffer,
130 11 => Register::PrimitiveId,
131 12 => Register::OutputDepth,
132 13 => Register::Null,
133 15 => Register::OutputCoverage,
134 16 => Register::Stream,
135 22 => Register::OutputControlPointId,
136 23 => Register::ForkInstanceId,
137 24 => Register::JoinInstanceId,
138 25 => Register::InputControlPoint,
139 26 => Register::OutputControlPoint,
140 27 => Register::InputPatchConstant,
141 28 => Register::InputDomainPoint,
142 37 => Register::GsInstanceId,
143 30 => Register::Uav,
144 31 => Register::GroupSharedMemory,
145 32 => Register::ThreadId,
146 33 => Register::ThreadGroupId,
147 34 => Register::ThreadIdInGroup,
148 36 => Register::ThreadIdInGroupFlattened,
149 35 => Register::InputCoverage,
150 38 => Register::OutputDepthGreaterEqual,
151 39 => Register::OutputDepthLessEqual,
152 41 => Register::OutputStencilRef,
153 _ => return None,
154 })
155 }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
160pub enum Selection {
161 Mask(u8),
163 Swizzle([u8; 4]),
165 Scalar(u8),
167 None,
169}
170
171#[derive(Clone, Debug)]
173pub struct Operand {
174 pub register: Register,
176 pub selection: Selection,
178 pub indices: Vec<Index>,
180 pub immediate: [u32; 4],
182 pub modifier: u8,
185 pub precision: u8,
188 pub non_uniform: bool,
190}
191
192#[derive(Clone, Debug)]
194pub struct Index {
195 pub base: u32,
197 pub relative: Option<Box<Operand>>,
199}
200
201#[derive(Clone, Debug)]
203pub struct Instruction {
204 pub word: usize,
206 pub opcode: u16,
208 pub control: u32,
211 pub extensions: Vec<u32>,
213 pub saturate: bool,
215 pub nonzero: bool,
217 pub operands: Vec<Operand>,
219}
220impl Instruction {
221 pub fn name(&self) -> &'static str {
223 opcode_name(self.opcode)
224 }
225 pub fn sample_offset(&self) -> Option<[i32; 3]> {
227 self.extension(1).map(|e| {
228 let field = |shift: u32| (((e >> shift) & 15) as i32) << 28 >> 28;
229 [field(9), field(13), field(17)]
230 })
231 }
232 pub fn resource_dimension(&self) -> Option<(u32, u32)> {
234 self.extension(2).map(|e| ((e >> 6) & 31, (e >> 11) & 0xf_ffff))
235 }
236 pub fn return_type(&self) -> Option<u32> {
238 self.extension(3).map(|e| (e >> 6) & 0xffff)
239 }
240 fn extension(&self, kind: u32) -> Option<u32> {
241 self.extensions.iter().copied().find(|e| e & 63 == kind)
242 }
243}
244
245pub fn opcode_name(opcode: u16) -> &'static str {
247 NAMES
248 .get(usize::from(opcode))
249 .copied()
250 .unwrap_or("unknown opcode")
251}
252
253pub type ReturnCodes = [u8; 4];
256
257#[derive(Clone, Debug)]
259pub struct Declaration {
260 pub word: usize,
262 pub opcode: u16,
264 pub control: u32,
266 pub kind: Declared,
268}
269
270#[derive(Clone, Debug)]
272pub enum Declared {
273 Temps(u32),
275 IndexableTemp {
277 register: u32,
279 size: u32,
281 components: u32,
283 },
284 GlobalFlags(u32),
288 ConstantBuffer {
290 slot: u32,
292 rows: u32,
294 dynamic: bool,
296 dimensions: usize,
298 },
299 Sampler {
301 slot: u32,
303 mode: u32,
305 },
306 Resource {
309 slot: u32,
311 dimension: u32,
313 samples: u32,
315 returns: ReturnCodes,
317 token: u32,
319 },
320 ResourceRaw {
322 slot: u32,
324 },
325 ResourceStructured {
327 slot: u32,
329 stride: u32,
331 },
332 UavTyped {
334 slot: u32,
336 dimension: u32,
338 returns: ReturnCodes,
340 token: u32,
342 control: u32,
344 },
345 UavRaw {
347 slot: u32,
349 control: u32,
351 },
352 UavStructured {
354 slot: u32,
356 stride: u32,
358 control: u32,
360 },
361 ThreadGroup([u32; 3]),
363 GroupSharedRaw {
365 register: u32,
367 bytes: u32,
369 },
370 GroupSharedStructured {
372 register: u32,
374 stride: u32,
376 count: u32,
378 },
379 Input {
382 operand: Operand,
384 interpolation: Option<u32>,
386 name: Option<u32>,
388 },
389 Output {
391 operand: Operand,
393 name: Option<u32>,
395 },
396 ImmediateBuffer(Vec<[u32; 4]>),
398 CustomData {
400 class: u32,
402 },
403 Other {
405 opcode: u16,
407 control: u32,
409 words: Vec<u32>,
411 },
412}
413
414#[derive(Clone, Copy, Debug, PartialEq, Eq)]
416pub enum ProgramType {
417 Pixel,
419 Vertex,
421 Geometry,
423 Hull,
425 Domain,
427 Compute,
429}
430impl ProgramType {
431 pub fn name(self) -> &'static str {
433 match self {
434 ProgramType::Pixel => "ps",
435 ProgramType::Vertex => "vs",
436 ProgramType::Geometry => "gs",
437 ProgramType::Hull => "hs",
438 ProgramType::Domain => "ds",
439 ProgramType::Compute => "cs",
440 }
441 }
442}
443
444#[derive(Clone, Debug)]
446pub struct Decoded {
447 pub kind: ProgramType,
449 pub major: u32,
451 pub minor: u32,
453 pub declarations: Vec<Declaration>,
455 pub instructions: Vec<Instruction>,
457}
458impl Decoded {
459 pub fn target(&self) -> String {
461 format!("{}_{}_{}", self.kind.name(), self.major, self.minor)
462 }
463}
464
465struct Cursor<'a> {
466 words: &'a [u32],
467 at: usize,
468 origin: usize,
469 kind: ProgramType,
471}
472impl Cursor<'_> {
473 fn position(&self) -> usize {
475 self.origin.saturating_add(self.at)
476 }
477 fn take(&mut self) -> Result<u32> {
478 let value = *self
479 .words
480 .get(self.at)
481 .ok_or_else(|| error(self.position(), "truncated instruction"))?;
482 self.at = self.at.saturating_add(1);
483 Ok(value)
484 }
485 fn rest(&mut self) -> Vec<u32> {
486 let rest = self.words.get(self.at..).unwrap_or_default().to_vec();
487 self.at = self.words.len();
488 rest
489 }
490 fn operand(&mut self, depth: usize) -> Result<Operand> {
491 let pos = self.position();
492 if depth > 2 {
493 return Err(error(pos, "nested relative operand exceeds bound"));
494 }
495 let t = self.take()?;
496 let components = t & 3;
497 let selection = match components {
498 0 => Selection::None,
499 1 => Selection::Scalar(0),
500 2 => match (t >> 2) & 3 {
501 0 => Selection::Mask(low8((t >> 4) & 15)),
502 1 => Selection::Swizzle([4, 6, 8, 10].map(|shift| low8((t >> shift) & 3))),
503 2 => Selection::Scalar(low8((t >> 4) & 3)),
504 _ => return Err(error(pos, "reserved component selection")),
505 },
506 _ => return Err(error(pos, "unsupported N-component operand")),
507 };
508 let kind = (t >> 12) & 255;
509 let register = Register::from_type(kind)
510 .ok_or_else(|| error(pos, format!("unsupported operand register type {kind}")))?;
511 let (mut modifier, mut precision, mut non_uniform) = (0, 0, false);
512 if t >> 31 != 0 {
513 let mut ext = self.take()?;
514 loop {
515 if ext & 63 != 1 {
517 return Err(error(pos, "unsupported extended operand (precision/modifier)"));
518 }
519 modifier = low8((ext >> 6) & 255);
520 precision = low8((ext >> 14) & 7);
521 non_uniform = ext & (1 << 17) != 0;
522 if ext >> 31 == 0 {
523 break;
524 }
525 ext = self.take()?;
526 }
527 if modifier > 3 {
528 return Err(error(pos, "unsupported extended operand (precision/modifier)"));
529 }
530 }
531 let dimensions = (t >> 20) & 3;
532 let expected = match (register, self.kind) {
533 (Register::Input, ProgramType::Geometry | ProgramType::Hull | ProgramType::Domain) => 2,
534 _ => register.dimensions(),
535 };
536 if dimensions != expected {
537 return Err(error(pos, "unexpected operand index dimensions"));
538 }
539 let mut indices = Vec::new();
540 for shift in [22, 25, 28].into_iter().take(dimensions as usize) {
542 let representation = (t >> shift) & 7;
543 if !matches!(representation, 0 | 2 | 3) {
544 return Err(error(pos, "unsupported 64-bit/reserved index"));
545 }
546 let base = if representation != 2 { self.take()? } else { 0 };
547 let relative = if representation != 0 {
548 Some(Box::new(self.operand(depth.saturating_add(1))?))
549 } else {
550 None
551 };
552 indices.push(Index { base, relative });
553 }
554 let mut immediate = [0; 4];
555 if register == Register::Immediate {
556 match components {
557 1 => immediate = [self.take()?; 4],
558 2 => {
559 for x in &mut immediate {
560 *x = self.take()?;
561 }
562 }
563 _ => return Err(error(pos, "immediate requires one or four components")),
564 }
565 }
566 Ok(Operand {
567 register,
568 selection,
569 indices,
570 immediate,
571 modifier,
572 precision,
573 non_uniform,
574 })
575 }
576}
577
578fn return_codes(token: u32) -> ReturnCodes {
579 [0, 4, 8, 12].map(|shift| low8((token >> shift) & 15))
580}
581
582fn is_declaration(opcode: u16) -> bool {
585 matches!(opcode, 88..=106 | 113..=116 | 143..=162 | 206)
586}
587
588pub fn decode(words: &[u32]) -> Result<Decoded> {
590 let (Some(&version), Some(&length)) = (words.first(), words.get(1)) else {
591 return Err(error(0, "token stream shorter than its header"));
592 };
593 if usize::try_from(length).ok() != Some(words.len()) {
594 return Err(error(
595 1,
596 format!(
597 "the declared length is {length} words, the stream has {}",
598 words.len()
599 ),
600 ));
601 }
602 let kind = match version >> 16 {
603 0 => ProgramType::Pixel,
604 1 => ProgramType::Vertex,
605 2 => ProgramType::Geometry,
606 3 => ProgramType::Hull,
607 4 => ProgramType::Domain,
608 5 => ProgramType::Compute,
609 other => {
610 return Err(error(
611 0,
612 format!("program type {other} is not a Shader Model 4 or 5 stage"),
613 ));
614 }
615 };
616 let mut result = Decoded {
617 kind,
618 major: (version >> 4) & 15,
619 minor: version & 15,
620 declarations: Vec::new(),
621 instructions: Vec::new(),
622 };
623 let mut at = 2;
624 while let Some(&t) = words.get(at) {
625 let opcode = low16(t & 0x7ff);
626 if opcode == 53 {
628 let len = words
629 .get(at.saturating_add(1))
630 .and_then(|&n| usize::try_from(n).ok())
631 .unwrap_or(0);
632 let end = at.checked_add(len).filter(|&end| len >= 2 && end <= words.len());
633 let (Some(end), Some(body)) = (end, words.get(at.saturating_add(2)..at.saturating_add(len)))
634 else {
635 return Err(error(
636 at,
637 format!(
638 "customdata of {len} words (at least 2) does not fit the stream's {} words",
639 words.len()
640 ),
641 ));
642 };
643 let class = t >> 11;
644 let kind = if class == 3 {
645 let (rows, rest) = body.as_chunks::<4>();
646 if !rest.is_empty() {
647 return Err(error(
648 at,
649 format!(
650 "an immediate constant buffer of {} words, expected whole rows of 4",
651 body.len()
652 ),
653 ));
654 }
655 Declared::ImmediateBuffer(rows.to_vec())
656 } else {
657 Declared::CustomData { class }
658 };
659 result.declarations.push(Declaration {
660 word: at,
661 opcode,
662 control: class,
663 kind,
664 });
665 at = end;
666 continue;
667 }
668 let len = usize::try_from((t >> 24) & 127).unwrap_or(0);
669 if len == 0 {
670 return Err(error(at, "instruction length 0 (at least 1)"));
671 }
672 let end = at.checked_add(len).filter(|&end| end <= words.len());
673 let (Some(end), Some(body)) = (end, words.get(at.saturating_add(1)..at.saturating_add(len))) else {
674 return Err(error(
675 at,
676 format!(
677 "instruction length {len} (at least 1) does not fit the stream's {} words",
678 words.len()
679 ),
680 ));
681 };
682 let mut c = Cursor {
683 words: body,
684 at: 0,
685 origin: at.saturating_add(1),
686 kind,
687 };
688 let mut extensions = Vec::new();
689 let mut extended = t >> 31 != 0;
690 while extended {
691 let ext = c.take()?;
692 extensions.push(ext);
693 extended = ext >> 31 != 0;
694 }
695 let control = (t >> 11) & 0x1fff;
696 if is_declaration(opcode) {
697 let kind = declaration(opcode, control, &mut c)?;
698 if c.at != c.words.len() {
699 return Err(error(
700 at,
701 format!(
702 "{} words after the operands of {}",
703 c.words.len().saturating_sub(c.at),
704 opcode_name(opcode)
705 ),
706 ));
707 }
708 result.declarations.push(Declaration {
709 word: at,
710 opcode,
711 control,
712 kind,
713 });
714 } else {
715 let mut operands = Vec::new();
716 while c.at < c.words.len() {
717 operands.push(c.operand(0)?);
718 }
719 result.instructions.push(Instruction {
720 word: at,
721 opcode,
722 control,
723 extensions,
724 saturate: t & 0x2000 != 0,
725 nonzero: t & 0x4_0000 != 0,
726 operands,
727 });
728 }
729 at = end;
730 }
731 Ok(result)
732}
733
734fn declaration(opcode: u16, control: u32, c: &mut Cursor<'_>) -> Result<Declared> {
735 let pos = c.origin.saturating_sub(1);
736 let slot = |o: &Operand| -> Result<u32> {
737 o.indices
738 .first()
739 .map(|i| i.base)
740 .ok_or_else(|| error(pos, "missing declaration index"))
741 };
742 Ok(match opcode {
743 104 => Declared::Temps(c.take()?),
744 105 => Declared::IndexableTemp {
745 register: c.take()?,
746 size: c.take()?,
747 components: c.take()?,
748 },
749 106 => Declared::GlobalFlags(control),
750 88 => {
751 let o = c.operand(0)?;
752 let token = c.take()?;
753 Declared::Resource {
754 slot: slot(&o)?,
755 dimension: control & 31,
756 samples: (control >> 5) & 127,
757 returns: return_codes(token),
758 token,
759 }
760 }
761 89 => {
762 let o = c.operand(0)?;
763 Declared::ConstantBuffer {
764 slot: slot(&o)?,
765 rows: o.indices.get(1).map_or(0, |i| i.base),
766 dynamic: control & 1 != 0,
767 dimensions: o.indices.len(),
768 }
769 }
770 90 => {
771 let o = c.operand(0)?;
772 Declared::Sampler {
773 slot: slot(&o)?,
774 mode: control & 15,
775 }
776 }
777 95..=100 => {
778 let operand = c.operand(0)?;
779 let pixel = matches!(opcode, 98..=100);
780 let interpolation = pixel.then_some(control & 15);
781 let name = matches!(opcode, 96 | 97 | 99 | 100)
782 .then(|| c.take())
783 .transpose()?;
784 Declared::Input {
785 operand,
786 interpolation,
787 name,
788 }
789 }
790 101..=103 => {
791 let operand = c.operand(0)?;
792 let name = (opcode != 101).then(|| c.take()).transpose()?;
793 Declared::Output { operand, name }
794 }
795 156 => {
796 let o = c.operand(0)?;
797 let token = c.take()?;
798 Declared::UavTyped {
799 slot: slot(&o)?,
800 dimension: control & 31,
801 returns: return_codes(token),
802 token,
803 control,
804 }
805 }
806 157 => {
807 let o = c.operand(0)?;
808 Declared::UavRaw {
809 slot: slot(&o)?,
810 control,
811 }
812 }
813 158 => {
814 let o = c.operand(0)?;
815 Declared::UavStructured {
816 slot: slot(&o)?,
817 stride: c.take()?,
818 control,
819 }
820 }
821 155 => Declared::ThreadGroup([c.take()?, c.take()?, c.take()?]),
822 159 => {
823 let o = c.operand(0)?;
824 Declared::GroupSharedRaw {
825 register: slot(&o)?,
826 bytes: c.take()?,
827 }
828 }
829 160 => {
830 let o = c.operand(0)?;
831 Declared::GroupSharedStructured {
832 register: slot(&o)?,
833 stride: c.take()?,
834 count: c.take()?,
835 }
836 }
837 161 => {
838 let o = c.operand(0)?;
839 Declared::ResourceRaw { slot: slot(&o)? }
840 }
841 162 => {
842 let o = c.operand(0)?;
843 Declared::ResourceStructured {
844 slot: slot(&o)?,
845 stride: c.take()?,
846 }
847 }
848 _ => Declared::Other {
849 opcode,
850 control,
851 words: c.rest(),
852 },
853 })
854}