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)]
90pub struct Source {
91 pub kind: SourceKind,
93 pub swizzle: [u8; 4],
95 pub negate: bool,
97 pub absolute: bool,
99}
100
101#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub struct Predicate {
104 pub test: u8,
106 pub swizzle: [u8; 4],
108 pub register: u8,
110}
111
112impl Predicate {
113 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#[derive(Clone, Debug, PartialEq, Eq)]
131pub enum Control {
132 If {
134 else_offset: usize,
136 end_offset: usize,
138 },
139 Loop {
141 count: u8,
143 initial: u8,
145 increment: u8,
147 end_offset: usize,
149 },
150 Call {
152 encoded: [u32; 3],
154 },
155 Break,
157 Return,
159}
160
161#[derive(Clone, Debug, PartialEq, Eq)]
163pub struct Instruction {
164 pub offset: usize,
166 pub size: usize,
168 pub opcode: Opcode,
170 pub words: [u32; 4],
172 pub end: bool,
174 pub destination: Register,
176 pub write_mask: u8,
178 pub no_destination: bool,
180 pub update_condition: bool,
182 pub condition_destination: u8,
184 pub predicate: Predicate,
186 pub saturate: bool,
188 pub precision: u8,
190 pub expand_texture: bool,
192 pub texture_unit: Option<u8>,
194 pub sources: Vec<Source>,
196 pub constant: Option<[u32; 4]>,
198 pub source_precision: [u8; 3],
200 pub scale: u8,
202 pub indexed_input: bool,
205 pub input_address: u16,
209 pub interpolant_flag: bool,
216 pub control: Option<Control>,
218}
219
220#[derive(Clone, Debug, PartialEq, Eq)]
222pub struct Program {
223 pub instructions: Vec<Instruction>,
225 pub byte_len: usize,
227}
228
229#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
231#[error("RSX fragment program at byte {offset}: {detail}")]
232pub struct Error {
233 pub offset: usize,
235 pub detail: String,
237}
238
239fn error(offset: usize, detail: impl Into<String>) -> Error {
240 Error {
241 offset,
242 detail: detail.into(),
243 }
244}
245fn bits(n: u32, shift: u32, width: u32) -> u32 {
247 let mask = 1u32.checked_shl(width).map_or(u32::MAX, |m| m.wrapping_sub(1));
248 n.checked_shr(shift).unwrap_or(0) & mask
249}
250fn bits8(n: u32, shift: u32, width: u32) -> u8 {
252 let [byte, ..] = bits(n, shift, width.min(8)).to_le_bytes();
253 byte
254}
255fn bits16(n: u32, shift: u32, width: u32) -> u16 {
257 let [a, b, ..] = bits(n, shift, width.min(16)).to_le_bytes();
258 u16::from_le_bytes([a, b])
259}
260fn swizzle(n: u32, shift: u32) -> [u8; 4] {
261 [0, 2, 4, 6].map(|k| bits8(n, shift.saturating_add(k), 2))
262}
263
264pub fn decode(bytes: &[u8]) -> Result<Program, Error> {
268 if bytes.is_empty() || !bytes.len().is_multiple_of(16) {
269 return Err(error(0, "fragment microcode must contain complete 16-byte slots"));
270 }
271 let view = View::new(bytes);
272 let slot = |at| -> Result<[u32; 4], Error> {
273 let data = view.slice(at, 16).map_err(|e| error(at, e.to_string()))?;
274 let (words, _) = data.as_chunks::<4>();
275 let [d, a, b, c] = words else {
276 return Err(error(at, "a 16-byte slot"));
277 };
278 Ok([d, a, b, c].map(|w| u32::from_be_bytes(*w).rotate_left(16)))
279 };
280 let mut at = 0;
281 let mut instructions = Vec::new();
282 loop {
283 let words = slot(at)?;
284 let [d, a, b, c] = words;
285 let code = bits8(d, 24, 6) | (bits8(b, 31, 1) << 6);
286 let opcode =
287 Opcode::read(code).ok_or_else(|| error(at, format!("unsupported fragment opcode {code:#x}")))?;
288 let mut sources = Vec::new();
289 for (i, raw) in [a, b, c].into_iter().take(opcode.source_count()).enumerate() {
290 let kind = match bits(raw, 0, 2) {
291 0 => SourceKind::Register(Register {
292 index: bits8(raw, 2, 6),
293 half: bits(raw, 8, 1) != 0,
294 }),
295 1 => SourceKind::Input(bits8(d, 13, 4)),
296 2 => SourceKind::Constant,
297 _ => {
298 return Err(error(
299 at.saturating_add(4).saturating_add(i.saturating_mul(4)),
300 "reserved source register type",
301 ));
302 }
303 };
304 sources.push(Source {
305 kind,
306 swizzle: swizzle(raw, 9),
307 negate: bits(raw, 17, 1) != 0,
308 absolute: bits(raw, if i == 0 { 29 } else { 18 }, 1) != 0,
309 });
310 }
311 let constant = if sources.iter().any(|s| s.kind == SourceKind::Constant) {
312 Some(slot(at.saturating_add(16))?)
313 } else {
314 None
315 };
316 let size = if constant.is_some() { 32 } else { 16 };
317 let control = match opcode {
318 Opcode::IFE => Some(Control::If {
319 else_offset: usize::try_from(b & 0x7fffffff)
320 .map_err(|_| error(at, "branch address overflow"))?
321 .checked_mul(4)
322 .ok_or_else(|| error(at, "branch address overflow"))?,
323 end_offset: usize::try_from(c)
324 .map_err(|_| error(at, "branch address overflow"))?
325 .checked_mul(4)
326 .ok_or_else(|| error(at, "branch address overflow"))?,
327 }),
328 Opcode::LOOP | Opcode::REP => Some(Control::Loop {
329 count: bits8(b, 2, 8),
330 initial: bits8(b, 10, 8),
331 increment: bits8(b, 19, 8),
332 end_offset: usize::try_from(c)
333 .map_err(|_| error(at, "loop address overflow"))?
334 .checked_mul(4)
335 .ok_or_else(|| error(at, "loop address overflow"))?,
336 }),
337 Opcode::CAL => Some(Control::Call { encoded: [a, b, c] }),
338 Opcode::BRK => Some(Control::Break),
339 Opcode::RET => Some(Control::Return),
340 _ => None,
341 };
342 let end = d & 1 != 0;
343 instructions.push(Instruction {
344 offset: at,
345 size,
346 opcode,
347 words,
348 end,
349 destination: Register {
350 index: bits8(d, 1, 6),
351 half: bits(d, 7, 1) != 0,
352 },
353 write_mask: bits8(d, 9, 4),
354 no_destination: bits(d, 30, 1) != 0,
355 update_condition: bits(d, 8, 1) != 0,
356 condition_destination: bits8(a, 30, 1),
357 predicate: Predicate {
358 test: bits8(a, 18, 3),
359 swizzle: swizzle(a, 21),
360 register: bits8(a, 31, 1),
361 },
362 saturate: bits(d, 31, 1) != 0,
363 precision: bits8(d, 22, 2),
364 expand_texture: bits(d, 21, 1) != 0,
365 texture_unit: opcode.samples_texture().then_some(bits8(d, 17, 4)),
366 sources,
367 constant,
368 source_precision: [bits8(b, 19, 3), bits8(b, 22, 3), bits8(b, 25, 3)],
369 scale: bits8(b, 28, 3),
370 indexed_input: bits(c, 30, 1) != 0,
371 input_address: bits16(c, 19, 11),
372 interpolant_flag: bits(c, 31, 1) != 0,
373 control,
374 });
375 at = at.saturating_add(size);
376 if end {
377 if at != bytes.len() {
378 return Err(error(at, "bytes after END instruction"));
379 }
380 break;
381 }
382 if at == bytes.len() {
383 return Err(error(at, "fragment program has no END instruction"));
384 }
385 }
386 let boundary = |offset| offset == bytes.len() || instructions.iter().any(|i| i.offset == offset);
387 for i in &instructions {
388 match i.control {
389 Some(Control::If {
390 else_offset,
391 end_offset,
392 }) if !boundary(else_offset)
393 || !boundary(end_offset)
394 || else_offset < i.offset.saturating_add(i.size)
395 || end_offset < else_offset =>
396 {
397 return Err(error(i.offset, "invalid IF boundaries"));
398 }
399 Some(Control::Loop { end_offset, .. })
400 if !boundary(end_offset) || end_offset < i.offset.saturating_add(i.size) =>
401 {
402 return Err(error(i.offset, "invalid loop boundary"));
403 }
404 _ => {}
405 }
406 }
407 Ok(Program {
408 instructions,
409 byte_len: bytes.len(),
410 })
411}
412
413fn register(r: Register) -> String {
414 format!("{}{}", if r.half { "h" } else { "r" }, r.index)
415}
416fn channels(s: [u8; 4]) -> String {
417 s.into_iter()
418 .map(|n| "xyzw".chars().nth(usize::from(n)).unwrap_or('?'))
419 .collect()
420}
421
422impl Program {
423 pub fn disassemble(&self) -> String {
426 let mut out = String::new();
427 for i in &self.instructions {
428 let _ = write!(out, "{:04x}: {}", i.offset, i.opcode.mnemonic());
429 if i.opcode.writes_destination() {
430 let mask: String = (0..4)
431 .zip("xyzw".chars())
432 .filter(|&(n, _)| bits(u32::from(i.write_mask), n, 1) != 0)
433 .map(|(_, channel)| channel)
434 .collect();
435 let _ = write!(
436 out,
437 " {}.{mask}",
438 if i.no_destination || i.destination.index == 63 {
439 format!("cc{}", i.condition_destination)
440 } else {
441 register(i.destination)
442 }
443 );
444 }
445 for s in &i.sources {
446 let source = match s.kind {
447 SourceKind::Register(r) => register(r),
448 SourceKind::Input(n) => format!("input[{n}]"),
449 SourceKind::Constant => format!("{:08x?}", i.constant),
450 };
451 let _ = write!(
452 out,
453 ", {}{}{}.{}",
454 if s.negate { "-" } else { "" },
455 if s.absolute { "abs " } else { "" },
456 source,
457 channels(s.swizzle)
458 );
459 }
460 if let Some(unit) = i.texture_unit {
461 let _ = write!(out, ", tex{unit}");
462 }
463 if let Some(control) = &i.control {
464 let _ = write!(out, " {control:?}");
465 }
466 let _ = writeln!(
467 out,
468 " ; pred={} cc{}.{}, sat={}, precision={}, scale={}, src_precision={:?}, end={}",
469 i.predicate.test,
470 i.predicate.register,
471 channels(i.predicate.swizzle),
472 i.saturate,
473 i.precision,
474 i.scale,
475 i.source_precision,
476 i.end
477 );
478 }
479 out
480 }
481}
482
483pub mod flow;