Implement compile-time immediate arithmetics

master
Ondřej Hruška 4 years ago
parent cfcd099060
commit 83996348cb
Signed by untrusted user: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 6
      compile_examples.sh
  2. 18
      crsn/crsn-sexp/src/error.rs
  3. 29
      crsn/src/asm/mod.rs
  4. 20
      crsn/src/asm/parse/arg_parser.rs
  5. 12
      crsn/src/asm/parse/mod.rs
  6. 51
      crsn/src/asm/parse/parse_data.rs
  7. 31
      crsn/src/asm/parse/parse_instr.rs
  8. 4
      crsn/src/asm/parse/parse_op.rs
  9. 2
      crsn/src/module/mod.rs
  10. 1
      crsn/src/runtime/run_thread/info.rs
  11. 10
      crsn/src/runtime/run_thread/state.rs
  12. 12
      examples/expr.csn
  13. 1
      launcher/src/main.rs

@ -4,8 +4,4 @@ set -e
cargo build cargo build
for file in examples/*.csn find examples -name '*.csn' -type f -exec target/debug/launcher -P {} \;
do
echo "--- $file ---"
target/debug/launcher -P "$file"
done

@ -1,4 +1,5 @@
use std::{cmp, fmt}; use std::{cmp, fmt};
use std::fmt::{Formatter, Debug};
/// The representation of an s-expression parse error. /// The representation of an s-expression parse error.
pub struct Error { pub struct Error {
@ -9,7 +10,7 @@ pub struct Error {
} }
/// Position in the input string /// Position in the input string
#[derive(Debug, PartialEq, Clone, Default)] #[derive(PartialEq, Clone, Default)]
pub struct SourcePosition { pub struct SourcePosition {
/// The line number on which the error occurred. /// The line number on which the error occurred.
pub line: u32, pub line: u32,
@ -25,6 +26,21 @@ impl fmt::Display for SourcePosition {
} }
} }
impl Debug for SourcePosition {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_struct("SourcePosition")
.field("line", &self.line)
.field("column", &self.column)
.field("index", &self.index)
.finish()
} else {
// shorter version
write!(f, "Pos({}:{})", self.line, self.column)
}
}
}
/// Since errors are the uncommon case, they're boxed. This keeps the size of /// Since errors are the uncommon case, they're boxed. This keeps the size of
/// structs down, which helps performance in the common case. /// structs down, which helps performance in the common case.
/// ///

@ -8,6 +8,8 @@ use crate::asm::parse::{ParserContext, ParserState};
use crate::module::{CrsnExtension, CrsnUniq}; use crate::module::{CrsnExtension, CrsnUniq};
use crate::runtime::program::Program; use crate::runtime::program::Program;
use crate::builtin::BuiltinOps; use crate::builtin::BuiltinOps;
use crate::runtime::run_thread::{RunState, ThreadInfo, ThreadToken};
use crate::runtime::frame::REG_COUNT;
pub mod data; pub mod data;
pub mod error; pub mod error;
@ -18,11 +20,12 @@ pub mod patches;
/// Parse a program from string and assemble a low level instruction sequence from it. /// Parse a program from string and assemble a low level instruction sequence from it.
pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExtension>>) -> Result<Arc<Program>, error::CrsnError> { pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExtension>>) -> Result<Arc<Program>, error::CrsnError> {
parsers.insert(0, BuiltinOps::new()); parsers.insert(0, BuiltinOps::new());
for p in &mut parsers { for p in &mut parsers {
p.init(uniq); p.init(uniq);
} }
let parsers_arc = Arc::new(parsers);
// remove first line if it looks like a shebang // remove first line if it looks like a shebang
let source = if source.starts_with("#!") { let source = if source.starts_with("#!") {
if let Some(nl) = source.find('\n') { if let Some(nl) = source.find('\n') {
@ -34,18 +37,38 @@ pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExt
source source
}; };
let ti = Arc::new(ThreadInfo {
id: ThreadToken(0),
uniq: Default::default(),
program: Program::new(vec![], parsers_arc.clone()).unwrap(),
cycle_time: Default::default(),
extensions: parsers_arc.clone(),
});
let pcx = ParserContext { let pcx = ParserContext {
parsers: &parsers, parsers: &parsers_arc,
state: RefCell::new(ParserState { state: RefCell::new(ParserState {
reg_aliases: Default::default(), reg_aliases: Default::default(),
reg_alias_stack: vec![], reg_alias_stack: vec![],
global_reg_aliases: Default::default(), global_reg_aliases: Default::default(),
constants: Default::default(), constants: Default::default(),
// This is a fake thread to pass to constant expressions when evaluating them.
// This allows to evaluate nearly all instructions at compile time.
const_eval: RunState {
thread_info: ti.clone(),
frame: Default::default(),
call_stack: vec![],
global_regs: [0; REG_COUNT],
ext_data: Default::default()
},
const_eval_ti: ti,
parsing_expr: false
}), }),
}; };
let ops = parse::parse(source, &SourcePosition::default(), &pcx)?; let ops = parse::parse(source, &SourcePosition::default(), &pcx)?;
let ops = labels_to_skips(ops)?; let ops = labels_to_skips(ops)?;
Ok(Program::new(ops, Arc::new(parsers))?) Ok(Program::new(ops, parsers_arc)?)
} }

@ -8,9 +8,10 @@ use crate::asm::parse::sexp_expect::expect_string_atom;
use crate::asm::data::literal::Value; use crate::asm::data::literal::Value;
use crate::builtin::defs::BitMask; use crate::builtin::defs::BitMask;
use crate::asm::patches::ErrWithPos; use crate::asm::patches::ErrWithPos;
use std::fmt::{Debug, Formatter};
use std::fmt;
/// Utility for argument parsing /// Utility for argument parsing
#[derive(Debug)]
pub struct TokenParser<'a> { pub struct TokenParser<'a> {
orig_len: usize, orig_len: usize,
args: Vec<Sexp>, args: Vec<Sexp>,
@ -18,6 +19,16 @@ pub struct TokenParser<'a> {
pub pcx: &'a ParserContext<'a>, pub pcx: &'a ParserContext<'a>,
} }
impl<'a> Debug for TokenParser<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("TokenParser")
.field("orig_len", &self.orig_len)
.field("args", &self.args)
.field("start_pos", &self.start_pos)
.finish()
}
}
impl<'a> IntoIterator for TokenParser<'a> { impl<'a> IntoIterator for TokenParser<'a> {
type Item = Sexp; type Item = Sexp;
type IntoIter = std::vec::IntoIter<Sexp>; type IntoIter = std::vec::IntoIter<Sexp>;
@ -42,6 +53,13 @@ impl<'a> TokenParser<'a> {
} }
} }
/// Prepend a token
pub fn prepend(&mut self, what: Sexp) {
// the list is reversed - actually, append it
self.args.push(what);
self.orig_len += 1;
}
/// Get error if not empty. /// Get error if not empty.
/// The argument is substituted into the phrase "Instruction needs ...!" - i.e. "one Wr argument and a list or string" /// The argument is substituted into the phrase "Instruction needs ...!" - i.e. "one Wr argument and a list or string"
pub fn ensure_empty(&self, what_arguments : &str) -> Result<(), CrsnError> { pub fn ensure_empty(&self, what_arguments : &str) -> Result<(), CrsnError> {

@ -11,6 +11,8 @@ use crate::asm::error::CrsnError;
use crate::asm::instr::Op; use crate::asm::instr::Op;
use crate::asm::parse::sexp_expect::expect_list; use crate::asm::parse::sexp_expect::expect_list;
use crate::module::CrsnExtension; use crate::module::CrsnExtension;
use crate::runtime::run_thread::{ThreadInfo, RunState};
use std::sync::Arc;
pub mod parse_cond; pub mod parse_cond;
pub mod parse_instr; pub mod parse_instr;
@ -28,7 +30,7 @@ pub struct ParserContext<'a> {
pub state: RefCell<ParserState>, pub state: RefCell<ParserState>,
} }
#[derive(Default, Debug)] #[derive(Debug)]
pub struct ParserState { pub struct ParserState {
/// Register aliases valid globally /// Register aliases valid globally
pub global_reg_aliases: HashMap<RegisterAlias, Register>, pub global_reg_aliases: HashMap<RegisterAlias, Register>,
@ -41,6 +43,14 @@ pub struct ParserState {
/// Global constants /// Global constants
pub constants: HashMap<ConstantName, Value>, pub constants: HashMap<ConstantName, Value>,
/// Dummy run state used for const eval
pub const_eval: RunState,
// ThreadInfo is needed separately from RunState. TODO check if this can be refactored
pub const_eval_ti: Arc<ThreadInfo>,
/// True if we are in an expression parser context
pub parsing_expr : bool,
} }
pub fn parse(source: &str, pos: &SourcePosition, parsers: &ParserContext) -> Result<Vec<Op>, CrsnError> { pub fn parse(source: &str, pos: &SourcePosition, parsers: &ParserContext) -> Result<Vec<Op>, CrsnError> {

@ -6,9 +6,12 @@ use sexp::{Atom, Sexp, SourcePosition};
use crate::asm::data::{DataDisp, Rd, RdData, reg, Wr, WrData, RdWr}; use crate::asm::data::{DataDisp, Rd, RdData, reg, Wr, WrData, RdWr};
use crate::asm::data::literal::{ConstantName, Label, RegisterAlias, Value}; use crate::asm::data::literal::{ConstantName, Label, RegisterAlias, Value};
use crate::asm::error::CrsnError; use crate::asm::error::CrsnError;
use crate::asm::parse::ParserContext; use crate::asm::parse::{ParserContext, parse_instructions};
use crate::asm::parse::sexp_expect::expect_string_atom; use crate::asm::parse::sexp_expect::expect_string_atom;
use crate::asm::patches::ErrWithPos; use crate::asm::patches::ErrWithPos;
use std::sync::atomic::AtomicU32;
use crate::module::OpTrait;
use crate::asm::instr::Cond;
fn is_valid_identifier(name: &str) -> bool { fn is_valid_identifier(name: &str) -> bool {
// ascii symbols "!\"#$_&'()*+,-./:;<=>?@[\\]^_`{|}~" // ascii symbols "!\"#$_&'()*+,-./:;<=>?@[\\]^_`{|}~"
@ -87,7 +90,51 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnE
Sexp::Atom(Atom::QS(_s), pos) => { Sexp::Atom(Atom::QS(_s), pos) => {
Err(CrsnError::Parse("Quoted string not expected here".into(), pos)) Err(CrsnError::Parse("Quoted string not expected here".into(), pos))
} }
Sexp::List(_list, pos) => { Sexp::List(list, pos) => {
if let Some(Sexp::Atom(Atom::S(s), s_pos)) = list.first() {
let parsing_expr = pcx.state.borrow().parsing_expr;
if !s.starts_with('=') && !parsing_expr {
return Err(CrsnError::Parse(format!("Invalid syntax, did you mean \"={}\"?", s).into(), s_pos.clone()));
}
// start expr parsing mode
let orig_parsing_expr = pcx.state.borrow().parsing_expr;
pcx.state.borrow_mut().parsing_expr = true;
let lmut = AtomicU32::new(0);
let mut expr_ops = parse_instructions(
// TODO avoid this madness
vec![Sexp::List(list, pos.clone())].into_iter(),
&pos, pcx)?
.flatten(&lmut)?;
if expr_ops.len() != 1 {
return Err(CrsnError::Parse(format!("Expected one top level operation in an expression, got: {:?}", expr_ops).into(), pos.clone()));
}
let op = expr_ops.remove(0);
let mut state_mut = pcx.state.borrow_mut();
state_mut.const_eval.clear_all();
let ticl = state_mut.const_eval.thread_info.clone();
op.execute(&ticl, &mut state_mut.const_eval)
.map_err(|f| CrsnError::ParseOther(Box::new(f), pos.clone()))?;
if state_mut.const_eval.test_cond(Cond::Invalid) {
return Err(CrsnError::Parse("Expression evaluation failed - invalid flag set!".into(), pos.clone()));
}
if !orig_parsing_expr {
// exit expr parsing mode
state_mut.parsing_expr = false;
}
return Ok(DataDisp::Immediate(state_mut.const_eval.frame.gen[0]));
}
Err(CrsnError::Parse("List not expected here".into(), pos)) Err(CrsnError::Parse("List not expected here".into(), pos))
} }
Sexp::Atom(Atom::S(s), pos) => { Sexp::Atom(Atom::S(s), pos) => {

@ -1,4 +1,4 @@
use sexp::{Sexp, SourcePosition, Atom}; use sexp::{Sexp, SourcePosition, Atom, atom_s};
use crate::asm::error::CrsnError; use crate::asm::error::CrsnError;
use crate::asm::instr::{Flatten, InstrWithBranches}; use crate::asm::instr::{Flatten, InstrWithBranches};
@ -18,9 +18,20 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition,
let (tokens, listpos) = expect_list(expr, false)?; let (tokens, listpos) = expect_list(expr, false)?;
let mut toki = tokens.into_iter(); let mut toki = tokens.into_iter();
let (name, namepos) = expect_string_atom(toki.next_or_err(listpos.clone(), "Expected instruction name token")?)?; let (mut name, namepos) = expect_string_atom(toki.next_or_err(listpos.clone(), "Expected instruction name token")?)?;
let parsing_expr = pcx.state.borrow().parsing_expr;
if parsing_expr {
if let Some(n) = name.strip_prefix('=') {
name = n.to_string();
}
}
if name == "proc" { if name == "proc" {
if parsing_expr {
return Err(CrsnError::Parse("Illegal syntax in const expression".into(), pos.clone()));
}
parsed.push(parse_routine(toki, pos, pcx)?); parsed.push(parse_routine(toki, pos, pcx)?);
continue; continue;
} }
@ -62,8 +73,13 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition,
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let arg_tokens = TokenParser::new(toki.take(token_count - branch_tokens.len()).collect(), &listpos, pcx); if parsing_expr && !branch_tokens.is_empty() {
// debug!("branch_tokens: {:#?}", branch_tokens); return Err(CrsnError::Parse(format!("Conditional branches are not allowed in const expression: {:?}", branch_tokens).into(), pos.clone()));
}
let mut arg_parser = TokenParser::new(toki.take(token_count - branch_tokens.len()).collect(), &listpos, pcx);
trace!("Parse op: {}\nargs {:?}\nbranches {:?}", name, arg_parser, branch_tokens);
let branches = { let branches = {
let mut branches = vec![]; let mut branches = vec![];
@ -77,7 +93,12 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition,
} }
}; };
if let Some(op) = parse_op(name.as_str(), arg_tokens, &namepos)? { if parsing_expr {
// hackity hack for const expr eval
arg_parser.prepend(atom_s("r0"));
}
if let Some(op) = parse_op(name.as_str(), arg_parser, &namepos)? {
parsed.push(Box::new(InstrWithBranches { parsed.push(Box::new(InstrWithBranches {
op, op,
pos: namepos, pos: namepos,

@ -16,6 +16,10 @@ pub fn parse_op<'a>(mut keyword: &str, mut arg_tokens: TokenParser<'a>, spos: &S
keyword = &keyword[..pos]; keyword = &keyword[..pos];
} }
if cond.is_some() && arg_tokens.pcx.state.borrow().parsing_expr {
return Err(CrsnError::Parse("Conditional suffixes are not allowed in const expression".into(), spos.clone()));
}
for p in arg_tokens.pcx.parsers { for p in arg_tokens.pcx.parsers {
arg_tokens = match p.parse_op(spos, keyword, arg_tokens) { arg_tokens = match p.parse_op(spos, keyword, arg_tokens) {
Ok(ParseRes::Parsed(kind)) => return Ok(Some(Op { Ok(ParseRes::Parsed(kind)) => return Ok(Some(Op {

@ -52,7 +52,7 @@ pub trait OpTrait: Debug + Send + Sync + 'static {
/// CRSN initializer object. /// CRSN initializer object.
/// Only one should be created for the lifespan of the parser and runtime. /// Only one should be created for the lifespan of the parser and runtime.
#[derive(Default)] #[derive(Default,Debug)]
pub struct CrsnUniq { pub struct CrsnUniq {
object_handle_counter : AtomicU64 object_handle_counter : AtomicU64
} }

@ -7,6 +7,7 @@ use crate::runtime::program::Program;
use crate::runtime::run_thread::ThreadToken; use crate::runtime::run_thread::ThreadToken;
use crate::module::{CrsnExtension, CrsnUniq}; use crate::module::{CrsnExtension, CrsnUniq};
#[derive(Debug)]
pub struct ThreadInfo { pub struct ThreadInfo {
/// Thread ID /// Thread ID
pub id: ThreadToken, pub id: ThreadToken,

@ -25,6 +25,16 @@ pub struct RunState {
pub ext_data: ExtensionDataStore, pub ext_data: ExtensionDataStore,
} }
impl RunState {
/// Clear everything. Caution: when done at runtime, this effectively reboots the thread
pub fn clear_all(&mut self) {
self.frame = Default::default();
self.call_stack = Default::default();
self.global_regs = Default::default();
self.ext_data = Default::default();
}
}
impl Debug for RunState { impl Debug for RunState {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("RunState") f.debug_struct("RunState")

@ -0,0 +1,12 @@
(
(def FOO 14)
(ld r0 (=add 14 1))
(cmp r0 15 (ne? (fault "1")))
(ld r0 (=add FOO 1))
(cmp r0 15 (ne? (fault "2")))
(ld r0 (=add (sub FOO 2) 3))
(cmp r0 15 (ne? (fault "3")))
)

@ -151,6 +151,7 @@ fn main() -> anyhow::Result<()> {
])?; ])?;
if config.assemble_only { if config.assemble_only {
println!("--- {} ---", config.program_file);
for (n, op) in parsed.ops.iter().enumerate() { for (n, op) in parsed.ops.iter().enumerate() {
if config.assemble_debug { if config.assemble_debug {
println!("{:04} : {:?}", n, op); println!("{:04} : {:?}", n, op);

Loading…
Cancel
Save