add round-trip tests for built-in instructions

pull/21/head
Ondřej Hruška 4 years ago
parent 05104c93ca
commit fe8bf87e6d
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 3
      crsn/src/asm/instr/flatten.rs
  2. 2
      crsn/src/asm/instr/mod.rs
  3. 5
      crsn/src/asm/parse/mod.rs
  4. 25
      crsn/src/asm/parse/parse_data.rs
  5. 62
      crsn/src/builtin/exec.rs
  6. 197
      crsn/src/builtin/parse.rs

@ -8,9 +8,10 @@ use crate::asm::instr::{Cond, InstrWithBranches, Op, Routine};
use crate::asm::instr::op::OpKind; use crate::asm::instr::op::OpKind;
use crate::builtin::defs::Barrier; use crate::builtin::defs::Barrier;
use crate::builtin::defs::BuiltinOp; use crate::builtin::defs::BuiltinOp;
use std::fmt::Debug;
/// A trait for something that can turn into multiple instructions /// A trait for something that can turn into multiple instructions
pub trait Flatten { pub trait Flatten : Debug {
fn flatten(self: Box<Self>, label_num: &AtomicU32) -> Result<Vec<Op>, CrsnError>; fn flatten(self: Box<Self>, label_num: &AtomicU32) -> Result<Vec<Op>, CrsnError>;
} }

