From fe8bf87e6dc6e27a90079c8882710b189bcf2f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Mon, 5 Oct 2020 21:57:59 +0200 Subject: [PATCH] add round-trip tests for built-in instructions --- crsn/src/asm/instr/flatten.rs | 3 +- crsn/src/asm/instr/mod.rs | 2 + crsn/src/asm/parse/mod.rs | 5 +- crsn/src/asm/parse/parse_data.rs | 27 ++++- crsn/src/builtin/exec.rs | 62 +--------- crsn/src/builtin/parse.rs | 197 ++++++++++++++++++++++++++++++- 6 files changed, 225 insertions(+), 71 deletions(-) diff --git a/crsn/src/asm/instr/flatten.rs b/crsn/src/asm/instr/flatten.rs index 5f4cb86..decfea7 100644 --- a/crsn/src/asm/instr/flatten.rs +++ b/crsn/src/asm/instr/flatten.rs @@ -8,9 +8,10 @@ use crate::asm::instr::{Cond, InstrWithBranches, Op, Routine}; use crate::asm::instr::op::OpKind; use crate::builtin::defs::Barrier; use crate::builtin::defs::BuiltinOp; +use std::fmt::Debug; /// A trait for something that can turn into multiple instructions -pub trait Flatten { +pub trait Flatten : Debug { fn flatten(self: Box, label_num: &AtomicU32) -> Result, CrsnError>; } diff --git a/crsn/src/asm/instr/mod.rs b/crsn/src/asm/instr/mod.rs index 0ac1913..de5a2de 100644 --- a/crsn/src/asm/instr/mod.rs +++ b/crsn/src/asm/instr/mod.rs @@ -9,12 +9,14 @@ pub mod cond; mod flatten; /// A higher-level instruction +#[derive(Debug)] pub struct InstrWithBranches { pub op: Op, pub branches: Option)>>, } /// A routine +#[derive(Debug)] pub struct Routine { pub name: RoutineName, pub body: Box, diff --git a/crsn/src/asm/parse/mod.rs b/crsn/src/asm/parse/mod.rs index c1b45f1..1c41507 100644 --- a/crsn/src/asm/parse/mod.rs +++ b/crsn/src/asm/parse/mod.rs @@ -26,6 +26,7 @@ pub struct ParserContext<'a> { pub state: RefCell, } +#[derive(Default)] pub struct ParserState { /// Register aliases within the routine pub reg_aliases: HashMap, @@ -40,7 +41,9 @@ pub struct ParserState { pub fn parse(source: &str, parsers: &ParserContext) -> Result, CrsnError> { 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)? .flatten(&label_num) } diff --git a/crsn/src/asm/parse/parse_data.rs b/crsn/src/asm/parse/parse_data.rs index e5ad224..56bc250 100644 --- a/crsn/src/asm/parse/parse_data.rs +++ b/crsn/src/asm/parse/parse_data.rs @@ -43,9 +43,18 @@ pub fn parse_constant_name(name: Option) -> Result) -> Result { // trace!("parse label: {:?}", 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 { + 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) @@ -82,7 +91,12 @@ pub fn parse_data_disp(tok: Option, pcx: &ParserContext) -> Result, pcx: &ParserContext) -> Result) -> Result { +pub fn parse_value(tok: Option, pcx: &ParserContext) -> Result { let tok = if let Some(tok) = tok { tok } else { @@ -110,6 +124,11 @@ pub fn parse_value(tok: Option) -> Result { Ok(unsafe { std::mem::transmute(*val) }) } 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)?) }) } _ => { diff --git a/crsn/src/builtin/exec.rs b/crsn/src/builtin/exec.rs index b23cb80..00909fa 100644 --- a/crsn/src/builtin/exec.rs +++ b/crsn/src/builtin/exec.rs @@ -144,66 +144,6 @@ impl OpTrait for BuiltinOp { } fn to_sexp(&self) -> Sexp { - match 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)]) - } + super::parse::to_sexp(self) } } diff --git a/crsn/src/builtin/parse.rs b/crsn/src/builtin/parse.rs index baeab36..218c3b4 100644 --- a/crsn/src/builtin/parse.rs +++ b/crsn/src/builtin/parse.rs @@ -5,10 +5,12 @@ use crate::asm::data::reg::parse_reg; use crate::asm::error::CrsnError; use crate::asm::instr::op::OpKind; 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::builtin::defs::{Barrier, BuiltinOp}; use crate::module::{CrsnExtension, ParseRes}; +use crate::utils::A; +use std::convert::TryFrom; #[derive(Debug, Clone)] pub struct BuiltinOps { @@ -82,7 +84,7 @@ impl CrsnExtension for BuiltinOps { "def" => { 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(); 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" => { BuiltinOp::Fault(match args.next() { None => None, @@ -200,8 +216,7 @@ impl CrsnExtension for BuiltinOps { other => { if let Some(label) = other.strip_prefix(':') { - let label = Label::Named(label.to_string()); - BuiltinOp::Label(label) + BuiltinOp::Label(parse_label_str(label)?) } else { return Ok(ParseRes::Unknown(args)); } @@ -222,3 +237,177 @@ pub(crate) fn parse_routine_name(name: String) -> Result 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); + } + } +}