Skip to main content

rsx_reader/
fragment.rs

1//! RSX fragment instructions. Words in the big-endian microcode are rotated by
2//! 16 bits before field extraction. Inline constants occupy one further slot.
3//! This reads instructions; it neither executes nor lowers them.
4use primase_foundations::bytes::View;
5use std::fmt::Write;
6
7macro_rules! opcodes {
8    ($($name:ident = $code:literal, $arity:literal;)+) => {
9        /// A fragment instruction's opcode.
10        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
11        #[repr(u8)]
12        pub enum Opcode {
13            $(
14                #[doc = concat!("`", stringify!($name), "`, opcode ", stringify!($code), ", with ", stringify!($arity), " sources.")]
15                $name = $code,
16            )+
17        }
18        impl Opcode {
19            fn read(code: u8) -> Option<Self> { match code { $($code => Some(Self::$name),)+ _ => None } }
20            /// The opcode's name in upper case (`MOV`, `TEX`, ...).
21            pub fn mnemonic(self) -> &'static str { match self { $(Self::$name => stringify!($name),)+ } }
22            /// How many sources the instruction reads.
23            pub fn source_count(self) -> usize { match self { $(Self::$name => $arity,)+ } }
24        }
25    }
26}
27
28// The arithmetic/texture vocabulary follows NV_fragment_program[2]. The RSX
29// encodings are described by the public RSX instruction declarations.
30opcodes! {
31    NOP=0x00,0; MOV=0x01,1; MUL=0x02,2; ADD=0x03,2; MAD=0x04,3;
32    DP3=0x05,2; DP4=0x06,2; DST=0x07,2; MIN=0x08,2; MAX=0x09,2;
33    SLT=0x0a,2; SGE=0x0b,2; SLE=0x0c,2; SGT=0x0d,2; SNE=0x0e,2; SEQ=0x0f,2;
34    FRC=0x10,1; FLR=0x11,1; KIL=0x12,0; PK4=0x13,1; UP4=0x14,1;
35    DDX=0x15,1; DDY=0x16,1; TEX=0x17,1; TXP=0x18,1; TXD=0x19,3;
36    RCP=0x1a,1; RSQ=0x1b,1; EX2=0x1c,1; LG2=0x1d,1; LIT=0x1e,1; LRP=0x1f,3;
37    STR=0x20,0; SFL=0x21,0; COS=0x22,1; SIN=0x23,1; PK2=0x24,1; UP2=0x25,1;
38    POW=0x26,2; PKB=0x27,1; UPB=0x28,1; PK16=0x29,1; UP16=0x2a,1;
39    PKG=0x2c,1; UPG=0x2d,1; DP2A=0x2e,3; TXL=0x2f,2; TXB=0x31,2;
40    REFL=0x36,2; DP2=0x38,2; NRM=0x39,1; DIV=0x3a,2; DIVSQ=0x3b,2; LIF=0x3c,1;
41    FENCT=0x3d,0; FENCB=0x3e,0; BRK=0x40,0; CAL=0x41,0;
42    IFE=0x42,0; LOOP=0x43,0; REP=0x44,0; RET=0x45,0;
43}
44
45impl Opcode {
46    /// Whether the instruction samples a texture.
47    pub fn samples_texture(self) -> bool {
48        matches!(self, Self::TEX | Self::TXP | Self::TXD | Self::TXL | Self::TXB)
49    }
50    /// Whether the instruction writes a destination register or condition code.
51    pub fn writes_destination(self) -> bool {
52        !matches!(
53            self,
54            Self::NOP
55                | Self::KIL
56                | Self::FENCT
57                | Self::FENCB
58                | Self::BRK
59                | Self::CAL
60                | Self::IFE
61                | Self::LOOP
62                | Self::REP
63                | Self::RET
64        )
65    }
66}
67
68/// A fragment register.
69#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
70pub struct Register {
71    /// The register number (0 to 63).
72    pub index: u8,
73    /// Whether it is a half-precision register (`h<n>`) rather than a full one (`r<n>`).
74    pub half: bool,
75}
76
77/// What a source operand reads.
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
79pub enum SourceKind {
80    /// A register.
81    Register(Register),
82    /// An interpolated input, by its 4-bit input index.
83    Input(u8),
84    /// The inline constant in the slot after the instruction.
85    Constant,
86}
87
88/// A source operand.
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90pub struct Source {
91    /// What the source reads.
92    pub kind: SourceKind,
93    /// For each lane the component (0 x to 3 w) it reads.
94    pub swizzle: [u8; 4],
95    /// The source is negated.
96    pub negate: bool,
97    /// The source's absolute value is used.
98    pub absolute: bool,
99}
100
101/// The condition code test an instruction executes under.
102#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub struct Predicate {
104    /// Bit 0 less, bit 1 equal, bit 2 greater. Zero never executes; seven always executes.
105    pub test: u8,
106    /// For each lane the component of the condition code it tests.
107    pub swizzle: [u8; 4],
108    /// Which of the two condition-code registers it tests.
109    pub register: u8,
110}
111
112impl Predicate {
113    /// NV condition-code test, with all three outcomes enabled meaning true.
114    pub fn condition(self) -> &'static str {
115        match self.test {
116            0 => "FL",
117            1 => "LT",
118            2 => "EQ",
119            3 => "LE",
120            4 => "GT",
121            5 => "NE",
122            6 => "GE",
123            7 => "TR",
124            _ => "invalid",
125        }
126    }
127}
128
129/// What a flow-control instruction does.
130#[derive(Clone, Debug, PartialEq, Eq)]
131pub enum Control {
132    /// `IFE`: a conditional block.
133    If {
134        /// The byte offset of the else block.
135        else_offset: usize,
136        /// The byte offset where the block ends.
137        end_offset: usize,
138    },
139    /// `LOOP` or `REP`.
140    Loop {
141        /// The iteration count.
142        count: u8,
143        /// The initial counter value.
144        initial: u8,
145        /// The counter increment.
146        increment: u8,
147        /// The byte offset where the loop ends.
148        end_offset: usize,
149    },
150    /// No call-target interpretation or execution is claimed by this reader.
151    Call {
152        /// The instruction's three operand words, as stored.
153        encoded: [u32; 3],
154    },
155    /// `BRK`.
156    Break,
157    /// `RET`.
158    Return,
159}
160
161/// A decoded instruction.
162#[derive(Clone, Debug, PartialEq, Eq)]
163pub struct Instruction {
164    /// The byte offset of the instruction's first slot.
165    pub offset: usize,
166    /// Its size in bytes: 16, or 32 with an inline constant.
167    pub size: usize,
168    /// The opcode.
169    pub opcode: Opcode,
170    /// Rotated words, including fields not interpreted here.
171    pub words: [u32; 4],
172    /// The instruction is the last of the program.
173    pub end: bool,
174    /// The destination register.
175    pub destination: Register,
176    /// The components written, bit 0 x to bit 3 w.
177    pub write_mask: u8,
178    /// The result goes to the condition code only.
179    pub no_destination: bool,
180    /// The instruction updates a condition-code register.
181    pub update_condition: bool,
182    /// Which condition-code register it updates.
183    pub condition_destination: u8,
184    /// The condition under which it executes.
185    pub predicate: Predicate,
186    /// The result is clamped to 0 to 1.
187    pub saturate: bool,
188    /// The result precision code, as stored.
189    pub precision: u8,
190    /// The texture lookup expands its result (the signed-range flag), as stored.
191    pub expand_texture: bool,
192    /// The texture unit a sampling instruction reads.
193    pub texture_unit: Option<u8>,
194    /// The sources, as many as the opcode reads.
195    pub sources: Vec<Source>,
196    /// The inline constant of the slot after the instruction, as IEEE bits.
197    pub constant: Option<[u32; 4]>,
198    /// Encoded modifiers for each source, and the result scale, retained without floating-point evaluation.
199    pub source_precision: [u8; 3],
200    /// The result scale code, as stored.
201    pub scale: u8,
202    /// The inputs are addressed indirectly.
203    pub indexed_input: bool,
204    /// The indirect input address, as stored.
205    pub input_address: u16,
206    /// The inputs are perspective-corrected.
207    pub perspective_input: bool,
208    /// What a flow-control instruction does.
209    pub control: Option<Control>,
210}
211
212/// A decoded fragment program.
213#[derive(Clone, Debug, PartialEq, Eq)]
214pub struct Program {
215    /// The instructions, in order.
216    pub instructions: Vec<Instruction>,
217    /// The program's size in bytes.
218    pub byte_len: usize,
219}
220
221/// What is wrong with a fragment program, and where.
222#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
223#[error("RSX fragment program at byte {offset}: {detail}")]
224pub struct Error {
225    /// The byte offset in the microcode where the problem is.
226    pub offset: usize,
227    /// What was expected there, or what was found instead.
228    pub detail: String,
229}
230
231fn error(offset: usize, detail: impl Into<String>) -> Error {
232    Error {
233        offset,
234        detail: detail.into(),
235    }
236}
237/// The `width` bits of `n` from bit `shift` up.
238fn bits(n: u32, shift: u32, width: u32) -> u32 {
239    let mask = 1u32.checked_shl(width).map_or(u32::MAX, |m| m.wrapping_sub(1));
240    n.checked_shr(shift).unwrap_or(0) & mask
241}
242/// [`bits`] of at most 8 bits.
243fn bits8(n: u32, shift: u32, width: u32) -> u8 {
244    let [byte, ..] = bits(n, shift, width.min(8)).to_le_bytes();
245    byte
246}
247/// [`bits`] of at most 16 bits.
248fn bits16(n: u32, shift: u32, width: u32) -> u16 {
249    let [a, b, ..] = bits(n, shift, width.min(16)).to_le_bytes();
250    u16::from_le_bytes([a, b])
251}
252fn swizzle(n: u32, shift: u32) -> [u8; 4] {
253    [0, 2, 4, 6].map(|k| bits8(n, shift.saturating_add(k), 2))
254}
255
256/// Decode exactly one big-endian fragment program. Requires END at the exact
257/// end, validates branch boundaries and refuses reserved/unimplemented opcodes.
258/// Inline constants are retained as IEEE bits, never decoded as instructions.
259pub fn decode(bytes: &[u8]) -> Result<Program, Error> {
260    if bytes.is_empty() || !bytes.len().is_multiple_of(16) {
261        return Err(error(0, "fragment microcode must contain complete 16-byte slots"));
262    }
263    let view = View::new(bytes);
264    let slot = |at| -> Result<[u32; 4], Error> {
265        let data = view.slice(at, 16).map_err(|e| error(at, e.to_string()))?;
266        let (words, _) = data.as_chunks::<4>();
267        let [d, a, b, c] = words else {
268            return Err(error(at, "a 16-byte slot"));
269        };
270        Ok([d, a, b, c].map(|w| u32::from_be_bytes(*w).rotate_left(16)))
271    };
272    let mut at = 0;
273    let mut instructions = Vec::new();
274    loop {
275        let words = slot(at)?;
276        let [d, a, b, c] = words;
277        let code = bits8(d, 24, 6) | (bits8(b, 31, 1) << 6);
278        let opcode =
279            Opcode::read(code).ok_or_else(|| error(at, format!("unsupported fragment opcode {code:#x}")))?;
280        let mut sources = Vec::new();
281        for (i, raw) in [a, b, c].into_iter().take(opcode.source_count()).enumerate() {
282            let kind = match bits(raw, 0, 2) {
283                0 => SourceKind::Register(Register {
284                    index: bits8(raw, 2, 6),
285                    half: bits(raw, 8, 1) != 0,
286                }),
287                1 => SourceKind::Input(bits8(d, 13, 4)),
288                2 => SourceKind::Constant,
289                _ => {
290                    return Err(error(
291                        at.saturating_add(4).saturating_add(i.saturating_mul(4)),
292                        "reserved source register type",
293                    ));
294                }
295            };
296            sources.push(Source {
297                kind,
298                swizzle: swizzle(raw, 9),
299                negate: bits(raw, 17, 1) != 0,
300                absolute: bits(raw, if i == 0 { 29 } else { 18 }, 1) != 0,
301            });
302        }
303        let constant = if sources.iter().any(|s| s.kind == SourceKind::Constant) {
304            Some(slot(at.saturating_add(16))?)
305        } else {
306            None
307        };
308        let size = if constant.is_some() { 32 } else { 16 };
309        let control = match opcode {
310            Opcode::IFE => Some(Control::If {
311                else_offset: usize::try_from(b & 0x7fffffff)
312                    .map_err(|_| error(at, "branch address overflow"))?
313                    .checked_mul(4)
314                    .ok_or_else(|| error(at, "branch address overflow"))?,
315                end_offset: usize::try_from(c)
316                    .map_err(|_| error(at, "branch address overflow"))?
317                    .checked_mul(4)
318                    .ok_or_else(|| error(at, "branch address overflow"))?,
319            }),
320            Opcode::LOOP | Opcode::REP => Some(Control::Loop {
321                count: bits8(b, 2, 8),
322                initial: bits8(b, 10, 8),
323                increment: bits8(b, 19, 8),
324                end_offset: usize::try_from(c)
325                    .map_err(|_| error(at, "loop address overflow"))?
326                    .checked_mul(4)
327                    .ok_or_else(|| error(at, "loop address overflow"))?,
328            }),
329            Opcode::CAL => Some(Control::Call { encoded: [a, b, c] }),
330            Opcode::BRK => Some(Control::Break),
331            Opcode::RET => Some(Control::Return),
332            _ => None,
333        };
334        let end = d & 1 != 0;
335        instructions.push(Instruction {
336            offset: at,
337            size,
338            opcode,
339            words,
340            end,
341            destination: Register {
342                index: bits8(d, 1, 6),
343                half: bits(d, 7, 1) != 0,
344            },
345            write_mask: bits8(d, 9, 4),
346            no_destination: bits(d, 30, 1) != 0,
347            update_condition: bits(d, 8, 1) != 0,
348            condition_destination: bits8(a, 30, 1),
349            predicate: Predicate {
350                test: bits8(a, 18, 3),
351                swizzle: swizzle(a, 21),
352                register: bits8(a, 31, 1),
353            },
354            saturate: bits(d, 31, 1) != 0,
355            precision: bits8(d, 22, 2),
356            expand_texture: bits(d, 21, 1) != 0,
357            texture_unit: opcode.samples_texture().then_some(bits8(d, 17, 4)),
358            sources,
359            constant,
360            source_precision: [bits8(b, 19, 3), bits8(b, 22, 3), bits8(b, 25, 3)],
361            scale: bits8(b, 28, 3),
362            indexed_input: bits(c, 30, 1) != 0,
363            input_address: bits16(c, 19, 11),
364            perspective_input: bits(c, 31, 1) != 0,
365            control,
366        });
367        at = at.saturating_add(size);
368        if end {
369            if at != bytes.len() {
370                return Err(error(at, "bytes after END instruction"));
371            }
372            break;
373        }
374        if at == bytes.len() {
375            return Err(error(at, "fragment program has no END instruction"));
376        }
377    }
378    let boundary = |offset| offset == bytes.len() || instructions.iter().any(|i| i.offset == offset);
379    for i in &instructions {
380        match i.control {
381            Some(Control::If {
382                else_offset,
383                end_offset,
384            }) if !boundary(else_offset)
385                || !boundary(end_offset)
386                || else_offset < i.offset.saturating_add(i.size)
387                || end_offset < else_offset =>
388            {
389                return Err(error(i.offset, "invalid IF boundaries"));
390            }
391            Some(Control::Loop { end_offset, .. })
392                if !boundary(end_offset) || end_offset < i.offset.saturating_add(i.size) =>
393            {
394                return Err(error(i.offset, "invalid loop boundary"));
395            }
396            _ => {}
397        }
398    }
399    Ok(Program {
400        instructions,
401        byte_len: bytes.len(),
402    })
403}
404
405fn register(r: Register) -> String {
406    format!("{}{}", if r.half { "h" } else { "r" }, r.index)
407}
408fn channels(s: [u8; 4]) -> String {
409    s.into_iter()
410        .map(|n| "xyzw".chars().nth(usize::from(n)).unwrap_or('?'))
411        .collect()
412}
413
414impl Program {
415    /// Stable inspection text. Full predicates and modifiers remain in the typed
416    /// instruction even where an external assembler dialect omits them.
417    pub fn disassemble(&self) -> String {
418        let mut out = String::new();
419        for i in &self.instructions {
420            let _ = write!(out, "{:04x}: {}", i.offset, i.opcode.mnemonic());
421            if i.opcode.writes_destination() {
422                let mask: String = (0..4)
423                    .zip("xyzw".chars())
424                    .filter(|&(n, _)| bits(u32::from(i.write_mask), n, 1) != 0)
425                    .map(|(_, channel)| channel)
426                    .collect();
427                let _ = write!(
428                    out,
429                    " {}.{mask}",
430                    if i.no_destination || i.destination.index == 63 {
431                        format!("cc{}", i.condition_destination)
432                    } else {
433                        register(i.destination)
434                    }
435                );
436            }
437            for s in &i.sources {
438                let source = match s.kind {
439                    SourceKind::Register(r) => register(r),
440                    SourceKind::Input(n) => format!("input[{n}]"),
441                    SourceKind::Constant => format!("{:08x?}", i.constant),
442                };
443                let _ = write!(
444                    out,
445                    ", {}{}{}.{}",
446                    if s.negate { "-" } else { "" },
447                    if s.absolute { "abs " } else { "" },
448                    source,
449                    channels(s.swizzle)
450                );
451            }
452            if let Some(unit) = i.texture_unit {
453                let _ = write!(out, ", tex{unit}");
454            }
455            if let Some(control) = &i.control {
456                let _ = write!(out, " {control:?}");
457            }
458            let _ = writeln!(
459                out,
460                " ; pred={} cc{}.{}, sat={}, precision={}, scale={}, src_precision={:?}, end={}",
461                i.predicate.test,
462                i.predicate.register,
463                channels(i.predicate.swizzle),
464                i.saturate,
465                i.precision,
466                i.scale,
467                i.source_precision,
468                i.end
469            );
470        }
471        out
472    }
473}
474
475pub mod flow;