use std::collections::HashMap;
use crate::{
Add, AddressingMode, Div, Immediate, Instruction, Max, Mul, RegisterIndex,
Sub
};
pub struct ConstantCommuter;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Application
{
Offset(i32),
Scale(i32),
Quotient(i32),
Floor(i32)
}
impl ConstantCommuter
{
pub fn commute(instructions: &[Instruction]) -> Vec<Instruction>
{
let mut reads = HashMap::<RegisterIndex, usize>::new();
for instruction in instructions
{
for source in instruction.sources()
{
if let AddressingMode::Register(register) = source
{
*reads.entry(register).or_default() += 1;
}
}
}
let mut applications =
HashMap::<RegisterIndex, (RegisterIndex, Application)>::new();
let mut commuted = Vec::with_capacity(instructions.len());
for instruction in instructions
{
let mut instruction = canonical(instruction);
if let Some((dest, (operand, outer))) =
Self::application(&instruction)
{
let merged = applications
.get(&operand)
.filter(|_| reads.get(&operand) == Some(&1))
.and_then(|&(inner_operand, inner)| {
merge(inner, outer)
.map(|merged| (inner_operand, merged))
});
let (operand, application) = match merged
{
Some((inner_operand, merged)) =>
{
*reads.get_mut(&operand).unwrap() -= 1;
*reads.entry(inner_operand).or_default() += 1;
instruction = emit(dest, inner_operand, merged);
(inner_operand, merged)
},
None => (operand, outer)
};
applications.insert(dest, (operand, application));
}
commuted.push(instruction);
}
commuted
}
fn application(
instruction: &Instruction
) -> Option<(RegisterIndex, (RegisterIndex, Application))>
{
use AddressingMode::{Immediate as Imm, Register as Reg};
let (dest, operand, application) = match *instruction
{
Instruction::Add(Add {
dest,
op1: Imm(Immediate(c)),
op2: Reg(x)
}) => (dest, x, Application::Offset(c)),
Instruction::Sub(Sub {
dest,
op1: Reg(x),
op2: Imm(Immediate(c))
}) => (dest, x, Application::Offset(c.checked_neg()?)),
Instruction::Mul(Mul {
dest,
op1: Imm(Immediate(c)),
op2: Reg(x)
}) => (dest, x, Application::Scale(c)),
Instruction::Div(Div {
dest,
op1: Reg(x),
op2: Imm(Immediate(c))
}) => (dest, x, Application::Quotient(c)),
Instruction::Max(Max {
dest,
op1: Imm(Immediate(c)),
op2: Reg(x)
}) => (dest, x, Application::Floor(c)),
_ => return None
};
Some((dest, (operand, application)))
}
}
fn canonical(instruction: &Instruction) -> Instruction
{
let order = |op1: AddressingMode, op2: AddressingMode| {
if op2 < op1 { (op2, op1) } else { (op1, op2) }
};
match *instruction
{
Instruction::Add(Add { dest, op1, op2 }) =>
{
let (op1, op2) = order(op1, op2);
Add { dest, op1, op2 }.into()
},
Instruction::Mul(Mul { dest, op1, op2 }) =>
{
let (op1, op2) = order(op1, op2);
Mul { dest, op1, op2 }.into()
},
Instruction::Max(Max { dest, op1, op2 }) =>
{
let (op1, op2) = order(op1, op2);
Max { dest, op1, op2 }.into()
},
ref instruction => instruction.clone()
}
}
fn merge(inner: Application, outer: Application) -> Option<Application>
{
match (inner, outer)
{
(Application::Offset(a), Application::Offset(b))
if (a >= 0) == (b >= 0) || a == 0 || b == 0 =>
{
a.checked_add(b).map(Application::Offset)
},
(Application::Scale(a), Application::Scale(b)) if a > 0 && b > 0 =>
{
a.checked_mul(b).map(Application::Scale)
},
(Application::Quotient(a), Application::Quotient(b))
if a > 0 && b > 0 =>
{
a.checked_mul(b).map(Application::Quotient)
},
(Application::Floor(a), Application::Floor(b)) =>
{
Some(Application::Floor(a.max(b)))
},
_ => None
}
}
fn emit(
dest: RegisterIndex,
operand: RegisterIndex,
application: Application
) -> Instruction
{
let x = AddressingMode::Register(operand);
let c = |c| AddressingMode::Immediate(Immediate(c));
match application
{
Application::Offset(delta) if delta < 0 && delta != i32::MIN => Sub {
dest,
op1: x,
op2: c(-delta)
}
.into(),
Application::Offset(delta) => Add {
dest,
op1: c(delta),
op2: x
}
.into(),
Application::Scale(factor) => Mul {
dest,
op1: c(factor),
op2: x
}
.into(),
Application::Quotient(divisor) => Div {
dest,
op1: x,
op2: c(divisor)
}
.into(),
Application::Floor(floor) => Max {
dest,
op1: c(floor),
op2: x
}
.into()
}
}