@ -9,12 +9,14 @@ pub mod cond;
mod flatten; mod flatten;
/// A higher-level instruction /// A higher-level instruction
#[derive(Debug)]
pub struct InstrWithBranches { pub struct InstrWithBranches {
pub op: Op, pub op: Op,
pub branches: Option<Vec<(Cond, Box<dyn Flatten>)>>, pub branches: Option<Vec<(Cond, Box<dyn Flatten>)>>,
} }
/// A routine /// A routine
#[derive(Debug)]
pub struct Routine { pub struct Routine {
pub name: RoutineName, pub name: RoutineName,
pub body: Box<dyn Flatten>, pub body: Box<dyn Flatten>,

@ -26,6 +26,7 @@ pub struct ParserContext<'a> {
pub state: RefCell<ParserState>, pub state: RefCell<ParserState>,
} }
#[derive(Default)]
pub struct ParserState { pub struct ParserState {
/// Register aliases within the routine /// Register aliases within the routine
pub reg_aliases: HashMap<RegisterAlias, Register>, pub reg_aliases: HashMap<RegisterAlias, Register>,
@ -40,7 +41,9 @@ pub struct ParserState {
pub fn parse(source: &str, parsers: &ParserContext) -> Result<Vec<Op>, CrsnError> { pub fn parse(source: &str, parsers: &ParserContext) -> Result<Vec<Op>, CrsnError> {
let items = expect_list(Some(sexp::parse(source)?), true)?; let items = expect_list(Some(sexp::parse(source)?), true)?;
let label_num = AtomicU32::new(0); /* numbered labels start with a weird high number
to avoid conflicts with user-defined numbered labels */
let label_num = AtomicU32::new(0x7890_0000);
parse_instructions(items.into_iter(), parsers)? parse_instructions(items.into_iter(), parsers)?
.flatten(&label_num) .flatten(&label_num)
} }

@ -43,9 +43,18 @@ pub fn parse_constant_name(name: Option<Sexp>) -> Result<ConstantName, CrsnError
/// Parse a label /// Parse a label
pub fn parse_label(name: Option<Sexp>) -> Result<Label, CrsnError> { pub fn parse_label(name: Option<Sexp>) -> Result<Label, CrsnError> {
// trace!("parse label: {:?}", name); // trace!("parse label: {:?}", name);
let name = expect_string_atom(name)?; let name = expect_string_atom(name)?;
Ok(Label::Named(name.trim_start_matches(':').into())) Ok(parse_label_str(&name)?)
}
pub fn parse_label_str(name: &str) -> Result<Label, CrsnError> {
let label = name.trim_start_matches(':');
Ok(if label.starts_with('#') {
Label::Numbered(u32::try_from(parse_u64(&label[1..])?).expect("numbered label fit in u32"))
} else {
Label::Named(label.to_string())
})
} }
/// Parse data disposition (address/value, without the read/write restriction) /// Parse data disposition (address/value, without the read/write restriction)
@ -82,7 +91,12 @@ pub fn parse_data_disp(tok: Option<Sexp>, pcx: &ParserContext) -> Result<DataDis
} }
if let Some(reference) = s.strip_prefix('@') { if let Some(reference) = s.strip_prefix('@') {
let pstate = pcx.state.borrow();
if let Some(val) = pstate.reg_aliases.get(reference) {
Ok(DataDisp::ObjectPtr(*val))
} else {
Ok(DataDisp::ObjectPtr(reg::parse_reg(reference)?)) Ok(DataDisp::ObjectPtr(reg::parse_reg(reference)?))
}
} else if s.starts_with(|c: char| c.is_ascii_digit()) { } else if s.starts_with(|c: char| c.is_ascii_digit()) {
Ok(DataDisp::Immediate(unsafe { std::mem::transmute(parse_i64(s)?) })) Ok(DataDisp::Immediate(unsafe { std::mem::transmute(parse_i64(s)?) }))
} else { } else {
@ -96,7 +110,7 @@ pub fn parse_data_disp(tok: Option<Sexp>, pcx: &ParserContext) -> Result<DataDis
} }
/// Parse immediate value /// Parse immediate value
pub fn parse_value(tok: Option<Sexp>) -> Result<Value, CrsnError> { pub fn parse_value(tok: Option<Sexp>, pcx: &ParserContext) -> Result<Value, CrsnError> {
let tok = if let Some(tok) = tok { let tok = if let Some(tok) = tok {
tok tok
} else { } else {
@ -110,6 +124,11 @@ pub fn parse_value(tok: Option<Sexp>) -> Result<Value, CrsnError> {
Ok(unsafe { std::mem::transmute(*val) }) Ok(unsafe { std::mem::transmute(*val) })
} }
Sexp::Atom(Atom::S(s)) => { Sexp::Atom(Atom::S(s)) => {
let pstate = pcx.state.borrow();
if let Some(val) = pstate.constants.get(s) {
return Ok(*val);
}
Ok(unsafe { std::mem::transmute(parse_i64(s)?) }) Ok(unsafe { std::mem::transmute(parse_i64(s)?) })
} }
_ => { _ => {

@ -144,66 +144,6 @@ impl OpTrait for BuiltinOp {
} }
fn to_sexp(&self) -> Sexp { fn to_sexp(&self) -> Sexp {
match self { super::parse::to_sexp(self)
BuiltinOp::Nop => sexp::list(&[A("nop")]),
BuiltinOp::Halt => sexp::list(&[A("halt")]),
BuiltinOp::Sleep { micros } => sexp::list(&[A("sleep"), A(micros)]),
BuiltinOp::Label(label) => sexp::list(&[A(label)]),
BuiltinOp::Jump(label) => sexp::list(&[A("j"), A(label)]),
BuiltinOp::FarLabel(label) => sexp::list(&[A("far"), A(label)]),
BuiltinOp::FarJump(label) => sexp::list(&[A("fj"), A(label)]),
BuiltinOp::Call(name, args) => {
if args.is_empty() {
sexp::list(&[A("call"), A(name)])
} else {
let mut inner = vec![A("call"), A(&name.name)];
inner.extend(args.iter().map(A));
sexp::list(&inner)
}
},
BuiltinOp::Ret(values) => {
if values.is_empty() {
sexp::list(&[A("ret")])
} else {
let mut inner = vec![A("ret")];
inner.extend(values.iter().map(A));
sexp::list(&inner)
}
}
BuiltinOp::Routine(name) => sexp::list(&[A("routine"), A(name)]),
BuiltinOp::Skip(n) => sexp::list(&[A("skip"), A(n)]),
BuiltinOp::Barrier { kind, msg } => {
let mut inner = vec![];
match kind {
Barrier::Open(label) => {
inner.push(A("barrier-open"));
inner.push(A(label));
}
Barrier::Close(label) => {
inner.push(A("barrier-close"));
inner.push(A(label));
}
Barrier::Standalone => {
inner.push(A("barrier"));
if let Some(msg) = msg {
inner.push(A(msg));
}
}
}
sexp::list(&inner)
}
BuiltinOp::Fault(msg) => {
if let Some(msg) = msg {
sexp::list(&[A("fault"), A(msg)])
} else {
sexp::list(&[A("fault")])
}
}
BuiltinOp::Drop(obj) => sexp::list(&[A("drop"), A(obj)]),
BuiltinOp::Move { dst, src } => sexp::list(&[A("ld"), A(dst), A(src)]),
BuiltinOp::StoreStatus { dst } => sexp::list(&[A("sst"), A(dst)]),
BuiltinOp::LoadStatus { src } => sexp::list(&[A("lst"), A(src)])
}
} }
} }

@ -5,10 +5,12 @@ use crate::asm::data::reg::parse_reg;
use crate::asm::error::CrsnError; use crate::asm::error::CrsnError;
use crate::asm::instr::op::OpKind; use crate::asm::instr::op::OpKind;
use crate::asm::parse::arg_parser::TokenParser; use crate::asm::parse::arg_parser::TokenParser;
use crate::asm::parse::parse_data::{parse_constant_name, parse_label, parse_rd, parse_reg_alias, parse_value}; use crate::asm::parse::parse_data::{parse_constant_name, parse_label, parse_rd, parse_reg_alias, parse_value, parse_u64, parse_label_str};
use crate::asm::parse::sexp_expect::expect_string_atom; use crate::asm::parse::sexp_expect::expect_string_atom;
use crate::builtin::defs::{Barrier, BuiltinOp}; use crate::builtin::defs::{Barrier, BuiltinOp};
use crate::module::{CrsnExtension, ParseRes}; use crate::module::{CrsnExtension, ParseRes};
use crate::utils::A;
use std::convert::TryFrom;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct BuiltinOps { pub struct BuiltinOps {
@ -82,7 +84,7 @@ impl CrsnExtension for BuiltinOps {
"def" => { "def" => {
let name = parse_constant_name(args.next())?; let name = parse_constant_name(args.next())?;
let value = parse_value(args.next())?; let value = parse_value(args.next(), pcx)?;
let mut pstate = pcx.state.borrow_mut(); let mut pstate = pcx.state.borrow_mut();
if pstate.constants.contains_key(&name) { if pstate.constants.contains_key(&name) {
@ -155,6 +157,20 @@ impl CrsnExtension for BuiltinOps {
} }
} }
"barrier-open" => {
BuiltinOp::Barrier {
kind: Barrier::Open(parse_label(args.next())?),
msg: None,
}
}
"barrier-close" => {
BuiltinOp::Barrier {
kind: Barrier::Close(parse_label(args.next())?),
msg: None,
}
}
"fault" => { "fault" => {
BuiltinOp::Fault(match args.next() { BuiltinOp::Fault(match args.next() {
None => None, None => None,
@ -200,8 +216,7 @@ impl CrsnExtension for BuiltinOps {
other => { other => {
if let Some(label) = other.strip_prefix(':') { if let Some(label) = other.strip_prefix(':') {
let label = Label::Named(label.to_string()); BuiltinOp::Label(parse_label_str(label)?)
BuiltinOp::Label(label)
} else { } else {
return Ok(ParseRes::Unknown(args)); return Ok(ParseRes::Unknown(args));
} }
@ -222,3 +237,177 @@ pub(crate) fn parse_routine_name(name: String) -> Result<RoutineName, CrsnError>
Ok(RoutineName { name, arity }) Ok(RoutineName { name, arity })
} }
pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp {
match op {
BuiltinOp::Nop => sexp::list(&[A("nop")]),
BuiltinOp::Halt => sexp::list(&[A("halt")]),
BuiltinOp::Sleep { micros } => sexp::list(&[A("sleep"), A(micros)]),
BuiltinOp::Label(label) => sexp::list(&[A(label)]),
BuiltinOp::Jump(label) => sexp::list(&[A("j"), A(label)]),
BuiltinOp::FarLabel(label) => sexp::list(&[A("far"), A(label)]),
BuiltinOp::FarJump(label) => sexp::list(&[A("fj"), A(label)]),
BuiltinOp::Call(name, args) => {
if args.is_empty() {
sexp::list(&[A("call"), A(&name.name)])
} else {
let mut inner = vec![A("call"), A(&name.name)];
inner.extend(args.iter().map(A));
sexp::list(&inner)
}
},
BuiltinOp::Ret(values) => {
if values.is_empty() {
sexp::list(&[A("ret")])
} else {
let mut inner = vec![A("ret")];
inner.extend(values.iter().map(A));
sexp::list(&inner)
}
}
BuiltinOp::Routine(name) => sexp::list(&[A("routine"), A(name)]),
BuiltinOp::Skip(n) => sexp::list(&[A("skip"), A(n)]),
BuiltinOp::Barrier { kind, msg } => {
let mut inner = vec![];
match kind {
Barrier::Open(label) => {
inner.push(A("barrier-open"));
inner.push(A(label));
}
Barrier::Close(label) => {
inner.push(A("barrier-close"));
inner.push(A(label));
}
Barrier::Standalone => {
inner.push(A("barrier"));
if let Some(msg) = msg {
inner.push(A(msg));
}
}
}
sexp::list(&inner)
}
BuiltinOp::Fault(msg) => {
if let Some(msg) = msg {
sexp::list(&[A("fault"), A(msg)])
} else {
sexp::list(&[A("fault")])
}
}
BuiltinOp::Drop(obj) => sexp::list(&[A("drop"), A(obj)]),
BuiltinOp::Move { dst, src } => sexp::list(&[A("ld"), A(dst), A(src)]),
BuiltinOp::StoreStatus { dst } => sexp::list(&[A("sst"), A(dst)]),
BuiltinOp::LoadStatus { src } => sexp::list(&[A("sld"), A(src)])
}
}
#[cfg(test)]
mod test {
use std::any::Any;
use std::cell::RefCell;
use std::sync::atomic::AtomicU32;
use crate::asm::instr::{Flatten, InstrWithBranches};
use crate::asm::parse::{parse_instructions, ParserContext};
use crate::asm::parse::sexp_expect::expect_list;
use crate::builtin::defs::BuiltinOp;
use crate::builtin::parse::BuiltinOps;
use crate::module::OpTrait;
#[test]
fn roundtrip() {
let samples = vec![
// sym, unsym, def, undef - pseudo-instructions
// jump is translated to a skip
("(nop)", "(nop)"),
("(halt)", "(halt)"),
("(sleep 1000)", "(sleep 1000)"),
("(:x)\
(j :x)", "(skip 0)"),
("(:#7)\
(j :#7)", "(skip 0)"),
("(fj :x)", "(fj :x)"),
("(skip 0)", "(skip 0)"),
("(skip r0)", "(skip r0)"),
("(sym banana r0)(unsym banana)(sym banana r1)(skip banana)", "(skip r1)"),
("(def foo 123)(skip foo)", "(skip 123)"),
("(def foo 123)(undef foo)(def foo 444)(skip foo)", "(skip 444)"),
("(def foo -777)(def bar foo)(skip bar)", "(skip -777)"),
("(skip -10)", "(skip -10)"),
("(skip -10000)", "(skip -10000)"),
("(call funcname)", "(call funcname)"),
("(call funcname 13)", "(call funcname 13)"),
("(sym haf r0)\
(call štěkej haf haf)", "(call štěkej r0 r0)"),
("(sym foo r0)\
(sym bar r1)\
(call funcname foo bar)", "(call funcname r0 r1)"),
("(ret)", "(ret)"),
("(ret r0)", "(ret r0)"),
("(ret r0 r1 r2 5)", "(ret r0 r1 r2 5)"),
("(routine ěščžřčřýřážíýáéáýúů)", "(routine ěščžřčřýřážíýáéáýúů/0)"),
("(routine ahoj)", "(routine ahoj/0)"),
("(routine ahoj/2)", "(routine ahoj/2)"),
("(barrier)", "(barrier)"),
("(barrier blablabla)", "(barrier blablabla)"),
("(barrier \"s mezerou\")", "(barrier \"s mezerou\")"),
("(barrier-open :xoxo)", "(barrier-open :xoxo)"),
("(barrier-open :#10)", "(barrier-open :#10)"),
("(barrier-close :xoxo)", "(barrier-close :xoxo)"),
("(barrier-close :#10)", "(barrier-close :#10)"),
("(fault)", "(fault)"),
("(fault kur*a)", "(fault kur*a)"),
("(fault \"do pr*ele\")", "(fault \"do pr*ele\")"),
("(ld r0 r0)", "(ld r0 r0)"),
("(ld r0 156)", "(ld r0 156)"),
("(ld _ -32767)", "(ld _ -32767)"),
("(sst r0)", "(sst r0)"),
("(sld r0)", "(sld r0)"),
("(far :label)", "(far :label)"),
("(drop @r5)", "(drop @r5)"),
("(sym cat r0)(drop @cat)", "(drop @r0)"),
];
let parser = BuiltinOps::new();
let parsers = &[parser];
for (sample, expected) in samples {
let mut pcx = ParserContext {
parsers,
state: Default::default(),
};
println!("Parse: {}", sample);
/* first cycle */
let s = sexp::parse(&format!("({})", sample))
.expect("parse sexp");
let list = expect_list(Some(s), false).unwrap();
let num = AtomicU32::new(0);
let mut parsed = parse_instructions(list.into_iter(), &pcx)
.expect("parse instr").flatten(&num)
.expect("flatten").remove(0);
let exported = parsed.to_sexp().to_string();
println!("Parsed: {}", exported);
assert_eq!(expected, exported);
println!(" - 2nd cycle");
/* second cycle, nothing should change */
let s = sexp::parse(&format!("({})", exported))
.expect("parse sexp (2c)");
let list = expect_list(Some(s), false).unwrap();
let num = AtomicU32::new(0);
let mut parsed = parse_instructions(list.into_iter(), &pcx)
.expect("parse instr (2c)").flatten(&num)
.expect("flatten (2c)").remove(0);
let exported2 = parsed.to_sexp().to_string();
assert_eq!(expected, exported2);
}
}
}

Loading…
Cancel
Save