diff --git a/csn_asm/src/data/literal.rs b/csn_asm/src/data/literal.rs index 6e0ea5f..c76e2be 100644 --- a/csn_asm/src/data/literal.rs +++ b/csn_asm/src/data/literal.rs @@ -56,7 +56,7 @@ impl From for Addr { } /// Label name -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum Label { Named(String), Numbered(u32), diff --git a/csn_asm/src/error.rs b/csn_asm/src/error.rs index 6cf40f8..8c785df 100644 --- a/csn_asm/src/error.rs +++ b/csn_asm/src/error.rs @@ -2,6 +2,7 @@ use crate::instr::{Cond}; use crate::data::{Mask, Register}; use thiserror::Error; use std::borrow::Cow; +use crate::data::literal::Label; /// csn_asm unified error type @@ -34,6 +35,8 @@ pub enum AsmError { ValueAsOutput, #[error("Conditional branch already defined for \"{0}\"")] ConditionalAlreadyUsed(Cond), + #[error("Label \"{0:?}\" not defined")] + LabelNotDefined(Label), } /// Architectural error - the code is syntactically OK, but cannot run diff --git a/csn_asm/src/instr/flatten.rs b/csn_asm/src/instr/flatten.rs new file mode 100644 index 0000000..42fa0d3 --- /dev/null +++ b/csn_asm/src/instr/flatten.rs @@ -0,0 +1,104 @@ +use std::sync::atomic::AtomicU32; +use crate::instr::{Op, Instr, Cond, Routine}; +use crate::error::{Error, AsmError}; +use std::collections::HashMap; +use crate::data::literal::{Label, Value}; +use crate::instr::op::Op::{Skip, SkipIf}; +use crate::data::{Rd, SrcDisp, Mask}; + +/// A trait for something that can turn into multiple instructions +pub trait Flatten { + fn flatten(self, label_num: &AtomicU32) -> Result, Error>; +} + +impl Flatten for Instr { + fn flatten(self, label_num: &AtomicU32) -> Result, Error> { + 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(Error::Asm(AsmError::ConditionalAlreadyUsed(cond))); + } + + let next_lbl = if cnt == branch_count - 1 { + end_lbl.clone() + } else { + Label::unique(label_num) + }; + ops.push(Op::JumpIf(!cond, next_lbl.clone())); + + for branch_instr in branch { + ops.extend(branch_instr.flatten(label_num)?); + } + + if cnt != branch_count - 1 { + ops.push(Op::Jump(end_lbl.clone())); + ops.push(Op::Label(next_lbl)); + } + } + ops.push(Op::Label(end_lbl)); + } + + Ok(ops) + } +} + +impl Flatten for Routine { + fn flatten(self, label_num: &AtomicU32) -> Result, Error> { + let mut ops = vec![ + Op::Routine(self.name.clone()), + ]; + + for instr in self.body { + ops.extend(instr.flatten(label_num)?); + } + + ops.push(Op::Barrier(Some(format!("Routine \"{}\" overrun", self.name).into()))); + Ok(ops) + } +} + +/// Convert jumps to relative skips +pub fn jumps_to_skips(ops: Vec) -> Result, Error> { + let mut label_positions = HashMap::::new(); + for (n, op) in ops.iter().enumerate() { + if let Op::Label(name) = op { + 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 { + Op::Label(_) => { + skipped += 1; + } + Op::Jump(target) => { + if let Some(dest) = label_positions.get(&target) { + let skip = *dest as isize - n as isize + skipped; + cleaned.push(Skip(Rd(SrcDisp::Immediate(Value(skip as i64)), Mask::default()))); + } else { + return Err(Error::Asm(AsmError::LabelNotDefined(target))); + } + } + Op::JumpIf(cond, target) => { + if let Some(dest) = label_positions.get(&target) { + let skip = *dest as isize - n as isize + skipped; + cleaned.push(SkipIf(cond, Rd(SrcDisp::Immediate(Value(skip as i64)), Mask::default()))); + } else { + return Err(Error::Asm(AsmError::LabelNotDefined(target))); + } + } + other => { + cleaned.push(other); + } + } + } + + Ok(cleaned) +} diff --git a/csn_asm/src/instr/mod.rs b/csn_asm/src/instr/mod.rs index 7489cdc..1dc420f 100644 --- a/csn_asm/src/instr/mod.rs +++ b/csn_asm/src/instr/mod.rs @@ -1,12 +1,16 @@ mod op; mod cond; +mod flatten; + +pub use flatten::Flatten; +pub use flatten::jumps_to_skips; pub use op::Op; pub use cond::Cond; -use crate::data::literal::{Label, RoutineName}; -use std::sync::atomic::{AtomicU32}; -use std::collections::HashMap; -use crate::error::{AsmError, Error}; +use crate::data::literal::{RoutineName}; + + + /// A higher-level instruction #[derive(Debug, Clone, Eq, PartialEq)] @@ -21,58 +25,3 @@ pub struct Routine { pub body: Vec, } -/// A trait for something that can turn into multiple instructions -pub trait Flatten { - fn flatten(self, label_num: &AtomicU32) -> Result, Error>; -} - -impl Flatten for Instr { - fn flatten(self, label_num: &AtomicU32) -> Result, Error> { - 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(Error::Asm(AsmError::ConditionalAlreadyUsed(cond))); - } - - let next_lbl = if cnt == branch_count - 1 { - end_lbl.clone() - } else { - Label::unique(label_num) - }; - ops.push(Op::JumpIf(!cond, next_lbl.clone())); - - for branch_instr in branch { - ops.extend(branch_instr.flatten(label_num)?); - } - - if cnt != branch_count - 1 { - ops.push(Op::Jump(end_lbl.clone())); - ops.push(Op::Label(next_lbl)); - } - } - ops.push(Op::Label(end_lbl)); - } - - Ok(ops) - } -} - -impl Flatten for Routine { - fn flatten(self, label_num: &AtomicU32) -> Result, Error> { - let mut ops = vec![ - Op::Routine(self.name.clone()), - ]; - - for instr in self.body { - ops.extend(instr.flatten(label_num)?); - } - - ops.push(Op::Barrier(Some(format!("Routine \"{}\" overrun", self.name).into()))); - Ok(ops) - } -} diff --git a/csn_asm/src/instr/op.rs b/csn_asm/src/instr/op.rs index f3a91ae..9709997 100644 --- a/csn_asm/src/instr/op.rs +++ b/csn_asm/src/instr/op.rs @@ -37,6 +37,8 @@ pub enum Op { Skip(Rd), /// Jump to a label if a flag is set JumpIf(Cond, Label), + /// Skip if a flag is set + SkipIf(Cond, Rd), /// Deny jumps, skips and run across this address, producing a run-time fault with a message. Barrier(Option), /// Generate a run-time fault with a debugger message diff --git a/csn_asm/src/lib.rs b/csn_asm/src/lib.rs index 6959932..68bcc8d 100644 --- a/csn_asm/src/lib.rs +++ b/csn_asm/src/lib.rs @@ -9,7 +9,7 @@ pub use parse::parse; #[cfg(test)] mod tests { use crate::parse; - use crate::instr::{Op, Flatten, Instr}; + use crate::instr::{Op, Flatten, Instr, jumps_to_skips}; use crate::data::{Wr, DstDisp, Register, SrcDisp, Rd}; use crate::data::literal::{Value, Addr, Label}; use std::sync::atomic::AtomicU32; @@ -256,4 +256,74 @@ mod tests { Op::Label(Label::Numbered(0)), ], parsed); } + + + #[test] + fn test_jumps_to_skips() { + let parsed = parse("( + (foo + (:foo) + (:unused) + (:whatever) + (mov r0 r0) + (j :foo) + (j :foo) + (mov r0 r0) + (mov r0 r0) + (j :whatever) + (j.if eq :whatever) + ) + )").unwrap(); + + assert_eq!( + vec![ + Op::Routine("foo".into()), + Op::Label(Label::Named("foo".to_string())), + Op::Label(Label::Named("unused".to_string())), + Op::Label(Label::Named("whatever".to_string())), + Op::Mov( + Wr::new(DstDisp::Register(Register::Gen(0))), + Rd::new(SrcDisp::Register(Register::Gen(0))), + ), + Op::Jump(Label::Named("foo".to_string())), + Op::Jump(Label::Named("foo".to_string())), + Op::Mov( + Wr::new(DstDisp::Register(Register::Gen(0))), + Rd::new(SrcDisp::Register(Register::Gen(0))), + ), + Op::Mov( + Wr::new(DstDisp::Register(Register::Gen(0))), + Rd::new(SrcDisp::Register(Register::Gen(0))), + ), + Op::Jump(Label::Named("whatever".to_string())), + Op::JumpIf(Cond::Equal, Label::Named("whatever".to_string())), + Op::Barrier(Some("Routine \"foo\" overrun".into())), + ], parsed); + + // Labels are removed and jumps become skips + + let cleaned = jumps_to_skips(parsed).unwrap(); + + assert_eq!( + vec![ + Op::Routine("foo".into()), + Op::Mov( + Wr::new(DstDisp::Register(Register::Gen(0))), + Rd::new(SrcDisp::Register(Register::Gen(0))), + ), + Op::Skip(Rd::new(SrcDisp::Immediate(Value(-1)))), + Op::Skip(Rd::new(SrcDisp::Immediate(Value(-2)))), + Op::Mov( + Wr::new(DstDisp::Register(Register::Gen(0))), + Rd::new(SrcDisp::Register(Register::Gen(0))), + ), + Op::Mov( + Wr::new(DstDisp::Register(Register::Gen(0))), + Rd::new(SrcDisp::Register(Register::Gen(0))), + ), + Op::Skip(Rd::new(SrcDisp::Immediate(Value(-5)))), + Op::SkipIf(Cond::Equal, Rd::new(SrcDisp::Immediate(Value(-6)))), + Op::Barrier(Some("Routine \"foo\" overrun".into())), + ], cleaned); + } } diff --git a/csn_asm/src/parse/parse_op.rs b/csn_asm/src/parse/parse_op.rs index 67e29c4..1dc30b9 100644 --- a/csn_asm/src/parse/parse_op.rs +++ b/csn_asm/src/parse/parse_op.rs @@ -11,9 +11,9 @@ pub fn parse_op(keyword: &str, far : bool, mut arg_tokens: impl Iterator { let dest = parse_label(arg_tokens.next())?; if far { - Op::Jump(dest) - } else { Op::FarJump(dest) + } else { + Op::Jump(dest) } } @@ -45,8 +45,9 @@ pub fn parse_op(keyword: &str, far : bool, mut arg_tokens: impl Iterator { + let cond = parse_cond(&expect_string_atom(arg_tokens.next())?)?; let dest = parse_label(arg_tokens.next())?; - Op::JumpIf(parse_cond(&expect_string_atom(arg_tokens.next())?)?, dest) + Op::JumpIf(cond, dest) } "barrier" => { @@ -93,9 +94,9 @@ pub fn parse_op(keyword: &str, far : bool, mut arg_tokens: impl Iterator