1use crate::format::{OperandFormat, operand_format};
8use crate::opcodes::NAMES;
9use crate::{Result, error};
10
11fn low8(v: u32) -> u8 {
13 let [byte, ..] = v.to_le_bytes();
14 byte
15}
16
17fn low16(v: u32) -> u16 {
19 let [a, b, ..] = v.to_le_bytes();
20 u16::from_le_bytes([a, b])
21}
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum Register {
26 Temp,
28 Input,
30 Output,
32 IndexableTemp,
34 Constant,
36 Immediate,
38 Texture,
40 Sampler,
42 Null,
44 Uav,
46 ImmediateBuffer,
48 PrimitiveId,
50 InputCoverage,
52 OutputDepth,
54 OutputDepthGreaterEqual,
56 OutputDepthLessEqual,
58 OutputCoverage,
60 Rasterizer,
62 OutputStencilRef,
64 GroupSharedMemory,
66 ThreadId,
68 ThreadGroupId,
70 ThreadIdInGroup,
72 ThreadIdInGroupFlattened,
74 OutputControlPointId,
76 ForkInstanceId,
78 JoinInstanceId,
80 InputControlPoint,
82 OutputControlPoint,
84 InputPatchConstant,
86 InputDomainPoint,
88 Stream,
90 GsInstanceId,
92}
93impl Register {
94 fn dimensions(self) -> u32 {
96 match self {
97 Register::Immediate
98 | Register::Null
99 | Register::PrimitiveId
100 | Register::InputCoverage
101 | Register::OutputDepth
102 | Register::OutputDepthGreaterEqual
103 | Register::OutputDepthLessEqual
104 | Register::OutputCoverage
105 | Register::Rasterizer
106 | Register::OutputStencilRef
107 | Register::ThreadId
108 | Register::ThreadGroupId
109 | Register::ThreadIdInGroup
110 | Register::ThreadIdInGroupFlattened
111 | Register::OutputControlPointId
112 | Register::ForkInstanceId
113 | Register::JoinInstanceId
114 | Register::InputDomainPoint
115 | Register::GsInstanceId => 0,
116 Register::Constant
117 | Register::IndexableTemp
118 | Register::InputControlPoint
119 | Register::OutputControlPoint => 2,
120 _ => 1,
121 }
122 }
123 fn from_type(n: u32) -> Option<Self> {
124 Some(match n {
125 0 => Register::Temp,
126 1 => Register::Input,
127 2 => Register::Output,
128 3 => Register::IndexableTemp,
129 4 => Register::Immediate,
130 6 => Register::Sampler,
131 7 => Register::Texture,
132 8 => Register::Constant,
133 9 => Register::ImmediateBuffer,
134 11 => Register::PrimitiveId,
135 12 => Register::OutputDepth,
136 13 => Register::Null,
137 14 => Register::Rasterizer,
138 15 => Register::OutputCoverage,
139 16 => Register::Stream,
140 22 => Register::OutputControlPointId,
141 23 => Register::ForkInstanceId,
142 24 => Register::JoinInstanceId,
143 25 => Register::InputControlPoint,
144 26 => Register::OutputControlPoint,
145 27 => Register::InputPatchConstant,
146 28 => Register::InputDomainPoint,
147 37 => Register::GsInstanceId,
148 30 => Register::Uav,
149 31 => Register::GroupSharedMemory,
150 32 => Register::ThreadId,
151 33 => Register::ThreadGroupId,
152 34 => Register::ThreadIdInGroup,
153 36 => Register::ThreadIdInGroupFlattened,
154 35 => Register::InputCoverage,
155 38 => Register::OutputDepthGreaterEqual,
156 39 => Register::OutputDepthLessEqual,
157 41 => Register::OutputStencilRef,
158 _ => return None,
159 })
160 }
161}
162
163#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165pub enum Selection {
166 Mask(u8),
168 Swizzle([u8; 4]),
170 Scalar(u8),
172 None,
174}
175
176#[derive(Clone, Debug)]
178pub struct Operand {
179 pub register: Register,
181 pub selection: Selection,
183 pub indices: Vec<Index>,
185 pub immediate: [u32; 4],
187 pub modifier: u8,
190 pub precision: u8,
193 pub non_uniform: bool,
195}
196
197#[derive(Clone, Debug)]
199pub struct Index {
200 pub base: u32,
202 pub relative: Option<Box<Operand>>,
204}
205
206#[derive(Clone, Debug)]
208pub struct Instruction {
209 pub word: usize,
211 pub opcode: u16,
213 pub control: u32,
216 pub extensions: Vec<u32>,
218 pub saturate: bool,
220 pub nonzero: bool,
222 pub operands: Vec<Operand>,
224}
225impl Instruction {
226 pub fn name(&self) -> &'static str {
228 opcode_name(self.opcode)
229 }
230 pub fn sample_offset(&self) -> Option<[i32; 3]> {
232 self.extension(1).map(|e| {
233 let field = |shift: u32| (((e >> shift) & 15) as i32) << 28 >> 28;
234 [field(9), field(13), field(17)]
235 })
236 }
237 pub fn resource_dimension(&self) -> Option<(u32, u32)> {
239 self.extension(2).map(|e| ((e >> 6) & 31, (e >> 11) & 0xf_ffff))
240 }
241 pub fn return_type(&self) -> Option<u32> {
243 self.extension(3).map(|e| (e >> 6) & 0xffff)
244 }
245 fn extension(&self, kind: u32) -> Option<u32> {
246 self.extensions.iter().copied().find(|e| e & 63 == kind)
247 }
248 pub fn format(&self) -> Option<OperandFormat> {
250 operand_format(self.opcode)
251 }
252 pub fn destinations_and_sources(&self) -> Option<(&[Operand], &[Operand])> {
255 self.operands.split_at_checked(self.format()?.destinations.len())
256 }
257}
258
259pub fn opcode_name(opcode: u16) -> &'static str {
261 NAMES
262 .get(usize::from(opcode))
263 .copied()
264 .unwrap_or("unknown opcode")
265}
266
267pub type ReturnCodes = [u8; 4];
270
271#[derive(Clone, Debug)]
273pub struct Declaration {
274 pub word: usize,
276 pub opcode: u16,
278 pub control: u32,
280 pub kind: Declared,
282}
283
284#[derive(Clone, Debug)]
286pub enum Declared {
287 Temps(u32),
289 IndexableTemp {
291 register: u32,
293 size: u32,
295 components: u32,
297 },
298 GlobalFlags(u32),
302 ConstantBuffer {
304 slot: u32,
306 rows: u32,
308 dynamic: bool,
310 dimensions: usize,
312 },
313 Sampler {
315 slot: u32,
317 mode: u32,
319 },
320 Resource {
323 slot: u32,
325 dimension: u32,
327 samples: u32,
329 returns: ReturnCodes,
331 token: u32,
333 },
334 ResourceRaw {
336 slot: u32,
338 },
339 ResourceStructured {
341 slot: u32,
343 stride: u32,
345 },
346 UavTyped {
348 slot: u32,
350 dimension: u32,
352 returns: ReturnCodes,
354 token: u32,
356 control: u32,
358 },
359 UavRaw {
361 slot: u32,
363 control: u32,
365 },
366 UavStructured {
368 slot: u32,
370 stride: u32,
372 control: u32,
374 },
375 ThreadGroup([u32; 3]),
377 GroupSharedRaw {
379 register: u32,
381 bytes: u32,
383 },
384 GroupSharedStructured {
386 register: u32,
388 stride: u32,
390 count: u32,
392 },
393 Input {
396 operand: Operand,
398 interpolation: Option<u32>,
400 name: Option<u32>,
402 },
403 Output {
405 operand: Operand,
407 name: Option<u32>,
409 },
410 ImmediateBuffer(Vec<[u32; 4]>),
412 CustomData {
414 class: u32,
416 },
417 Other {
419 opcode: u16,
421 control: u32,
423 words: Vec<u32>,
425 },
426}
427
428#[derive(Clone, Copy, Debug, PartialEq, Eq)]
430pub enum ProgramType {
431 Pixel,
433 Vertex,
435 Geometry,
437 Hull,
439 Domain,
441 Compute,
443}
444impl ProgramType {
445 pub fn name(self) -> &'static str {
447 match self {
448 ProgramType::Pixel => "ps",
449 ProgramType::Vertex => "vs",
450 ProgramType::Geometry => "gs",
451 ProgramType::Hull => "hs",
452 ProgramType::Domain => "ds",
453 ProgramType::Compute => "cs",
454 }
455 }
456}
457
458#[derive(Clone, Debug)]
460pub struct Decoded {
461 pub kind: ProgramType,
463 pub major: u32,
465 pub minor: u32,
467 pub declarations: Vec<Declaration>,
469 pub instructions: Vec<Instruction>,
471}
472impl Decoded {
473 pub fn target(&self) -> String {
475 format!("{}_{}_{}", self.kind.name(), self.major, self.minor)
476 }
477}
478
479struct Cursor<'a> {
480 words: &'a [u32],
481 at: usize,
482 origin: usize,
483 kind: ProgramType,
485}
486impl Cursor<'_> {
487 fn position(&self) -> usize {
489 self.origin.saturating_add(self.at)
490 }
491 fn take(&mut self) -> Result<u32> {
492 let value = *self
493 .words
494 .get(self.at)
495 .ok_or_else(|| error(self.position(), "truncated instruction"))?;
496 self.at = self.at.saturating_add(1);
497 Ok(value)
498 }
499 fn rest(&mut self) -> Vec<u32> {
500 let rest = self.words.get(self.at..).unwrap_or_default().to_vec();
501 self.at = self.words.len();
502 rest
503 }
504 fn operand(&mut self, depth: usize) -> Result<Operand> {
505 let pos = self.position();
506 if depth > 2 {
507 return Err(error(pos, "nested relative operand exceeds bound"));
508 }
509 let t = self.take()?;
510 let components = t & 3;
511 let selection = match components {
512 0 => Selection::None,
513 1 => Selection::Scalar(0),
514 2 => match (t >> 2) & 3 {
515 0 => Selection::Mask(low8((t >> 4) & 15)),
516 1 => Selection::Swizzle([4, 6, 8, 10].map(|shift| low8((t >> shift) & 3))),
517 2 => Selection::Scalar(low8((t >> 4) & 3)),
518 _ => return Err(error(pos, "reserved component selection")),
519 },
520 _ => return Err(error(pos, "unsupported N-component operand")),
521 };
522 let kind = (t >> 12) & 255;
523 let register = Register::from_type(kind)
524 .ok_or_else(|| error(pos, format!("unsupported operand register type {kind}")))?;
525 let (mut modifier, mut precision, mut non_uniform) = (0, 0, false);
526 if t >> 31 != 0 {
527 let mut ext = self.take()?;
528 loop {
529 if ext & 63 != 1 {
531 return Err(error(pos, "unsupported extended operand (precision/modifier)"));
532 }
533 modifier = low8((ext >> 6) & 255);
534 precision = low8((ext >> 14) & 7);
535 non_uniform = ext & (1 << 17) != 0;
536 if ext >> 31 == 0 {
537 break;
538 }
539 ext = self.take()?;
540 }
541 if modifier > 3 {
542 return Err(error(pos, "unsupported extended operand (precision/modifier)"));
543 }
544 }
545 let dimensions = (t >> 20) & 3;
546 let expected = match (register, self.kind) {
547 (Register::Input, ProgramType::Geometry | ProgramType::Hull | ProgramType::Domain) => 2,
548 _ => register.dimensions(),
549 };
550 if dimensions != expected {
551 return Err(error(pos, "unexpected operand index dimensions"));
552 }
553 let mut indices = Vec::new();
554 for shift in [22, 25, 28].into_iter().take(dimensions as usize) {
556 let representation = (t >> shift) & 7;
557 if !matches!(representation, 0 | 2 | 3) {
558 return Err(error(pos, "unsupported 64-bit/reserved index"));
559 }
560 let base = if representation != 2 { self.take()? } else { 0 };
561 let relative = if representation != 0 {
562 Some(Box::new(self.operand(depth.saturating_add(1))?))
563 } else {
564 None
565 };
566 indices.push(Index { base, relative });
567 }
568 let mut immediate = [0; 4];
569 if register == Register::Immediate {
570 match components {
571 1 => immediate = [self.take()?; 4],
572 2 => {
573 for x in &mut immediate {
574 *x = self.take()?;
575 }
576 }
577 _ => return Err(error(pos, "immediate requires one or four components")),
578 }
579 }
580 Ok(Operand {
581 register,
582 selection,
583 indices,
584 immediate,
585 modifier,
586 precision,
587 non_uniform,
588 })
589 }
590}
591
592fn return_codes(token: u32) -> ReturnCodes {
593 [0, 4, 8, 12].map(|shift| low8((token >> shift) & 15))
594}
595
596fn is_declaration(opcode: u16) -> bool {
599 matches!(opcode, 88..=106 | 113..=116 | 143..=162 | 206)
600}
601
602pub fn decode(words: &[u32]) -> Result<Decoded> {
604 let (Some(&version), Some(&length)) = (words.first(), words.get(1)) else {
605 return Err(error(0, "token stream shorter than its header"));
606 };
607 if usize::try_from(length).ok() != Some(words.len()) {
608 return Err(error(
609 1,
610 format!(
611 "the declared length is {length} words, the stream has {}",
612 words.len()
613 ),
614 ));
615 }
616 let kind = match version >> 16 {
617 0 => ProgramType::Pixel,
618 1 => ProgramType::Vertex,
619 2 => ProgramType::Geometry,
620 3 => ProgramType::Hull,
621 4 => ProgramType::Domain,
622 5 => ProgramType::Compute,
623 other => {
624 return Err(error(
625 0,
626 format!("program type {other} is not a Shader Model 4 or 5 stage"),
627 ));
628 }
629 };
630 let mut result = Decoded {
631 kind,
632 major: (version >> 4) & 15,
633 minor: version & 15,
634 declarations: Vec::new(),
635 instructions: Vec::new(),
636 };
637 let mut at = 2;
638 while let Some(&t) = words.get(at) {
639 let opcode = low16(t & 0x7ff);
640 if opcode == 53 {
642 let len = words
643 .get(at.saturating_add(1))
644 .and_then(|&n| usize::try_from(n).ok())
645 .unwrap_or(0);
646 let end = at.checked_add(len).filter(|&end| len >= 2 && end <= words.len());
647 let (Some(end), Some(body)) = (end, words.get(at.saturating_add(2)..at.saturating_add(len)))
648 else {
649 return Err(error(
650 at,
651 format!(
652 "customdata of {len} words (at least 2) does not fit the stream's {} words",
653 words.len()
654 ),
655 ));
656 };
657 let class = t >> 11;
658 let kind = if class == 3 {
659 let (rows, rest) = body.as_chunks::<4>();
660 if !rest.is_empty() {
661 return Err(error(
662 at,
663 format!(
664 "an immediate constant buffer of {} words, expected whole rows of 4",
665 body.len()
666 ),
667 ));
668 }
669 Declared::ImmediateBuffer(rows.to_vec())
670 } else {
671 Declared::CustomData { class }
672 };
673 result.declarations.push(Declaration {
674 word: at,
675 opcode,
676 control: class,
677 kind,
678 });
679 at = end;
680 continue;
681 }
682 let len = usize::try_from((t >> 24) & 127).unwrap_or(0);
683 if len == 0 {
684 return Err(error(at, "instruction length 0 (at least 1)"));
685 }
686 let end = at.checked_add(len).filter(|&end| end <= words.len());
687 let (Some(end), Some(body)) = (end, words.get(at.saturating_add(1)..at.saturating_add(len))) else {
688 return Err(error(
689 at,
690 format!(
691 "instruction length {len} (at least 1) does not fit the stream's {} words",
692 words.len()
693 ),
694 ));
695 };
696 let mut c = Cursor {
697 words: body,
698 at: 0,
699 origin: at.saturating_add(1),
700 kind,
701 };
702 let mut extensions = Vec::new();
703 let mut extended = t >> 31 != 0;
704 while extended {
705 let ext = c.take()?;
706 extensions.push(ext);
707 extended = ext >> 31 != 0;
708 }
709 let control = (t >> 11) & 0x1fff;
710 if is_declaration(opcode) {
711 let kind = declaration(opcode, control, &mut c)?;
712 if c.at != c.words.len() {
713 return Err(error(
714 at,
715 format!(
716 "{} words after the operands of {}",
717 c.words.len().saturating_sub(c.at),
718 opcode_name(opcode)
719 ),
720 ));
721 }
722 result.declarations.push(Declaration {
723 word: at,
724 opcode,
725 control,
726 kind,
727 });
728 } else {
729 let mut operands = Vec::new();
730 while c.at < c.words.len() {
731 operands.push(c.operand(0)?);
732 }
733 if let Some(format) = operand_format(opcode)
734 && operands.len() != format.operands()
735 {
736 return Err(error(
737 at,
738 format!(
739 "{} has {} operands, its format is {} destinations and {} sources",
740 opcode_name(opcode),
741 operands.len(),
742 format.destinations.len(),
743 format.sources.len()
744 ),
745 ));
746 }
747 result.instructions.push(Instruction {
748 word: at,
749 opcode,
750 control,
751 extensions,
752 saturate: t & 0x2000 != 0,
753 nonzero: t & 0x4_0000 != 0,
754 operands,
755 });
756 }
757 at = end;
758 }
759 Ok(result)
760}
761
762fn declaration(opcode: u16, control: u32, c: &mut Cursor<'_>) -> Result<Declared> {
763 let pos = c.origin.saturating_sub(1);
764 let slot = |o: &Operand| -> Result<u32> {
765 o.indices
766 .first()
767 .map(|i| i.base)
768 .ok_or_else(|| error(pos, "missing declaration index"))
769 };
770 Ok(match opcode {
771 104 => Declared::Temps(c.take()?),
772 105 => Declared::IndexableTemp {
773 register: c.take()?,
774 size: c.take()?,
775 components: c.take()?,
776 },
777 106 => Declared::GlobalFlags(control),
778 88 => {
779 let o = c.operand(0)?;
780 let token = c.take()?;
781 Declared::Resource {
782 slot: slot(&o)?,
783 dimension: control & 31,
784 samples: (control >> 5) & 127,
785 returns: return_codes(token),
786 token,
787 }
788 }
789 89 => {
790 let o = c.operand(0)?;
791 Declared::ConstantBuffer {
792 slot: slot(&o)?,
793 rows: o.indices.get(1).map_or(0, |i| i.base),
794 dynamic: control & 1 != 0,
795 dimensions: o.indices.len(),
796 }
797 }
798 90 => {
799 let o = c.operand(0)?;
800 Declared::Sampler {
801 slot: slot(&o)?,
802 mode: control & 15,
803 }
804 }
805 95..=100 => {
806 let operand = c.operand(0)?;
807 let pixel = matches!(opcode, 98..=100);
808 let interpolation = pixel.then_some(control & 15);
809 let name = matches!(opcode, 96 | 97 | 99 | 100)
810 .then(|| c.take())
811 .transpose()?;
812 Declared::Input {
813 operand,
814 interpolation,
815 name,
816 }
817 }
818 101..=103 => {
819 let operand = c.operand(0)?;
820 let name = (opcode != 101).then(|| c.take()).transpose()?;
821 Declared::Output { operand, name }
822 }
823 156 => {
824 let o = c.operand(0)?;
825 let token = c.take()?;
826 Declared::UavTyped {
827 slot: slot(&o)?,
828 dimension: control & 31,
829 returns: return_codes(token),
830 token,
831 control,
832 }
833 }
834 157 => {
835 let o = c.operand(0)?;
836 Declared::UavRaw {
837 slot: slot(&o)?,
838 control,
839 }
840 }
841 158 => {
842 let o = c.operand(0)?;
843 Declared::UavStructured {
844 slot: slot(&o)?,
845 stride: c.take()?,
846 control,
847 }
848 }
849 155 => Declared::ThreadGroup([c.take()?, c.take()?, c.take()?]),
850 159 => {
851 let o = c.operand(0)?;
852 Declared::GroupSharedRaw {
853 register: slot(&o)?,
854 bytes: c.take()?,
855 }
856 }
857 160 => {
858 let o = c.operand(0)?;
859 Declared::GroupSharedStructured {
860 register: slot(&o)?,
861 stride: c.take()?,
862 count: c.take()?,
863 }
864 }
865 161 => {
866 let o = c.operand(0)?;
867 Declared::ResourceRaw { slot: slot(&o)? }
868 }
869 162 => {
870 let o = c.operand(0)?;
871 Declared::ResourceStructured {
872 slot: slot(&o)?,
873 stride: c.take()?,
874 }
875 }
876 _ => Declared::Other {
877 opcode,
878 control,
879 words: c.rest(),
880 },
881 })
882}