use super::{Control, Error, Instruction, Opcode, Program, Register, SourceKind, error};
use std::collections::BTreeSet;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Dependencies {
pub textures: u16,
pub inputs: u16,
}
impl Dependencies {
fn join(self, other: Self) -> Self {
Self {
textures: self.textures | other.textures,
inputs: self.inputs | other.inputs,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Sample {
pub offset: usize,
pub unit: u8,
pub coordinates: Dependencies,
pub arguments: Dependencies,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Output {
pub register: Register,
pub components: [Dependencies; 4],
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Analysis {
pub samples: Vec<Sample>,
pub outputs: Vec<Output>,
pub kill: Dependencies,
pub consumers: [Vec<Opcode>; 16],
}
#[derive(Clone)]
struct State {
lanes: [Dependencies; 512],
cc: [[Dependencies; 4]; 2],
control: Dependencies,
}
impl Default for State {
fn default() -> Self {
Self {
lanes: [Dependencies::default(); 512],
cc: [[Dependencies::default(); 4]; 2],
control: Dependencies::default(),
}
}
}
fn unit_bit(n: usize) -> u16 {
u32::try_from(n)
.ok()
.and_then(|n| 1u16.checked_shl(n))
.unwrap_or(0)
}
fn positions(r: Register, c: usize) -> std::ops::Range<usize> {
if r.half {
let at = usize::from(r.index).saturating_mul(4).saturating_add(c);
at..at.saturating_add(1)
} else {
let at = usize::from(r.index)
.saturating_mul(8)
.saturating_add(c.saturating_mul(2));
at..at.saturating_add(2)
}
}
impl State {
fn read(&self, r: Register, c: usize) -> Dependencies {
positions(r, c)
.map(|n| self.lanes.get(n).copied().unwrap_or_default())
.fold(Dependencies::default(), Dependencies::join)
}
fn merge(&mut self, other: &Self) {
for (a, b) in self.lanes.iter_mut().zip(other.lanes) {
*a = a.join(b);
}
for (a, b) in self.cc.iter_mut().flatten().zip(other.cc.into_iter().flatten()) {
*a = a.join(b);
}
self.control = self.control.join(other.control);
}
fn predicate(&self, i: &Instruction) -> Dependencies {
if matches!(i.predicate.test, 0 | 7) {
Dependencies::default()
} else {
i.predicate
.swizzle
.iter()
.map(|&c| {
self.cc
.get(usize::from(i.predicate.register))
.and_then(|row| row.get(usize::from(c)))
.copied()
.unwrap_or_default()
})
.fold(Dependencies::default(), Dependencies::join)
}
}
}
pub fn analyze(program: &Program, outputs: &[Register]) -> Result<Analysis, Error> {
if outputs.iter().any(|r| r.index > 63) {
return Err(error(0, "output register index exceeds encoding"));
}
let code = &program.instructions;
let mut incoming: Vec<Option<State>> = vec![None; code.len().saturating_add(1)];
if let Some(first) = incoming.first_mut() {
*first = Some(State::default());
}
let mut exits: Option<State> = None;
let mut samples = Vec::new();
let mut consumers: [BTreeSet<u8>; 16] = std::array::from_fn(|_| BTreeSet::new());
let mut kill = Dependencies::default();
let mut branches = Vec::new();
let index = |offset| -> usize { code.iter().position(|i| i.offset == offset).unwrap_or(code.len()) };
for (n, i) in code.iter().enumerate() {
if i.indexed_input && i.sources.iter().any(|s| matches!(s.kind, SourceKind::Input(_))) {
return Err(error(
i.offset,
"texture flow analysis does not support indexed interpolators",
));
}
match i.control {
Some(Control::If {
else_offset,
end_offset,
}) => branches.push((n, index(else_offset), index(end_offset))),
Some(Control::Loop { .. } | Control::Repeat { .. } | Control::Call { .. } | Control::Break) => {
return Err(error(
i.offset,
"texture flow analysis does not support calls, loops or BRK",
));
}
_ => {}
}
}
for &(start, _, end) in &branches {
if branches.iter().any(|&(s, _, e)| start < s && s < end && e > end) {
return Err(error(
code.get(start).map_or(0, |i| i.offset),
"crossing IF regions in flow analysis",
));
}
}
let merge_exit = |exits: &mut Option<State>, state: &State| {
if let Some(old) = exits {
old.merge(state);
} else {
*exits = Some(state.clone());
}
};
for (n, i) in code.iter().enumerate() {
let Some(mut state) = incoming.get_mut(n).and_then(Option::take) else {
continue;
};
let predicate = state.predicate(i);
let mut source = Dependencies::default();
let mut coordinates = Dependencies::default();
for (operand, s) in i.sources.iter().enumerate() {
let dependency = match s.kind {
SourceKind::Register(r) => s
.swizzle
.iter()
.map(|&c| state.read(r, usize::from(c)))
.fold(Dependencies::default(), Dependencies::join),
SourceKind::Input(k) => Dependencies {
textures: 0,
inputs: unit_bit(usize::from(k)),
},
SourceKind::Constant => Dependencies::default(),
};
if operand == 0 {
coordinates = dependency;
}
source = source.join(dependency);
}
let influenced = source.join(predicate).join(state.control);
if i.predicate.test != 0 {
for (unit, set) in consumers.iter_mut().enumerate() {
if influenced.textures & unit_bit(unit) != 0 {
set.insert(i.opcode as u8);
}
}
let mut result = influenced;
if let Some(unit) = i.texture_unit {
samples.push(Sample {
offset: i.offset,
unit,
coordinates,
arguments: source,
});
result.textures |= unit_bit(usize::from(unit));
}
if i.opcode.writes_destination() {
for c in 0..4 {
if i.write_mask & unit_bit(c).to_le_bytes()[0] == 0 {
continue;
}
if !i.no_destination && i.destination.index != 63 {
for at in positions(i.destination, c) {
if let Some(lane) = state.lanes.get_mut(at) {
*lane = if i.predicate.test == 7 {
result
} else {
lane.join(result)
};
}
}
}
if i.update_condition
&& let Some(cc) = state
.cc
.get_mut(usize::from(i.condition_destination))
.and_then(|row| row.get_mut(c))
{
*cc = if i.predicate.test == 7 {
result
} else {
cc.join(result)
};
}
}
}
if i.opcode == Opcode::KIL {
kill = kill.join(predicate).join(state.control);
}
}
let mut next = Vec::new();
let returns = matches!(i.control, Some(Control::Return));
let kills = i.opcode == Opcode::KIL;
if returns && i.predicate.test != 0 {
state.control = state.control.join(predicate);
}
if (i.end || returns && i.predicate.test != 0) && !(kills && i.predicate.test == 7) {
merge_exit(&mut exits, &state);
}
if !i.end && (!(returns || kills) || i.predicate.test != 7) {
if let Some(Control::If {
else_offset,
end_offset,
}) = i.control
{
state.control = state.control.join(predicate);
if i.predicate.test != 0 {
next.push(if n.saturating_add(1) == index(else_offset) {
index(end_offset)
} else {
n.saturating_add(1)
});
}
if i.predicate.test != 7 {
next.push(index(else_offset));
}
} else {
next.push(n.saturating_add(1));
}
}
for mut successor in next {
for &(start, else_at, end_at) in branches.iter().rev() {
if start < n && n < else_at && successor == else_at && else_at != end_at {
successor = end_at;
}
}
if let Some(slot) = incoming.get_mut(successor) {
match slot {
Some(old) => old.merge(&state),
None => *slot = Some(state.clone()),
}
}
}
}
if let Some(state) = incoming.last().and_then(Option::as_ref) {
merge_exit(&mut exits, state);
}
let state = exits.unwrap_or_default();
Ok(Analysis {
samples,
outputs: outputs
.iter()
.map(|&r| Output {
register: r,
components: std::array::from_fn(|c| state.read(r, c)),
})
.collect(),
kill,
consumers: consumers.map(|set| set.into_iter().filter_map(Opcode::read).collect()),
})
}