use {
crate::{
backend::{
allocate_input_variable, hardware_register_allocation, reserve_output_variable,
AllocatedVariable, RegisterBank, RegisterMapping,
},
codegen::{
generate_rust_global_asm, generate_rust_includable_asm, generate_rust_inline_asm,
},
frontend::{Assembler, FreshAllocator, FreshVariable},
ir::{HardwareRegister, Instruction, Variable},
liveness::liveness_analysis,
AtomicInstructionBlock,
},
std::{
collections::{hash_map::Entry, HashMap},
path::Path,
},
};
pub type Setup =
fn(alloc: &mut FreshAllocator, asm: &mut Assembler) -> (Vec<FreshVariable>, FreshVariable);
pub fn build_single<P: AsRef<Path>>(path: P, label: &str, f: Setup) {
build_standalone(path, label, Interleaving::single(f));
}
pub fn build_standalone<P: AsRef<Path>>(path: P, label: &str, algos: Interleaving<Setup>) {
build(path, algos, |inputs, outputs, instructions| {
generate_rust_global_asm(label, inputs, outputs, instructions)
})
}
pub fn build_includable<P: AsRef<Path>>(path: P, algos: Interleaving<Setup>) {
build(path, algos, generate_rust_includable_asm)
}
pub fn build_inline<P: AsRef<Path>>(path: P, algos: Interleaving<Setup>) {
build(path, algos, generate_rust_inline_asm)
}
pub fn build<P, C>(path: P, algos: Interleaving<Setup>, codegen: C)
where
P: AsRef<Path>,
C: FnOnce(
&[AllocatedVariable],
&[AllocatedVariable],
&[Instruction<HardwareRegister>],
) -> String,
{
let mut alloc = FreshAllocator::new();
let mut mapping = RegisterMapping::new();
let mut register_bank = RegisterBank::new();
let (input_hw_registers, output_hw_registers, instructions) = run_setups(&mut alloc, algos);
let input_hw_registers = unique_variable(input_hw_registers);
let output_hw_registers = unique_variable(output_hw_registers);
let instructions: Vec<_> = instructions.into_iter().flatten().collect();
let (releases, lifetimes) = liveness_analysis(&alloc, &output_hw_registers, &instructions);
let input_hw_registers = allocate_input_variable(
&mut mapping,
&mut register_bank,
input_hw_registers,
&lifetimes,
);
output_hw_registers.iter().for_each(|variable| {
reserve_output_variable(&mut register_bank, &lifetimes, variable);
});
let hardware_instructions = hardware_register_allocation(
&mut mapping,
&mut register_bank,
instructions,
releases,
lifetimes,
);
let output_hw_registers: Vec<_> = output_hw_registers
.iter()
.map(|fresh_variable| mapping.get_allocated_variable(fresh_variable))
.collect();
let assembly = codegen(
&input_hw_registers,
&output_hw_registers,
&hardware_instructions,
);
use std::io::Write;
let mut file = std::fs::File::create(&path)
.unwrap_or_else(|_| panic!("Unable to create file: {:#?}", path.as_ref()));
file.write_all(assembly.as_bytes())
.unwrap_or_else(|_| panic!("Unable to write assembly to file: {:#?}", path.as_ref()));
}
fn run_setups(
alloc: &mut FreshAllocator,
algos: Interleaving<Setup>,
) -> (
Vec<FreshVariable>, // inputs
Vec<FreshVariable>, // outputs
Vec<AtomicInstructionBlock>,
) {
match algos {
Interleaving::Seq(items) => items.into_iter().fold(
(Vec::new(), Vec::new(), Vec::new()),
|(mut inputs, mut outputs, mut instructions), func| {
let (input, output, instrs) = run_setup(alloc, func);
inputs.extend(input);
outputs.push(output);
instructions.extend(instrs);
(inputs, outputs, instructions)
},
),
Interleaving::Par(left, right) => {
let (inputs_left, outputs_left, instructions_left) = run_setups(alloc, *left);
let (inputs_right, outputs_right, instructions_right) = run_setups(alloc, *right);
let mut inputs = inputs_left;
inputs.extend(inputs_right);
let mut outputs = outputs_left;
outputs.extend(outputs_right);
let instructions = interleave(instructions_left, instructions_right);
(inputs, outputs, instructions)
}
}
}
fn run_setup(
alloc: &mut FreshAllocator,
f: Setup,
) -> (
Vec<FreshVariable>,
FreshVariable,
Vec<AtomicInstructionBlock>,
) {
let mut asm = Assembler::new();
let (inputs, outputs) = f(alloc, &mut asm);
(inputs, outputs, asm.instructions)
}
fn unique_variable<T>(variables: Vec<Variable<T>>) -> Vec<Variable<T>> {
let mut variable_count: HashMap<String, u8> = HashMap::new();
variables
.into_iter()
.map(|variable| {
let label = variable.label.clone();
match variable_count.entry(label.clone()) {
Entry::Vacant(entry) => {
entry.insert(1);
variable
}
Entry::Occupied(mut entry) => {
let count = entry.get_mut();
let new_label = format!("{}{}", label, *count);
*count += 1;
Variable {
label: new_label,
registers: variable.registers,
}
}
}
})
.collect()
}
pub enum Interleaving<T> {
Seq(Vec<T>),
Par(Box<Interleaving<T>>, Box<Interleaving<T>>),
}
impl<T> Interleaving<T> {
pub fn single(t: T) -> Self {
Interleaving::Seq(vec![t])
}
pub fn seq(t: Vec<T>) -> Self {
Interleaving::Seq(t)
}
pub fn par(t1: Interleaving<T>, t2: Interleaving<T>) -> Self {
Interleaving::Par(Box::new(t1), Box::new(t2))
}
}
fn interleave<T>(lhs: Vec<T>, rhs: Vec<T>) -> Vec<T> {
let (shorter, longer) = if lhs.len() <= rhs.len() {
(lhs, rhs)
} else {
(rhs, lhs)
};
if shorter.is_empty() {
return longer;
}
let mut result = Vec::with_capacity(shorter.len() + longer.len());
let short_len = shorter.len();
let mut short_iter = shorter.into_iter().enumerate();
let long_len = longer.len();
let mut long_iter = longer.into_iter();
let mut next = long_len / short_len;
for i in 0..=long_len {
if i == next {
if let Some((short_index, item)) = short_iter.next() {
result.push(item);
next = ((short_index + 2) * long_len) / short_len;
}
}
if let Some(item) = long_iter.next() {
result.push(item)
}
}
assert!(short_iter.next().is_none());
result
}
#[cfg(test)]
mod test {
use quickcheck_macros::quickcheck;
#[quickcheck]
fn interleave(lhs: Vec<u64>, rhs: Vec<u64>) -> bool {
let left = lhs.len();
let right = rhs.len();
let res = super::interleave(lhs, rhs);
res.len() == left + right
}
}