forked from MightyPork/crsn
fixes, safeguards and docs for "compile-time arithmetics"
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Label, CrsnEr
|
||||
pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnError> {
|
||||
// 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<DataDisp, CrsnE
|
||||
}
|
||||
Sexp::List(list, pos) => {
|
||||
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<DataDisp, CrsnE
|
||||
|
||||
let op = expr_ops.remove(0);
|
||||
|
||||
if !op.is_compile_time_evaluable() {
|
||||
return Err(CrsnError::Parse(format!("Instruction {} cannot be used in an expression!", op.to_sexp()).into(), pos.clone()));
|
||||
}
|
||||
|
||||
let mut state_mut = pcx.state.borrow_mut();
|
||||
state_mut.const_eval.clear_all();
|
||||
|
||||
@@ -144,12 +151,16 @@ pub fn parse_data_disp(tok: Sexp, pcx: &ParserContext) -> Result<DataDisp, CrsnE
|
||||
}
|
||||
|
||||
/// Parse data disp from a string token (a &str is used so the string can be preprocessed)
|
||||
pub fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserContext) -> Result<DataDisp, CrsnError> {
|
||||
fn parse_data_disp_from_str(s: &str, pos: &SourcePosition, pcx: &ParserContext) -> Result<DataDisp, CrsnError> {
|
||||
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));
|
||||
}
|
||||
let pos = tok.pos().clone();
|
||||
let parsed = parse_rd(tok, pcx)?;
|
||||
|
||||
/* extension constants */
|
||||
for p in pcx.parsers {
|
||||
if let Some(val) = p.get_constant_value(&s) {
|
||||
return Ok((val, pos));
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, 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)? {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+6
-3
@@ -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")))
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user