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 samples_texture(self) -> bool {
48 matches!(self, Self::TEX | Self::TXP | Self::TXD | Self::TXL | Self::TXB)
49 }
50 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#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
70pub struct Register {
71 pub index: u8,
73 pub half: bool,
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
79pub enum SourceKind {
80 Register(Register),
82 Input(u8),
84 Constant,
86}
87
88#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub enum InputSource {
98 Color0,
100 TexCoord(u8),
102 Other(u8),
104}
105
106impl InputSource {
107 const TEXCOORD_BASE: u8 = 4;
109 const TEXCOORDS: u8 = 10;
111
112 pub fn from_index(index: u8) -> Self {
114 match index {
115 1 => Self::Color0,
116 i => match i.checked_sub(Self::TEXCOORD_BASE) {
117 Some(k) if k < Self::TEXCOORDS => Self::TexCoord(k),
118 _ => Self::Other(i),
119 },
120 }
121 }
122
123 pub fn index(self) -> u8 {
125 match self {
126 Self::Color0 => 1,
127 Self::TexCoord(k) => Self::TEXCOORD_BASE.saturating_add(k),
128 Self::Other(i) => i,
129 }
130 }
131}
132
133impl SourceKind {
134 pub fn input_source(self) -> Option<InputSource> {
136 match self {
137 Self::Input(index) => Some(InputSource::from_index(index)),
138 _ => None,
139 }
140 }
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145pub struct Source {
146 pub kind: SourceKind,
148 pub swizzle: [u8; 4],
150 pub negate: bool,
152 pub absolute: bool,
154}
155
156#[derive(Clone, Copy, Debug, PartialEq, Eq)]
158pub struct Predicate {
159 pub test: u8,
161 pub swizzle: [u8; 4],
163 pub register: u8,
165}
166
167impl Predicate {
168 pub fn condition(self) -> &'static str {
170 match self.test {
171 0 => "FL",
172 1 => "LT",
173 2 => "EQ",
174 3 => "LE",
175 4 => "GT",
176 5 => "NE",
177 6 => "GE",
178 7 => "TR",
179 _ => "invalid",
180 }
181 }
182}
183
184#[derive(Clone, Debug, PartialEq, Eq)]
186pub enum Control {
187 If {
189 else_offset: usize,
191 end_offset: usize,
193 },
194 Loop {
196 count: u8,
198 initial: u8,
200 increment: u8,
202 end_offset: usize,
204 },
205 Call {
207 encoded: [u32; 3],
209 },
210 Break,
212 Return,
214}
215
216#[derive(Clone, Debug, PartialEq, Eq)]
218pub struct Instruction {
219 pub offset: usize,
221 pub size: usize,
223 pub opcode: Opcode,
225 pub words: [u32; 4],
227 pub end: bool,
229 pub destination: Register,
231 pub write_mask: u8,
233 pub no_destination: bool,
235 pub update_condition: bool,
237 pub condition_destination: u8,
239 pub predicate: Predicate,
241 pub saturate: bool,
243 pub precision: u8,
245 pub expand_texture: bool,
247 pub texture_unit: Option<u8>,
249 pub sources: Vec<Source>,
251 pub constant: Option<[u32; 4]>,
253 pub source_precision: [u8; 3],
255 pub scale: u8,
257 pub indexed_input: bool,
260 pub input_address: u16,
264 pub interpolant_flag: bool,
271 pub control: Option<Control>,
273}
274
275#[derive(Clone, Debug, PartialEq, Eq)]
277pub struct Program {
278 pub instructions: Vec<Instruction>,
280 pub byte_len: usize,
282}
283
284#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
286#[error("RSX fragment program at byte {offset}: {detail}")]
287pub struct Error {
288 pub offset: usize,
290 pub detail: String,
292}
293
294fn error(offset: usize, detail: impl Into<String>) -> Error {
295 Error {
296 offset,
297 detail: detail.into(),
298 }
299}
300fn bits(n: u32, shift: u32, width: u32) -> u32 {
302 let mask = 1u32.checked_shl(width).map_or(u32::MAX, |m| m.wrapping_sub(1));
303 n.checked_shr(shift).unwrap_or(0) & mask
304}
305fn bits8(n: u32, shift: u32, width: u32) -> u8 {
307 let [byte, ..] = bits(n, shift, width.min(8)).to_le_bytes();
308 byte
309}
310fn bits16(n: u32, shift: u32, width: u32) -> u16 {
312 let [a, b, ..] = bits(n, shift, width.min(16)).to_le_bytes();
313 u16::from_le_bytes([a, b])
314}
315fn swizzle(n: u32, shift: u32) -> [u8; 4] {
316 [0, 2, 4, 6].map(|k| bits8(n, shift.saturating_add(k), 2))
317}
318
319pub fn decode(bytes: &[u8]) -> Result<Program, Error> {
323 if bytes.is_empty() || !bytes.len().is_multiple_of(16) {
324 return Err(error(0, "fragment microcode must contain complete 16-byte slots"));
325 }
326 let view = View::new(bytes);
327 let slot = |at| -> Result<[u32; 4], Error> {
328 let data = view.slice(at, 16).map_err(|e| error(at, e.to_string()))?;
329 let (words, _) = data.as_chunks::<4>();
330 let [d, a, b, c] = words else {
331 return Err(error(at, "a 16-byte slot"));
332 };
333 Ok([d, a, b, c].map(|w| u32::from_be_bytes(*w).rotate_left(16)))
334 };
335 let mut at = 0;
336 let mut instructions = Vec::new();
337 loop {
338 let words = slot(at)?;
339 let [d, a, b, c] = words;
340 let code = bits8(d, 24, 6) | (bits8(b, 31, 1) << 6);
341 let opcode =
342 Opcode::read(code).ok_or_else(|| error(at, format!("unsupported fragment opcode {code:#x}")))?;
343 let mut sources = Vec::new();
344 for (i, raw) in [a, b, c].into_iter().take(opcode.source_count()).enumerate() {
345 let kind = match bits(raw, 0, 2) {
346 0 => SourceKind::Register(Register {
347 index: bits8(raw, 2, 6),
348 half: bits(raw, 8, 1) != 0,
349 }),
350 1 => SourceKind::Input(bits8(d, 13, 4)),
351 2 => SourceKind::Constant,
352 _ => {
353 return Err(error(
354 at.saturating_add(4).saturating_add(i.saturating_mul(4)),
355 "reserved source register type",
356 ));
357 }
358 };
359 sources.push(Source {
360 kind,
361 swizzle: swizzle(raw, 9),
362 negate: bits(raw, 17, 1) != 0,
363 absolute: bits(raw, if i == 0 { 29 } else { 18 }, 1) != 0,
364 });
365 }
366 let constant = if sources.iter().any(|s| s.kind == SourceKind::Constant) {
367 Some(slot(at.saturating_add(16))?)
368 } else {
369 None
370 };
371 let size = if constant.is_some() { 32 } else { 16 };
372 let control = match opcode {
373 Opcode::IFE => Some(Control::If {
374 else_offset: usize::try_from(b & 0x7fffffff)
375 .map_err(|_| error(at, "branch address overflow"))?
376 .checked_mul(4)
377 .ok_or_else(|| error(at, "branch address overflow"))?,
378 end_offset: usize::try_from(c)
379 .map_err(|_| error(at, "branch address overflow"))?
380 .checked_mul(4)
381 .ok_or_else(|| error(at, "branch address overflow"))?,
382 }),
383 Opcode::LOOP | Opcode::REP => Some(Control::Loop {
384 count: bits8(b, 2, 8),
385 initial: bits8(b, 10, 8),
386 increment: bits8(b, 19, 8),
387 end_offset: usize::try_from(c)
388 .map_err(|_| error(at, "loop address overflow"))?
389 .checked_mul(4)
390 .ok_or_else(|| error(at, "loop address overflow"))?,
391 }),
392 Opcode::CAL => Some(Control::Call { encoded: [a, b, c] }),
393 Opcode::BRK => Some(Control::Break),
394 Opcode::RET => Some(Control::Return),
395 _ => None,
396 };
397 let end = d & 1 != 0;
398 instructions.push(Instruction {
399 offset: at,
400 size,
401 opcode,
402 words,
403 end,
404 destination: Register {
405 index: bits8(d, 1, 6),
406 half: bits(d, 7, 1) != 0,
407 },
408 write_mask: bits8(d, 9, 4),
409 no_destination: bits(d, 30, 1) != 0,
410 update_condition: bits(d, 8, 1) != 0,
411 condition_destination: bits8(a, 30, 1),
412 predicate: Predicate {
413 test: bits8(a, 18, 3),
414 swizzle: swizzle(a, 21),
415 register: bits8(a, 31, 1),
416 },
417 saturate: bits(d, 31, 1) != 0,
418 precision: bits8(d, 22, 2),
419 expand_texture: bits(d, 21, 1) != 0,
420 texture_unit: opcode.samples_texture().then_some(bits8(d, 17, 4)),
421 sources,
422 constant,
423 source_precision: [bits8(b, 19, 3), bits8(b, 22, 3), bits8(b, 25, 3)],
424 scale: bits8(b, 28, 3),
425 indexed_input: bits(c, 30, 1) != 0,
426 input_address: bits16(c, 19, 11),
427 interpolant_flag: bits(c, 31, 1) != 0,
428 control,
429 });
430 at = at.saturating_add(size);
431 if end {
432 if at != bytes.len() {
433 return Err(error(at, "bytes after END instruction"));
434 }
435 break;
436 }
437 if at == bytes.len() {
438 return Err(error(at, "fragment program has no END instruction"));
439 }
440 }
441 let boundary = |offset| offset == bytes.len() || instructions.iter().any(|i| i.offset == offset);
442 for i in &instructions {
443 match i.control {
444 Some(Control::If {
445 else_offset,
446 end_offset,
447 }) if !boundary(else_offset)
448 || !boundary(end_offset)
449 || else_offset < i.offset.saturating_add(i.size)
450 || end_offset < else_offset =>
451 {
452 return Err(error(i.offset, "invalid IF boundaries"));
453 }
454 Some(Control::Loop { end_offset, .. })
455 if !boundary(end_offset) || end_offset < i.offset.saturating_add(i.size) =>
456 {
457 return Err(error(i.offset, "invalid loop boundary"));
458 }
459 _ => {}
460 }
461 }
462 Ok(Program {
463 instructions,
464 byte_len: bytes.len(),
465 })
466}
467
468fn register(r: Register) -> String {
469 format!("{}{}", if r.half { "h" } else { "r" }, r.index)
470}
471fn channels(s: [u8; 4]) -> String {
472 s.into_iter()
473 .map(|n| "xyzw".chars().nth(usize::from(n)).unwrap_or('?'))
474 .collect()
475}
476
477impl Program {
478 pub fn disassemble(&self) -> String {
481 let mut out = String::new();
482 for i in &self.instructions {
483 let _ = write!(out, "{:04x}: {}", i.offset, i.opcode.mnemonic());
484 if i.opcode.writes_destination() {
485 let mask: String = (0..4)
486 .zip("xyzw".chars())
487 .filter(|&(n, _)| bits(u32::from(i.write_mask), n, 1) != 0)
488 .map(|(_, channel)| channel)
489 .collect();
490 let _ = write!(
491 out,
492 " {}.{mask}",
493 if i.no_destination || i.destination.index == 63 {
494 format!("cc{}", i.condition_destination)
495 } else {
496 register(i.destination)
497 }
498 );
499 }
500 for s in &i.sources {
501 let source = match s.kind {
502 SourceKind::Register(r) => register(r),
503 SourceKind::Input(n) => format!("input[{n}]"),
504 SourceKind::Constant => format!("{:08x?}", i.constant),
505 };
506 let _ = write!(
507 out,
508 ", {}{}{}.{}",
509 if s.negate { "-" } else { "" },
510 if s.absolute { "abs " } else { "" },
511 source,
512 channels(s.swizzle)
513 );
514 }
515 if let Some(unit) = i.texture_unit {
516 let _ = write!(out, ", tex{unit}");
517 }
518 if let Some(control) = &i.control {
519 let _ = write!(out, " {control:?}");
520 }
521 let _ = writeln!(
522 out,
523 " ; pred={} cc{}.{}, sat={}, precision={}, scale={}, src_precision={:?}, end={}",
524 i.predicate.test,
525 i.predicate.register,
526 channels(i.predicate.swizzle),
527 i.saturate,
528 i.precision,
529 i.scale,
530 i.source_precision,
531 i.end
532 );
533 }
534 out
535 }
536}
537
538pub mod flow;