use std::collections::HashMap; use std::sync::atomic::{AtomicU32, Ordering}; use crate::asm::data::{Rd, RdData}; use crate::asm::data::literal::{Label, Value}; use crate::asm::error::{AsmError, CrsnError}; use crate::asm::instr::{Cond, InstrWithBranches, Op, Routine}; use crate::asm::instr::op::OpKind; use crate::builtin::defs::Barrier; use crate::builtin::defs::BuiltinOp; /// A trait for something that can turn into multiple instructions pub trait Flatten { fn flatten(self: Box, label_num: &AtomicU32) -> Result, CrsnError>; } impl Flatten for () { fn flatten(self: Box, _label_num: &AtomicU32) -> Result, CrsnError> { Ok(vec![]) } } impl Flatten for InstrWithBranches { fn flatten(self: Box, label_num: &AtomicU32) -> Result, CrsnError> { let mut ops = vec![self.op]; if let Some(branches) = self.branches { let labels = HashMap::::new(); let branch_count = branches.len(); let end_lbl = Label::unique(label_num); for (cnt, (cond, branch)) in branches.into_iter().enumerate() { if labels.contains_key(&cond) { return Err(CrsnError::Asm(AsmError::ConditionalAlreadyUsed(cond))); } let next_lbl = if cnt == branch_count - 1 { end_lbl.clone() } else { Label::unique(label_num) }; let mut flattened = branch.flatten(label_num)?; if flattened.len() == 0 { ops.push(Op { cond: Some(cond), kind: BuiltinOp::Jump(end_lbl.clone()).into() }); } else if flattened.len() == 1 && flattened[0].cond.is_none() && branch_count == 1 { // optimization for single-branch conditionals with a single instruction ops.push(Op { cond: Some(cond), kind: flattened.remove(0).kind }); } else { ops.push(Op { kind: OpKind::BuiltIn(BuiltinOp::Jump(next_lbl.clone())), cond: Some(!cond), }); ops.extend(flattened); } if cnt != branch_count - 1 { ops.push(BuiltinOp::Jump(end_lbl.clone()).into()); ops.push(BuiltinOp::Label(next_lbl).into()); } } ops.push(BuiltinOp::Label(end_lbl).into()); } Ok(ops) } } impl Flatten for Vec> { fn flatten(self: Box, label_num: &AtomicU32) -> Result, CrsnError> { let mut ops = vec![]; for item in self.into_iter() { ops.extend(item.flatten(label_num)?); } labels_to_skips(ops) } } impl Flatten for Routine { fn flatten(self: Box, label_num: &AtomicU32) -> Result, CrsnError> { let skip_label = Label::unique(label_num); let mut ops: Vec = vec![ BuiltinOp::Barrier { kind: Barrier::Open(skip_label.clone()), msg: Some(format!("proc {} start", self.name).into()), }.into(), BuiltinOp::Routine(self.name.clone()).into(), ]; ops.extend(self.body.flatten(label_num)?); ops.push( BuiltinOp::Barrier { kind: Barrier::Close(skip_label.clone()), msg: Some(format!("proc {} end", self.name).into()), }.into() ); labels_to_skips(ops) } } /// Convert jumps to relative skips pub fn labels_to_skips(ops: Vec) -> Result, CrsnError> { let mut label_positions = HashMap::::new(); for (n, op) in ops.iter().enumerate() { if let OpKind::BuiltIn(BuiltinOp::Label(name)) = &op.kind { label_positions.insert(name.clone(), n - label_positions.len()); } } let mut cleaned = vec![]; let mut skipped = 0; for (n, op) in ops.into_iter().enumerate() { match op.kind { OpKind::BuiltIn(BuiltinOp::Label(_)) => { skipped += 1; } OpKind::BuiltIn(BuiltinOp::Jump(target)) => { if let Some(dest) = label_positions.get(&target) { let skip = *dest as isize - n as isize + skipped; cleaned.push(Op { cond: op.cond, kind: OpKind::BuiltIn(BuiltinOp::Skip(Rd::new(RdData::Immediate(skip as Value)))), }); } else { return Err(CrsnError::Asm(AsmError::LabelNotDefined(target))); } } _ => { cleaned.push(op); } } } Ok(cleaned) }