Skip to main content

dxbc_reader/
decode.rs

1//! The token-level decoder of Shader Model 4 and 5 programs: any stage's version token, declarations as typed
2//! records, and instructions as an opcode, its control bits, its extended tokens and its operands. It decides nothing
3//! about which instructions a caller can use: they are all returned, and the caller refuses what it cannot handle. A token
4//! it cannot read at all (an operand register type it has no name for, a truncated instruction) is an error naming the
5//! word.
6
7use crate::format::{OperandFormat, operand_format};
8use crate::opcodes::NAMES;
9use crate::{Result, error};
10
11/// The low byte of `v`.
12fn low8(v: u32) -> u8 {
13    let [byte, ..] = v.to_le_bytes();
14    byte
15}
16
17/// The low 16 bits of `v`.
18fn low16(v: u32) -> u16 {
19    let [a, b, ..] = v.to_le_bytes();
20    u16::from_le_bytes([a, b])
21}
22
23/// The kind of register an operand names.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum Register {
26    /// A temporary register (`r<n>`).
27    Temp,
28    /// An input register (`v<n>`).
29    Input,
30    /// An output register (`o<n>`).
31    Output,
32    /// An indexable temporary (`x<n>[i]`).
33    IndexableTemp,
34    /// A constant buffer register (`cb<slot>[row]`).
35    Constant,
36    /// An immediate operand (`l(...)`), its values in [`Operand::immediate`].
37    Immediate,
38    /// A shader resource view (`t<n>`).
39    Texture,
40    /// A sampler (`s<n>`).
41    Sampler,
42    /// The null register (`null`), a destination that discards its result.
43    Null,
44    /// An unordered-access view (`u<n>`).
45    Uav,
46    /// The immediate constant buffer (`icb[n]`), its rows the program's own.
47    ImmediateBuffer,
48    /// `vPrim`, the primitive id input.
49    PrimitiveId,
50    /// `vCoverage`, the input coverage mask.
51    InputCoverage,
52    /// `oDepth`.
53    OutputDepth,
54    /// `oDepthGE`, a depth output that is at least the rasterized depth.
55    OutputDepthGreaterEqual,
56    /// `oDepthLE`, a depth output that is at most the rasterized depth.
57    OutputDepthLessEqual,
58    /// `oMask`, the coverage output.
59    OutputCoverage,
60    /// `rasterizer`: the render target as `sample_info` and `sample_pos` query it (its sample count, a sample's position).
61    Rasterizer,
62    /// `oStencilRef`, the stencil reference output.
63    OutputStencilRef,
64    /// `g<n>`, thread-group shared memory.
65    GroupSharedMemory,
66    /// `vThreadID`, `vThreadGroupID`, `vThreadIDInGroup` and `vThreadIDInGroupFlattened`, a compute program's ids.
67    ThreadId,
68    /// `vThreadGroupID`.
69    ThreadGroupId,
70    /// `vThreadIDInGroup`.
71    ThreadIdInGroup,
72    /// `vThreadIDInGroupFlattened`.
73    ThreadIdInGroupFlattened,
74    /// `vOutputControlPointID`, `vForkInstanceID`, `vJoinInstanceID`: a hull program's phase numbers.
75    OutputControlPointId,
76    /// `vForkInstanceID`, a hull program's fork phase instance.
77    ForkInstanceId,
78    /// `vJoinInstanceID`, a hull program's join phase instance.
79    JoinInstanceId,
80    /// `vicp[point][register]`: an input control point of a hull or domain program.
81    InputControlPoint,
82    /// `vocp[point][register]`: an output control point a hull program's patch-constant phases read.
83    OutputControlPoint,
84    /// `vpc[register]`: a patch constant a domain program reads (or a hull program's join phase).
85    InputPatchConstant,
86    /// `vDomain`: a domain program's location in its patch.
87    InputDomainPoint,
88    /// `m<n>`: a geometry program's output stream.
89    Stream,
90    /// `vGSInstanceID`.
91    GsInstanceId,
92}
93impl Register {
94    /// How many index dimensions an operand of this register has in Shader Model 5.0.
95    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/// The components of a register an operand selects.
164#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165pub enum Selection {
166    /// A write mask: bit 0 is x, bit 3 is w.
167    Mask(u8),
168    /// A four-component swizzle: for each result lane the source lane (0 x to 3 w) it reads.
169    Swizzle([u8; 4]),
170    /// One source lane (0 x to 3 w).
171    Scalar(u8),
172    /// No selection: the operand has no components (a sampler, a resource).
173    None,
174}
175
176/// An instruction's or declaration's operand.
177#[derive(Clone, Debug)]
178pub struct Operand {
179    /// The kind of register it names.
180    pub register: Register,
181    /// Which components it selects.
182    pub selection: Selection,
183    /// Its index dimensions, outermost first: none for an immediate, one for `r3`, two for `cb2[5]`.
184    pub indices: Vec<Index>,
185    /// The values of an immediate operand (one lane repeated, or four); zero for other registers.
186    pub immediate: [u32; 4],
187    /// 0 none, 1 negate, 2 absolute, 3 negated absolute (float sources); 4 two's-complement negate (an integer
188    /// instruction's negate; the encoding has no such value, so a caller that knows the instruction reads integers sets it).
189    pub modifier: u8,
190    /// The minimum-precision annotation of the operand's extended token (0 none; 1 float16, 2 float2_8, 4 sint16,
191    /// 5 uint16).
192    pub precision: u8,
193    /// The operand's extended token marks it as a non-uniform resource index.
194    pub non_uniform: bool,
195}
196
197/// One index dimension of an operand: a constant, a relative register, or both added.
198#[derive(Clone, Debug)]
199pub struct Index {
200    /// The constant part of the index.
201    pub base: u32,
202    /// The register whose component is added to `base`, where the index is relative.
203    pub relative: Option<Box<Operand>>,
204}
205
206/// An instruction as stored.
207#[derive(Clone, Debug)]
208pub struct Instruction {
209    /// The instruction's first word in the token stream.
210    pub word: usize,
211    /// The opcode token's low 11 bits (see [`opcode_name`]).
212    pub opcode: u16,
213    /// The opcode token's bits 11 to 23: saturate and the opcode-specific controls (the test of a conditional, a
214    /// `resinfo` return type, the interpolation of a declaration).
215    pub control: u32,
216    /// The opcode's extended tokens as stored (sample offsets, resource dimension, return type).
217    pub extensions: Vec<u32>,
218    /// The saturate bit of the opcode token.
219    pub saturate: bool,
220    /// A conditional (`if`, `breakc`, `continuec`) taken when its operand is nonzero (`_nz`), else when zero (`_z`).
221    pub nonzero: bool,
222    /// The operands, destination first where the instruction has one.
223    pub operands: Vec<Operand>,
224}
225impl Instruction {
226    /// The opcode's name (`mov`, `add`, ...), or `unknown opcode`.
227    pub fn name(&self) -> &'static str {
228        opcode_name(self.opcode)
229    }
230    /// The sample offsets of the extended token of kind 1 (`_aoffimmi`), if the instruction has one.
231    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    /// The resource dimension and structured stride of the extended token of kind 2.
238    pub fn resource_dimension(&self) -> Option<(u32, u32)> {
239        self.extension(2).map(|e| ((e >> 6) & 31, (e >> 11) & 0xf_ffff))
240    }
241    /// The return-type token of the extended token of kind 3 (four nibbles).
242    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    /// The opcode's operand format (see [`operand_format`]); `decode` has checked the operand count against it.
249    pub fn format(&self) -> Option<OperandFormat> {
250        operand_format(self.opcode)
251    }
252    /// The destination operands and the source operands, split as the opcode's format says; `None` for an opcode
253    /// with no format.
254    pub fn destinations_and_sources(&self) -> Option<(&[Operand], &[Operand])> {
255        self.operands.split_at_checked(self.format()?.destinations.len())
256    }
257}
258
259/// The name of an opcode in Microsoft's tokenized program format, or `unknown opcode`.
260pub fn opcode_name(opcode: u16) -> &'static str {
261    NAMES
262        .get(usize::from(opcode))
263        .copied()
264        .unwrap_or("unknown opcode")
265}
266
267/// The type of a program's four return lanes in a resource declaration, as D3D_RETURN_TYPE codes (1 unorm, 2 snorm,
268/// 3 sint, 4 uint, 5 float, 6 mixed, 7 double, 8 continued, 9 unused), one nibble per lane.
269pub type ReturnCodes = [u8; 4];
270
271/// A declaration, typed. `word` is where it starts.
272#[derive(Clone, Debug)]
273pub struct Declaration {
274    /// The word of the token stream the declaration starts at.
275    pub word: usize,
276    /// The opcode token's low 11 bits (see [`opcode_name`]).
277    pub opcode: u16,
278    /// The opcode token's bits 11 to 23 as stored.
279    pub control: u32,
280    /// The declaration, typed.
281    pub kind: Declared,
282}
283
284/// A declaration, typed where this reader knows its layout.
285#[derive(Clone, Debug)]
286pub enum Declared {
287    /// `dcl_temps`.
288    Temps(u32),
289    /// `dcl_indexable_temp x<register>[size], components`.
290    IndexableTemp {
291        /// The register index (the `<n>` of `x<n>`).
292        register: u32,
293        /// The number of elements.
294        size: u32,
295        /// The number of components per element.
296        components: u32,
297    },
298    /// `dcl_globalFlags`: the control bits as stored (bit 0 refactoring allowed, 1 double precision, 2 forced early
299    /// depth-stencil, 3 raw and structured buffers, 4 skip optimization, 5 min precision, 6 double extensions, 7
300    /// shader extensions).
301    GlobalFlags(u32),
302    /// `dcl_constantbuffer cb<slot>[rows]`; `dynamic` when it is dynamically indexed.
303    ConstantBuffer {
304        /// The buffer slot (the `<slot>` of `cb<slot>`).
305        slot: u32,
306        /// The number of 16-byte rows; 0 where the declaration states none.
307        rows: u32,
308        /// The buffer is dynamically indexed.
309        dynamic: bool,
310        /// The number of index dimensions the operand had (1 or 2).
311        dimensions: usize,
312    },
313    /// `dcl_sampler s<slot>`, its mode: 0 default, 1 comparison, 2 mono.
314    Sampler {
315        /// The sampler slot.
316        slot: u32,
317        /// 0 default, 1 comparison, 2 mono.
318        mode: u32,
319    },
320    /// `dcl_resource_<dimension> t<slot>`: the D3D resource dimension code, the sample count of a multisampled one,
321    /// and the four return types.
322    Resource {
323        /// The resource slot.
324        slot: u32,
325        /// The D3D resource dimension code.
326        dimension: u32,
327        /// The sample count of a multisampled resource.
328        samples: u32,
329        /// The return type of each lane.
330        returns: ReturnCodes,
331        /// The return type token as stored.
332        token: u32,
333    },
334    /// `dcl_resource_raw t<slot>`.
335    ResourceRaw {
336        /// The resource slot.
337        slot: u32,
338    },
339    /// `dcl_resource_structured t<slot>, stride`.
340    ResourceStructured {
341        /// The resource slot.
342        slot: u32,
343        /// The element stride in bytes.
344        stride: u32,
345    },
346    /// `dcl_uav_typed` and its control bits (dimension in bits 0 to 4, globally coherent bit 5).
347    UavTyped {
348        /// The UAV slot.
349        slot: u32,
350        /// The D3D resource dimension code.
351        dimension: u32,
352        /// The return type of each lane.
353        returns: ReturnCodes,
354        /// The return type token as stored.
355        token: u32,
356        /// The opcode token's control bits.
357        control: u32,
358    },
359    /// `dcl_uav_raw u<slot>`.
360    UavRaw {
361        /// The UAV slot.
362        slot: u32,
363        /// The opcode token's control bits.
364        control: u32,
365    },
366    /// `dcl_uav_structured u<slot>, stride`.
367    UavStructured {
368        /// The UAV slot.
369        slot: u32,
370        /// The element stride in bytes.
371        stride: u32,
372        /// The opcode token's control bits.
373        control: u32,
374    },
375    /// `dcl_thread_group x, y, z`.
376    ThreadGroup([u32; 3]),
377    /// `dcl_tgsm_raw g<register>, bytes`.
378    GroupSharedRaw {
379        /// The shared memory register index.
380        register: u32,
381        /// Its size in bytes.
382        bytes: u32,
383    },
384    /// `dcl_tgsm_structured g<register>, stride, count`.
385    GroupSharedStructured {
386        /// The shared memory register index.
387        register: u32,
388        /// The element stride in bytes.
389        stride: u32,
390        /// The number of elements.
391        count: u32,
392    },
393    /// An input register or special input: `dcl_input`, `_sgv`, `_siv`, `_ps`, `_ps_sgv`, `_ps_siv`. `interpolation` is
394    /// a pixel input's `D3D_INTERPOLATION_MODE`; `name` a system value's `D3D_NAME`.
395    Input {
396        /// The input register or special input the declaration names.
397        operand: Operand,
398        /// The interpolation mode of a pixel input.
399        interpolation: Option<u32>,
400        /// The system value (`D3D_NAME`) of a `_sgv` or `_siv` declaration.
401        name: Option<u32>,
402    },
403    /// An output register or special output: `dcl_output`, `_sgv`, `_siv`.
404    Output {
405        /// The output register or special output the declaration names.
406        operand: Operand,
407        /// The system value (`D3D_NAME`) of a `_siv` or `_sgv` declaration.
408        name: Option<u32>,
409    },
410    /// `dcl_immediateConstantBuffer`.
411    ImmediateBuffer(Vec<[u32; 4]>),
412    /// Custom data that is not an immediate constant buffer (a comment, debug information, ...) by its class.
413    CustomData {
414        /// The custom data class.
415        class: u32,
416    },
417    /// A declaration this decoder does not type: stage-specific (geometry, tessellation, compute), interface tables.
418    Other {
419        /// The opcode.
420        opcode: u16,
421        /// The opcode token's control bits.
422        control: u32,
423        /// The declaration's words after its operands, as stored.
424        words: Vec<u32>,
425    },
426}
427
428/// The kind of program a version token names.
429#[derive(Clone, Copy, Debug, PartialEq, Eq)]
430pub enum ProgramType {
431    /// A pixel program (`ps`).
432    Pixel,
433    /// A vertex program (`vs`).
434    Vertex,
435    /// A geometry program (`gs`).
436    Geometry,
437    /// A hull program (`hs`).
438    Hull,
439    /// A domain program (`ds`).
440    Domain,
441    /// A compute program (`cs`).
442    Compute,
443}
444impl ProgramType {
445    /// The target's stage prefix (`ps`, `vs`, ...).
446    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/// A decoded token stream: its target, declarations and instructions, each in program order.
459#[derive(Clone, Debug)]
460pub struct Decoded {
461    /// The program's stage.
462    pub kind: ProgramType,
463    /// The shader model's major version.
464    pub major: u32,
465    /// The shader model's minor version.
466    pub minor: u32,
467    /// The declarations, in program order.
468    pub declarations: Vec<Declaration>,
469    /// The instructions, in program order.
470    pub instructions: Vec<Instruction>,
471}
472impl Decoded {
473    /// The target's name (`ps_5_0`).
474    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    /// The program's type: the input registers of a geometry, hull or domain program are indexed by vertex.
484    kind: ProgramType,
485}
486impl Cursor<'_> {
487    /// The word the cursor is at, in the token stream.
488    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                // only the modifier extension (kind 1) exists in Shader Model 4 and 5
530                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        // the representation of each index dimension, three bits each from bit 22
555        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
596/// The opcodes that are declarations (and the phase markers of hull programs, which carry none): 88-106 and
597/// 143-162 of Direct3D 10 and 11, the hull phases 113-116 and `dcl_gs_instance_count` (206).
598fn is_declaration(opcode: u16) -> bool {
599    matches!(opcode, 88..=106 | 113..=116 | 143..=162 | 206)
600}
601
602/// Decode a complete token stream: version token, length, then declarations and instructions to its end.
603pub 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        // customdata: its class in the opcode token's upper bits, its length (these two words included) the next word
641        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}