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,
204 pub input_address: u16,
206 pub perspective_input: bool,
208 pub control: Option<Control>,
210}
211
212#[derive(Clone, Debug, PartialEq, Eq)]
214pub struct Program {
215 pub instructions: Vec<Instruction>,
217 pub byte_len: usize,
219}
220
221#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
223#[error("RSX fragment program at byte {offset}: {detail}")]
224pub struct Error {
225 pub offset: usize,
227 pub detail: String,
229}
230
231fn error(offset: usize, detail: impl Into<String>) -> Error {
232 Error {
233 offset,
234 detail: detail.into(),
235 }
236}
237fn 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}
242fn bits8(n: u32, shift: u32, width: u32) -> u8 {
244 let [byte, ..] = bits(n, shift, width.min(8)).to_le_bytes();
245 byte
246}
247fn 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
256pub 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 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;