1use primase_foundations::bytes::View;
5use std::fmt::Write;
6
7macro_rules! opcodes {
8 ($($name:ident = $code:literal, $arity:literal;)+) => {
9 #[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 pub fn mnemonic(self) -> &'static str { match self { $(Self::$name => stringify!($name),)+ } }
22 pub fn source_count(self) -> usize { match self { $(Self::$name => $arity,)+ } }
24 }
25 }
26}
27
28opcodes! {
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 pub fn from_code(code: u8) -> Option<Self> {
48 Self::read(code)
49 }
50
51 pub fn mesa_name(self) -> Option<&'static str> {
56 Some(match self {
57 Self::PK4 => "PK4B",
58 Self::UP4 => "UP4B",
59 Self::PK2 => "PK2H",
60 Self::UP2 => "UP2H",
61 Self::PKB => "PK4UB",
62 Self::UPB => "UP4UB",
63 Self::PK16 => "PK2US",
64 Self::UP16 => "UP2US",
65 Self::REFL => "RFL",
66 Self::LIF => "LITEX2",
67 Self::IFE => "IF",
68 Self::PKG | Self::UPG | Self::DP2 | Self::NRM | Self::DIVSQ | Self::FENCT | Self::FENCB => {
69 return None;
70 }
71 other => other.mnemonic(),
72 })
73 }
74
75 pub fn samples_texture(self) -> bool {
77 matches!(self, Self::TEX | Self::TXP | Self::TXD | Self::TXL | Self::TXB)
78 }
79 pub fn writes_destination(self) -> bool {
81 !matches!(
82 self,
83 Self::NOP
84 | Self::KIL
85 | Self::FENCT
86 | Self::FENCB
87 | Self::BRK
88 | Self::CAL
89 | Self::IFE
90 | Self::LOOP
91 | Self::REP
92 | Self::RET
93 )
94 }
95}
96
97#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
99pub struct Register {
100 pub index: u8,
102 pub half: bool,
104}
105
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108pub enum SourceKind {
109 Register(Register),
111 Input(u8),
113 Constant,
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Eq)]
126pub enum InputSource {
127 Color0,
129 TexCoord(u8),
131 Other(u8),
133}
134
135impl InputSource {
136 const TEXCOORD_BASE: u8 = 4;
138 const TEXCOORDS: u8 = 10;
140
141 pub fn from_index(index: u8) -> Self {
143 match index {
144 1 => Self::Color0,
145 i => match i.checked_sub(Self::TEXCOORD_BASE) {
146 Some(k) if k < Self::TEXCOORDS => Self::TexCoord(k),
147 _ => Self::Other(i),
148 },
149 }
150 }
151
152 pub fn index(self) -> u8 {
154 match self {
155 Self::Color0 => 1,
156 Self::TexCoord(k) => Self::TEXCOORD_BASE.saturating_add(k),
157 Self::Other(i) => i,
158 }
159 }
160}
161
162impl SourceKind {
163 pub fn input_source(self) -> Option<InputSource> {
165 match self {
166 Self::Input(index) => Some(InputSource::from_index(index)),
167 _ => None,
168 }
169 }
170}
171
172#[derive(Clone, Copy, Debug, PartialEq, Eq)]
174pub struct Source {
175 pub kind: SourceKind,
177 pub swizzle: [u8; 4],
179 pub negate: bool,
181 pub absolute: bool,
183}
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq)]
187pub struct Predicate {
188 pub test: u8,
190 pub swizzle: [u8; 4],
192 pub register: u8,
194}
195
196impl Predicate {
197 pub fn condition(self) -> &'static str {
199 match self.test {
200 0 => "FL",
201 1 => "LT",
202 2 => "EQ",
203 3 => "LE",
204 4 => "GT",
205 5 => "NE",
206 6 => "GE",
207 7 => "TR",
208 _ => "invalid",
209 }
210 }
211}
212
213#[derive(Clone, Debug, PartialEq, Eq)]
215pub enum Control {
216 If {
218 else_offset: usize,
220 end_offset: usize,
222 },
223 Loop {
226 count: u8,
228 initial: u8,
230 increment: u8,
232 end_offset: usize,
234 },
235 Repeat {
238 counts: [u8; 3],
240 end_offset: usize,
242 },
243 Call {
246 target: usize,
248 encoded: [u32; 3],
250 },
251 Break,
253 Return,
255}
256
257#[derive(Clone, Debug, PartialEq, Eq)]
259pub struct Instruction {
260 pub offset: usize,
262 pub size: usize,
264 pub opcode: Opcode,
266 pub words: [u32; 4],
268 pub end: bool,
270 pub destination: Register,
272 pub write_mask: u8,
274 pub no_destination: bool,
276 pub update_condition: bool,
278 pub condition_destination: u8,
280 pub predicate: Predicate,
282 pub saturate: bool,
284 pub precision: u8,
286 pub expand_texture: bool,
288 pub texture_unit: Option<u8>,
290 pub sources: Vec<Source>,
292 pub constant: Option<[u32; 4]>,
294 pub source_precision: [u8; 3],
296 pub scale: u8,
298 pub indexed_input: bool,
301 pub input_address: u16,
305 pub interpolant_flag: bool,
312 pub control: Option<Control>,
314}
315
316#[derive(Clone, Debug, PartialEq, Eq)]
318pub struct Program {
319 pub instructions: Vec<Instruction>,
321 pub byte_len: usize,
323}
324
325#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
327#[error("RSX fragment program at byte {offset}: {detail}")]
328pub struct Error {
329 pub offset: usize,
331 pub detail: String,
333}
334
335fn error(offset: usize, detail: impl Into<String>) -> Error {
336 Error {
337 offset,
338 detail: detail.into(),
339 }
340}
341fn bits(n: u32, shift: u32, width: u32) -> u32 {
343 let mask = 1u32.checked_shl(width).map_or(u32::MAX, |m| m.wrapping_sub(1));
344 n.checked_shr(shift).unwrap_or(0) & mask
345}
346fn bits8(n: u32, shift: u32, width: u32) -> u8 {
348 let [byte, ..] = bits(n, shift, width.min(8)).to_le_bytes();
349 byte
350}
351fn bits16(n: u32, shift: u32, width: u32) -> u16 {
353 let [a, b, ..] = bits(n, shift, width.min(16)).to_le_bytes();
354 u16::from_le_bytes([a, b])
355}
356fn swizzle(n: u32, shift: u32) -> [u8; 4] {
357 [0, 2, 4, 6].map(|k| bits8(n, shift.saturating_add(k), 2))
358}
359
360pub fn decode(bytes: &[u8]) -> Result<Program, Error> {
364 if bytes.is_empty() || !bytes.len().is_multiple_of(16) {
365 return Err(error(0, "fragment microcode must contain complete 16-byte slots"));
366 }
367 let view = View::new(bytes);
368 let slot = |at| -> Result<[u32; 4], Error> {
369 let data = view.slice(at, 16).map_err(|e| error(at, e.to_string()))?;
370 let (words, _) = data.as_chunks::<4>();
371 let [d, a, b, c] = words else {
372 return Err(error(at, "a 16-byte slot"));
373 };
374 Ok([d, a, b, c].map(|w| u32::from_be_bytes(*w).rotate_left(16)))
375 };
376 let mut at = 0;
377 let mut instructions = Vec::new();
378 loop {
379 let words = slot(at)?;
380 let [d, a, b, c] = words;
381 let code = bits8(d, 24, 6) | (bits8(b, 31, 1) << 6);
382 let opcode =
383 Opcode::read(code).ok_or_else(|| error(at, format!("unsupported fragment opcode {code:#x}")))?;
384 let mut sources = Vec::new();
385 for (i, raw) in [a, b, c].into_iter().take(opcode.source_count()).enumerate() {
386 let kind = match bits(raw, 0, 2) {
387 0 => SourceKind::Register(Register {
388 index: bits8(raw, 2, 6),
389 half: bits(raw, 8, 1) != 0,
390 }),
391 1 => SourceKind::Input(bits8(d, 13, 4)),
392 2 => SourceKind::Constant,
393 _ => {
394 return Err(error(
395 at.saturating_add(4).saturating_add(i.saturating_mul(4)),
396 "reserved source register type",
397 ));
398 }
399 };
400 sources.push(Source {
401 kind,
402 swizzle: swizzle(raw, 9),
403 negate: bits(raw, 17, 1) != 0,
404 absolute: bits(raw, if i == 0 { 29 } else { 18 }, 1) != 0,
405 });
406 }
407 let constant = if sources.iter().any(|s| s.kind == SourceKind::Constant) {
408 Some(slot(at.saturating_add(16))?)
409 } else {
410 None
411 };
412 let size = if constant.is_some() { 32 } else { 16 };
413 let control = match opcode {
414 Opcode::IFE => Some(Control::If {
415 else_offset: usize::try_from(b & 0x7fffffff)
416 .map_err(|_| error(at, "branch address overflow"))?
417 .checked_mul(4)
418 .ok_or_else(|| error(at, "branch address overflow"))?,
419 end_offset: usize::try_from(c & 0x7fff_ffff)
420 .map_err(|_| error(at, "branch address overflow"))?
421 .checked_mul(4)
422 .ok_or_else(|| error(at, "branch address overflow"))?,
423 }),
424 Opcode::LOOP => Some(Control::Loop {
425 count: bits8(b, 2, 8),
426 initial: bits8(b, 10, 8),
427 increment: bits8(b, 19, 8),
428 end_offset: usize::try_from(c & 0x7fff_ffff)
429 .map_err(|_| error(at, "loop address overflow"))?
430 .checked_mul(4)
431 .ok_or_else(|| error(at, "loop address overflow"))?,
432 }),
433 Opcode::REP => Some(Control::Repeat {
434 counts: [bits8(b, 2, 8), bits8(b, 10, 8), bits8(b, 19, 8)],
435 end_offset: usize::try_from(c & 0x7fff_ffff)
436 .map_err(|_| error(at, "loop address overflow"))?
437 .checked_mul(4)
438 .ok_or_else(|| error(at, "loop address overflow"))?,
439 }),
440 Opcode::CAL => Some(Control::Call {
441 target: usize::try_from(b & 0x7fff_ffff)
442 .map_err(|_| error(at, "call address overflow"))?
443 .checked_mul(4)
444 .ok_or_else(|| error(at, "call address overflow"))?,
445 encoded: [a, b, c],
446 }),
447 Opcode::BRK => Some(Control::Break),
448 Opcode::RET => Some(Control::Return),
449 _ => None,
450 };
451 let end = d & 1 != 0;
452 instructions.push(Instruction {
453 offset: at,
454 size,
455 opcode,
456 words,
457 end,
458 destination: Register {
459 index: bits8(d, 1, 6),
460 half: bits(d, 7, 1) != 0,
461 },
462 write_mask: bits8(d, 9, 4),
463 no_destination: bits(d, 30, 1) != 0,
464 update_condition: bits(d, 8, 1) != 0,
465 condition_destination: bits8(a, 30, 1),
466 predicate: Predicate {
467 test: bits8(a, 18, 3),
468 swizzle: swizzle(a, 21),
469 register: bits8(a, 31, 1),
470 },
471 saturate: bits(d, 31, 1) != 0,
472 precision: bits8(d, 22, 2),
473 expand_texture: bits(d, 21, 1) != 0,
474 texture_unit: opcode.samples_texture().then_some(bits8(d, 17, 4)),
475 sources,
476 constant,
477 source_precision: [bits8(b, 19, 3), bits8(b, 22, 3), bits8(b, 25, 3)],
478 scale: bits8(b, 28, 3),
479 indexed_input: bits(c, 30, 1) != 0,
480 input_address: bits16(c, 19, 11),
481 interpolant_flag: bits(c, 31, 1) != 0,
482 control,
483 });
484 at = at.saturating_add(size);
485 if end {
486 if at != bytes.len() {
487 return Err(error(at, "bytes after END instruction"));
488 }
489 break;
490 }
491 if at == bytes.len() {
492 return Err(error(at, "fragment program has no END instruction"));
493 }
494 }
495 let boundary = |offset| offset == bytes.len() || instructions.iter().any(|i| i.offset == offset);
496 for i in &instructions {
497 match i.control {
498 Some(Control::If {
499 else_offset,
500 end_offset,
501 }) if !boundary(else_offset)
502 || !boundary(end_offset)
503 || else_offset < i.offset.saturating_add(i.size)
504 || end_offset < else_offset =>
505 {
506 return Err(error(i.offset, "invalid IF boundaries"));
507 }
508 Some(Control::Loop { end_offset, .. } | Control::Repeat { end_offset, .. })
509 if !boundary(end_offset) || end_offset < i.offset.saturating_add(i.size) =>
510 {
511 return Err(error(i.offset, "invalid loop boundary"));
512 }
513 Some(Control::Call { target, .. }) if !boundary(target) => {
514 return Err(error(i.offset, "invalid call target"));
515 }
516 _ => {}
517 }
518 }
519 Ok(Program {
520 instructions,
521 byte_len: bytes.len(),
522 })
523}
524
525fn register(r: Register) -> String {
526 format!("{}{}", if r.half { "h" } else { "r" }, r.index)
527}
528fn channels(s: [u8; 4]) -> String {
529 s.into_iter()
530 .map(|n| "xyzw".chars().nth(usize::from(n)).unwrap_or('?'))
531 .collect()
532}
533
534impl Program {
535 pub fn disassemble(&self) -> String {
538 let mut out = String::new();
539 for i in &self.instructions {
540 let _ = write!(out, "{:04x}: {}", i.offset, i.opcode.mnemonic());
541 if i.opcode.writes_destination() {
542 let mask: String = (0..4)
543 .zip("xyzw".chars())
544 .filter(|&(n, _)| bits(u32::from(i.write_mask), n, 1) != 0)
545 .map(|(_, channel)| channel)
546 .collect();
547 let _ = write!(
548 out,
549 " {}.{mask}",
550 if i.no_destination || i.destination.index == 63 {
551 format!("cc{}", i.condition_destination)
552 } else {
553 register(i.destination)
554 }
555 );
556 }
557 for s in &i.sources {
558 let source = match s.kind {
559 SourceKind::Register(r) => register(r),
560 SourceKind::Input(n) => format!("input[{n}]"),
561 SourceKind::Constant => format!("{:08x?}", i.constant),
562 };
563 let _ = write!(
564 out,
565 ", {}{}{}.{}",
566 if s.negate { "-" } else { "" },
567 if s.absolute { "abs " } else { "" },
568 source,
569 channels(s.swizzle)
570 );
571 }
572 if let Some(unit) = i.texture_unit {
573 let _ = write!(out, ", tex{unit}");
574 }
575 if let Some(control) = &i.control {
576 let _ = write!(out, " {control:?}");
577 }
578 let _ = writeln!(
579 out,
580 " ; pred={} cc{}.{}, sat={}, precision={}, scale={}, src_precision={:?}, end={}",
581 i.predicate.test,
582 i.predicate.register,
583 channels(i.predicate.swizzle),
584 i.saturate,
585 i.precision,
586 i.scale,
587 i.source_precision,
588 i.end
589 );
590 }
591 out
592 }
593}
594
595pub mod flow;