From 83996348cb674f5619cb15e7e61413031cb7f369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Wed, 28 Oct 2020 17:36:16 +0100 Subject: [PATCH 1/2] Implement compile-time immediate arithmetics --- compile_examples.sh | 6 +--- crsn/crsn-sexp/src/error.rs | 18 +++++++++- crsn/src/asm/mod.rs | 29 ++++++++++++++-- crsn/src/asm/parse/arg_parser.rs | 20 ++++++++++- crsn/src/asm/parse/mod.rs | 12 ++++++- crsn/src/asm/parse/parse_data.rs | 51 ++++++++++++++++++++++++++-- crsn/src/asm/parse/parse_instr.rs | 31 ++++++++++++++--- crsn/src/asm/parse/parse_op.rs | 4 +++ crsn/src/module/mod.rs | 2 +- crsn/src/runtime/run_thread/info.rs | 1 + crsn/src/runtime/run_thread/state.rs | 10 ++++++ examples/expr.csn | 12 +++++++ launcher/src/main.rs | 1 + 13 files changed, 178 insertions(+), 19 deletions(-) create mode 100644 examples/expr.csn diff --git a/compile_examples.sh b/compile_examples.sh index cdc0960..5fec96e 100755 --- a/compile_examples.sh +++ b/compile_examples.sh @@ -4,8 +4,4 @@ set -e cargo build -for file in examples/*.csn -do - echo "--- $file ---" - target/debug/launcher -P "$file" -done +find examples -name '*.csn' -type f -exec target/debug/launcher -P {} \; diff --git a/crsn/crsn-sexp/src/error.rs b/crsn/crsn-sexp/src/error.rs index cb5dea9..8a3ada2 100644 --- a/crsn/crsn-sexp/src/error.rs +++ b/crsn/crsn-sexp/src/error.rs @@ -1,4 +1,5 @@ use std::{cmp, fmt}; +use std::fmt::{Formatter, Debug}; /// The representation of an s-expression parse error. pub struct Error { @@ -9,7 +10,7 @@ pub struct Error { } /// Position in the input string -#[derive(Debug, PartialEq, Clone, Default)] +#[derive(PartialEq, Clone, Default)] pub struct SourcePosition { /// The line number on which the error occurred. 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 /// structs down, which helps performance in the common case. /// diff --git a/crsn/src/asm/mod.rs b/crsn/src/asm/mod.rs index 6798920..1974265 100644 --- a/crsn/src/asm/mod.rs +++ b/crsn/src/asm/mod.rs @@ -8,6 +8,8 @@ use crate::asm::parse::{ParserContext, ParserState}; use crate::module::{CrsnExtension, CrsnUniq}; use crate::runtime::program::Program; use crate::builtin::BuiltinOps; +use crate::runtime::run_thread::{RunState, ThreadInfo, ThreadToken}; +use crate::runtime::frame::REG_COUNT; pub mod data; pub mod error; @@ -18,11 +20,12 @@ pub mod patches; /// Parse a program from string and assemble a low level instruction sequence from it. pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec>) -> Result, error::CrsnError> { parsers.insert(0, BuiltinOps::new()); - for p in &mut parsers { p.init(uniq); } + let parsers_arc = Arc::new(parsers); + // remove first line if it looks like a shebang let source = if source.starts_with("#!") { if let Some(nl) = source.find('\n') { @@ -34,18 +37,38 @@ pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec { orig_len: usize, args: Vec, @@ -18,6 +19,16 @@ pub struct TokenParser<'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> { type Item = Sexp; type IntoIter = std::vec::IntoIter; @@ -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. /// 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> { diff --git a/crsn/src/asm/parse/mod.rs b/crsn/src/asm/parse/mod.rs index 0107c02..a52125c 100644 --- a/crsn/src/asm/parse/mod.rs +++ b/crsn/src/asm/parse/mod.rs @@ -11,6 +11,8 @@ use crate::asm::error::CrsnError; use crate::asm::instr::Op; use crate::asm::parse::sexp_expect::expect_list; use crate::module::CrsnExtension; +use crate::runtime::run_thread::{ThreadInfo, RunState}; +use std::sync::Arc; pub mod parse_cond; pub mod parse_instr; @@ -28,7 +30,7 @@ pub struct ParserContext<'a> { pub state: RefCell, } -#[derive(Default, Debug)] +#[derive(Debug)] pub struct ParserState { /// Register aliases valid globally pub global_reg_aliases: HashMap, @@ -41,6 +43,14 @@ pub struct ParserState { /// Global constants pub constants: HashMap, + + /// 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, + + /// True if we are in an expression parser context + pub parsing_expr : bool, } pub fn parse(source: &str, pos: &SourcePosition, parsers: &ParserContext) -> Result, CrsnError> { diff --git a/crsn/src/asm/parse/parse_data.rs b/crsn/src/asm/parse/parse_data.rs index 27e5152..c8251f6 100644 --- a/crsn/src/asm/parse/parse_data.rs +++ b/crsn/src/asm/parse/parse_data.rs @@ -6,9 +6,12 @@ use sexp::{Atom, Sexp, SourcePosition}; use crate::asm::data::{DataDisp, Rd, RdData, reg, Wr, WrData, RdWr}; use crate::asm::data::literal::{ConstantName, Label, RegisterAlias, Value}; 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::patches::ErrWithPos; +use std::sync::atomic::AtomicU32; +use crate::module::OpTrait; +use crate::asm::instr::Cond; fn is_valid_identifier(name: &str) -> bool { // ascii symbols "!\"#$_&'()*+,-./:;<=>?@[\\]^_`{|}~" @@ -87,7 +90,51 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result { 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)) } Sexp::Atom(Atom::S(s), pos) => { diff --git a/crsn/src/asm/parse/parse_instr.rs b/crsn/src/asm/parse/parse_instr.rs index ac276a8..6fccd7e 100644 --- a/crsn/src/asm/parse/parse_instr.rs +++ b/crsn/src/asm/parse/parse_instr.rs @@ -1,4 +1,4 @@ -use sexp::{Sexp, SourcePosition, Atom}; +use sexp::{Sexp, SourcePosition, Atom, atom_s}; use crate::asm::error::CrsnError; use crate::asm::instr::{Flatten, InstrWithBranches}; @@ -18,9 +18,20 @@ pub fn parse_instructions(items: impl Iterator, pos: &SourcePosition, let (tokens, listpos) = expect_list(expr, false)?; 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 parsing_expr { + return Err(CrsnError::Parse("Illegal syntax in const expression".into(), pos.clone())); + } parsed.push(parse_routine(toki, pos, pcx)?); continue; } @@ -62,8 +73,13 @@ pub fn parse_instructions(items: impl Iterator, pos: &SourcePosition, }) .collect::>(); - let arg_tokens = TokenParser::new(toki.take(token_count - branch_tokens.len()).collect(), &listpos, pcx); - // debug!("branch_tokens: {:#?}", branch_tokens); + if parsing_expr && !branch_tokens.is_empty() { + 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 mut branches = vec![]; @@ -77,7 +93,12 @@ pub fn parse_instructions(items: impl Iterator, 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 { op, pos: namepos, diff --git a/crsn/src/asm/parse/parse_op.rs b/crsn/src/asm/parse/parse_op.rs index 9fb058d..ca057fd 100644 --- a/crsn/src/asm/parse/parse_op.rs +++ b/crsn/src/asm/parse/parse_op.rs @@ -16,6 +16,10 @@ pub fn parse_op<'a>(mut keyword: &str, mut arg_tokens: TokenParser<'a>, spos: &S 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 { arg_tokens = match p.parse_op(spos, keyword, arg_tokens) { Ok(ParseRes::Parsed(kind)) => return Ok(Some(Op { diff --git a/crsn/src/module/mod.rs b/crsn/src/module/mod.rs index 1b160de..07442f9 100644 --- a/crsn/src/module/mod.rs +++ b/crsn/src/module/mod.rs @@ -52,7 +52,7 @@ pub trait OpTrait: Debug + Send + Sync + 'static { /// CRSN initializer object. /// Only one should be created for the lifespan of the parser and runtime. -#[derive(Default)] +#[derive(Default,Debug)] pub struct CrsnUniq { object_handle_counter : AtomicU64 } diff --git a/crsn/src/runtime/run_thread/info.rs b/crsn/src/runtime/run_thread/info.rs index 7559559..7e2ed08 100644 --- a/crsn/src/runtime/run_thread/info.rs +++ b/crsn/src/runtime/run_thread/info.rs @@ -7,6 +7,7 @@ use crate::runtime::program::Program; use crate::runtime::run_thread::ThreadToken; use crate::module::{CrsnExtension, CrsnUniq}; +#[derive(Debug)] pub struct ThreadInfo { /// Thread ID pub id: ThreadToken, diff --git a/crsn/src/runtime/run_thread/state.rs b/crsn/src/runtime/run_thread/state.rs index 1315ea5..48133e5 100644 --- a/crsn/src/runtime/run_thread/state.rs +++ b/crsn/src/runtime/run_thread/state.rs @@ -25,6 +25,16 @@ pub struct RunState { 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 { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("RunState") diff --git a/examples/expr.csn b/examples/expr.csn new file mode 100644 index 0000000..5368c93 --- /dev/null +++ b/examples/expr.csn @@ -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"))) +) diff --git a/launcher/src/main.rs b/launcher/src/main.rs index c8e8fa0..13f441a 100644 --- a/launcher/src/main.rs +++ b/launcher/src/main.rs @@ -151,6 +151,7 @@ fn main() -> anyhow::Result<()> { ])?; if config.assemble_only { + println!("--- {} ---", config.program_file); for (n, op) in parsed.ops.iter().enumerate() { if config.assemble_debug { println!("{:04} : {:?}", n, op); From 02f73a21f4b72a3ed16cf7bd471a9a10c43d885c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Wed, 28 Oct 2020 18:24:51 +0100 Subject: [PATCH 2/2] fixes, safeguards and docs for "compile-time arithmetics" --- README.md | 33 +++++++++++++++- crsn/src/asm/instr/op.rs | 7 ++++ crsn/src/asm/parse/parse_data.rs | 65 ++++++++++++++----------------- crsn/src/asm/parse/parse_instr.rs | 2 +- crsn/src/builtin/exec.rs | 7 ++++ crsn/src/module/mod.rs | 7 ++++ crsn_arith/src/exec.rs | 9 +++++ examples/expr.csn | 9 +++-- 8 files changed, 98 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index a94c1d1..ad487a0 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,35 @@ Some instructions use bit offsets and widths. Width is directly attached to the offsets are attached at the respective arguments after a colon: `(ld16 r0:8 r1:32)` - load 16 bits, starting at offset 32 of `r1`, into `r0`, starting at offset 8. The rest if the register is not affected. +### Compile-time arithmetics + +Quite often you will want to do some calculations at compile time, for example to create a constant whose value depends on another, +or to compose a binary value using bit shifts and masking. This can be done in any place that expects an input operand (`Rd`). + +The syntax for this is as follows: + +``` +(ld r0 (=add 123 456)) +``` + +The expressions can be nested. The equals sign is not required in the inner levels, and the output operand must be omitted +(the compiler inserts a placeholder register there during evaluation): + +``` +(def WIDTH 1024) +(def HALFW_MAX (=sub (div WIDTH 2) 1)) +``` + +Almost any instruction can be evaluated this way, excluding instructions that perform IO, work with the screen, objects etc. +Instructions that are not compile-time evaluation safe will produce a compile error. + +There are several limitations to consider: + +- Only immediate values (literals) and constant names may be used. +- Registers, if used, will always read as zero and writes have no effect. (This should also produce an error) +- Only instructions that take the form `(op Wr ...)` can be used. The `Wr` operand is inserted automatically and must NOT be specified! +- Conditionals are not allowed inside expressions: branches, conditional suffixes + ### Conditional branches Conditonal branches are written like this: @@ -296,7 +325,9 @@ Jumping to a label is always safer than a manual skip. ; However, if the register is a global register, then the alias is valid everywhere. (sym SYM REG) -; Define a constant. These are valid in the whole program. +; Define a constant. These are valid in the entire program following the definition point, unless +; un-defined. Constants are evaluated and assigned at compile time, the program control flow has no +; effect. ; Value must be known at compile time. (def CONST VALUE) diff --git a/crsn/src/asm/instr/op.rs b/crsn/src/asm/instr/op.rs index b81799f..296c1ce 100644 --- a/crsn/src/asm/instr/op.rs +++ b/crsn/src/asm/instr/op.rs @@ -65,4 +65,11 @@ impl OpTrait for Op { se } + + fn is_compile_time_evaluable(&self) -> bool { + match &self.kind { + OpKind::BuiltIn(op) => op.is_compile_time_evaluable(), + OpKind::Ext(op) => op.is_compile_time_evaluable() + } + } } diff --git a/crsn/src/asm/parse/parse_data.rs b/crsn/src/asm/parse/parse_data.rs index c8251f6..3335a48 100644 --- a/crsn/src/asm/parse/parse_data.rs +++ b/crsn/src/asm/parse/parse_data.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; 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, Register}; use crate::asm::data::literal::{ConstantName, Label, RegisterAlias, Value}; use crate::asm::error::CrsnError; use crate::asm::parse::{ParserContext, parse_instructions}; @@ -75,6 +75,7 @@ pub fn parse_label_str(name: &str, pos: &SourcePosition) -> Result Result { // trace!("parse data: {:?}", tok); match tok { + // immediates are okay in const eval - no tests needed Sexp::Atom(Atom::I(val), _pos) => { Ok(DataDisp::Immediate(unsafe { std::mem::transmute(val) })) } @@ -92,10 +93,12 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result { if let Some(Sexp::Atom(Atom::S(s), s_pos)) = list.first() { + // Const eval + 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())); + return Err(CrsnError::Parse(format!("Invalid syntax, did you mean \"={}\" (a compile-time expression)?", s).into(), s_pos.clone())); } // start expr parsing mode @@ -116,6 +119,10 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result Result Result { +fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserContext) -> Result { if s == "_" { return Ok(DataDisp::Discard); } if let Some(reference) = s.strip_prefix('@') { + if pcx.state.borrow().parsing_expr { + return Err(CrsnError::Parse("Object handles cannot be used in expressions!".into(), pos.clone())); + } + /* extension constants (pre-defined handles) */ for p in pcx.parsers { if let Some(val) = p.get_constant_value(reference) { @@ -188,7 +199,7 @@ pub fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserConte return Ok(DataDisp::Immediate(*val)); } - /* register aliases */ + /* register aliases - no need to check for const_eval, the aliases could never be defined */ if let Some(val) = pstate.reg_aliases.get(s) { return Ok(DataDisp::Register(*val)); } else if let Some(val) = pstate.global_reg_aliases.get(s) { @@ -215,6 +226,14 @@ pub fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserConte } } + if pcx.state.borrow().parsing_expr { + if s == "=result" { + return Ok(DataDisp::Register(Register::Gen(0))); + } else { + return Err(CrsnError::Parse("Registers cannot be used in expressions!".into(), pos.clone())); + } + } + /* register */ let reg = reg::parse_reg(&s, &pos)?; @@ -231,39 +250,13 @@ pub fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserConte /// Parse immediate value pub fn parse_value(tok: Sexp, pcx: &ParserContext) -> Result<(Value, SourcePosition), CrsnError> { - match tok { - Sexp::Atom(Atom::I(val), pos) => { - Ok((unsafe { std::mem::transmute(val) }, pos)) - } - Sexp::Atom(Atom::U(val), pos) => { - Ok((val, pos)) - } - Sexp::Atom(Atom::C(val), pos) => { - Ok((val as u64, pos)) - } - Sexp::Atom(Atom::QS(_), pos) => { - Err(CrsnError::Parse("quoted string not expected here".into(), pos)) - } - Sexp::Atom(Atom::F(val), pos) => { - Ok((unsafe { std::mem::transmute(val) }, pos)) - } - Sexp::Atom(Atom::S(s), pos) => { - let pstate = pcx.state.borrow(); - if let Some(val) = pstate.constants.get(&s) { - return Ok((*val, pos)); - } - - /* extension constants */ - for p in pcx.parsers { - if let Some(val) = p.get_constant_value(&s) { - return Ok((val, pos)); - } - } + let pos = tok.pos().clone(); + let parsed = parse_rd(tok, pcx)?; - Err(CrsnError::Parse(format!("unknown constant: {}", s).into(), pos)) - } - Sexp::List(_, pos) => { - Err(CrsnError::Parse("expected a value".into(), pos)) + match parsed { + Rd(RdData::Immediate(value)) => Ok((value, pos)), + _ => { + Err(CrsnError::Parse("expected an immediate value".into(), pos)) } } } diff --git a/crsn/src/asm/parse/parse_instr.rs b/crsn/src/asm/parse/parse_instr.rs index 6fccd7e..adb3eb1 100644 --- a/crsn/src/asm/parse/parse_instr.rs +++ b/crsn/src/asm/parse/parse_instr.rs @@ -95,7 +95,7 @@ pub fn parse_instructions(items: impl Iterator, pos: &SourcePosition, if parsing_expr { // hackity hack for const expr eval - arg_parser.prepend(atom_s("r0")); + arg_parser.prepend(atom_s("=result")); } if let Some(op) = parse_op(name.as_str(), arg_parser, &namepos)? { diff --git a/crsn/src/builtin/exec.rs b/crsn/src/builtin/exec.rs index 80eaab8..34f9c83 100644 --- a/crsn/src/builtin/exec.rs +++ b/crsn/src/builtin/exec.rs @@ -204,6 +204,13 @@ impl OpTrait for BuiltinOp { Ok(res) } + fn is_compile_time_evaluable(&self) -> bool { + match self { + BuiltinOp::Load { .. } | BuiltinOp::LoadBits { .. } => true, + _ => false + } + } + fn to_sexp(&self) -> Sexp { super::parse::to_sexp(self) } diff --git a/crsn/src/module/mod.rs b/crsn/src/module/mod.rs index 07442f9..661aed5 100644 --- a/crsn/src/module/mod.rs +++ b/crsn/src/module/mod.rs @@ -48,6 +48,13 @@ pub trait OpTrait: Debug + Send + Sync + 'static { /// Turn into an S-expression that produces this instruction when parsed fn to_sexp(&self) -> Sexp; + + /// Check if an operation is safe to use in compile-time arithmetics - has no side effects + /// outside the input and output registers (and flags), has the form (op Wr ...), and does not + /// use object handles or extension data. + fn is_compile_time_evaluable(&self) -> bool { + false + } } /// CRSN initializer object. diff --git a/crsn_arith/src/exec.rs b/crsn_arith/src/exec.rs index 5625d5a..53cfc48 100644 --- a/crsn_arith/src/exec.rs +++ b/crsn_arith/src/exec.rs @@ -757,6 +757,15 @@ impl OpTrait for ArithOp { ArithOp::FloatHypAcot { dst, a } => to_sexp_1_or_2("facoth", dst, a), } } + + fn is_compile_time_evaluable(&self) -> bool { + match self { + ArithOp::Test { .. } | ArithOp::Compare { .. } | ArithOp::RangeTest { .. } + | ArithOp::FloatTest { .. } | ArithOp::FloatCompare { .. } | ArithOp::FloatRangeTest { .. } => false, + // rng is allowed... makes little sense, but it also has no side effects + _ => true, // a bold claim... + } + } } fn to_sexp_2_or_3(name: &str, dst: &Wr, a: &Rd, b: &Rd) -> Sexp { diff --git a/examples/expr.csn b/examples/expr.csn index 5368c93..2088779 100644 --- a/examples/expr.csn +++ b/examples/expr.csn @@ -1,12 +1,15 @@ ( - (def FOO 14) + (def A 10) + (def FOO (=add A (sub 10 5))) + + ; FOO is 15 (ld r0 (=add 14 1)) (cmp r0 15 (ne? (fault "1"))) (ld r0 (=add FOO 1)) - (cmp r0 15 (ne? (fault "2"))) + (cmp r0 16 (ne? (fault "2"))) (ld r0 (=add (sub FOO 2) 3)) - (cmp r0 15 (ne? (fault "3"))) + (cmp r0 16 (ne? (fault "3"))) )