forked from MightyPork/crsn
add a readme
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
# CROISSANT VIRTUAL MACHINE
|
||||
|
||||
Croissant (or Crsn for short) is an extensible runtime emulating a weird microcomputer.
|
||||
|
||||
## FAQ
|
||||
|
||||
### What is this for?
|
||||
|
||||
F U N
|
||||
|
||||
#### What if I don't enjoy writing assembly that looks like Lisp?
|
||||
|
||||
maybe go play fortnite instead
|
||||
|
||||
# Architecture
|
||||
|
||||
## Registers
|
||||
|
||||
- 8 general purpose registers `reg0`-`reg7`
|
||||
- 8 argument registers `arg0`-`arg7`
|
||||
- 8 result registers `res0`-`res7`
|
||||
|
||||
All registers are 64-bit unsigned integers that can be treated as
|
||||
signed, if you want to. Overflow is allowed and reported by status flags.
|
||||
|
||||
8-, 16-, and 32-bit arithmetic is not currently implemented (only 64-bit), but will be
|
||||
added later. Probably. Maybe.
|
||||
|
||||
## Status Flags
|
||||
|
||||
Arithmetic and other operations set status flags that can be used for conditional jumps.
|
||||
|
||||
- Equal … Values are equal
|
||||
- Lower … A < B
|
||||
- Greater … A > B
|
||||
- Zero … Value is zero, buffer is empty, etc.
|
||||
- Positive … Value is positive
|
||||
- Negative … Value is negative
|
||||
- Overflow … Arithmetic overflow or underflow, buffer underflow, etc.
|
||||
- Invalid … Invalid arguments for an instruction
|
||||
- Carry … Arithmetic carry *this is not currently used for anything*
|
||||
|
||||
### Status Tests
|
||||
|
||||
These keywords (among others) are used in conditional branches to specify flag tests:
|
||||
|
||||
- `eq` … Equal,
|
||||
- `ne` … NotEqual,
|
||||
- `z` … Zero,
|
||||
- `nz` … NotZero,
|
||||
- `lt` … Lower,
|
||||
- `le` … LowerOrEqual,
|
||||
- `gt` … Greater,
|
||||
- `ge` … GreaterOrEqual,
|
||||
- `pos` … Positive,
|
||||
- `neg` … Negative,
|
||||
- `npos` … NonPositive,
|
||||
- `nneg` … NonNegative,
|
||||
- `c` … Carry,
|
||||
- `nc` … NotCarry,
|
||||
- `valid` … Valid,
|
||||
- `inval` … Invalid,
|
||||
- `ov` … Overflow,
|
||||
- `nov` … NotOverflow,
|
||||
|
||||
# Syntax
|
||||
|
||||
*The syntax is very much subject to change at the moment. The format described here
|
||||
is valid at the time this file is added to version control.*
|
||||
|
||||
Instructions are written using S-expressions, because they are easy to parse
|
||||
and everyone loves Lisp.
|
||||
|
||||
A program has this format:
|
||||
|
||||
```
|
||||
(
|
||||
(RoutineName Instructions…)
|
||||
…
|
||||
)
|
||||
```
|
||||
|
||||
Instructions are written like this:
|
||||
|
||||
```
|
||||
(Keyword Args… ConditionalBranches…)
|
||||
```
|
||||
|
||||
Args are either:
|
||||
- one of the registers (`reg0`, `arg3` etc)
|
||||
- `_` to discard an output value
|
||||
- a literal value (decimal, hex or binary)
|
||||
- label or routine name
|
||||
- condition flag keyword
|
||||
- anything else an extension supports...
|
||||
|
||||
Conditonal branches are written like this:
|
||||
|
||||
```
|
||||
(Condition? Instructions…)
|
||||
```
|
||||
|
||||
If there is more than one conditional branch chained to an instruction,
|
||||
then only one branch is taken - there is no fall-through. The definition order
|
||||
is preserved, i.e. if the `inval` flag is to be checked, it should be done
|
||||
before checking e.g. `nz`, which is, incidentally, true by default,
|
||||
because all flags start cleared.
|
||||
|
||||
Example routine to calculate the factorial of `arg0`:
|
||||
|
||||
```
|
||||
(fac
|
||||
(cmp arg0 2
|
||||
(eq? (ret 2)))
|
||||
(sub r0 arg0 1)
|
||||
(call fac r0)
|
||||
(mul r0 arg0 res0)
|
||||
(ret r0)
|
||||
)
|
||||
```
|
||||
|
||||
# Instruction Set
|
||||
|
||||
Crsn instruction set is composed of extensions.
|
||||
|
||||
Extensions can also define special syntax for their instructions, so long as it's valid S-expressions.
|
||||
|
||||
## Labels, jumps and barriers
|
||||
|
||||
These are defined as part of the built-in instruction set (see below).
|
||||
|
||||
- Barrier - marks the boundary between routines to prevent overrun. Cannot be jumped across.
|
||||
- Local labels - can be jumped to within the same routine, both forward and backward.
|
||||
- Far labels - can be jumped to from any place in the code using a far jump (disregarding barriers).
|
||||
This is a very cursed functionality that may or may not have some valid use case.
|
||||
- Skips - cannot cross a barrier, similar to a local label but without explicitly defining a label.
|
||||
|
||||
Skipping across conditional branches may have *surprising results* - conditional branches are expanded
|
||||
to series of simple and conditional skips by the assembler. Only use skips if you really know what you're doing.
|
||||
Jumping to a label is always a safer choice.
|
||||
|
||||
## Built-in Instructions
|
||||
|
||||
```
|
||||
; Do nothing
|
||||
(nop)
|
||||
|
||||
; Stop execution
|
||||
(halt)
|
||||
|
||||
; Mark a jump target.
|
||||
(:LABEL)
|
||||
|
||||
; Mark a far jump target (can be jumped to from another routine).
|
||||
; This label is preserved in optimized code.
|
||||
(far :LABEL)
|
||||
|
||||
; Jump to a label
|
||||
(j :LABEL)
|
||||
|
||||
; Jump to a label if a flag is set
|
||||
(jif COND :LABEL)
|
||||
|
||||
; Jump to a label that can be in another function
|
||||
(fj :LABEL)
|
||||
|
||||
; Far jump to a label if a flag is set
|
||||
(fjif COND :LABEL)
|
||||
|
||||
; Skip backward or forward
|
||||
(s COUNT)
|
||||
|
||||
; Skip if a flag is set
|
||||
(sif COND COUNT)
|
||||
|
||||
; Mark a routine entry point (call target).
|
||||
(routine NAME)
|
||||
|
||||
; Call a routine with arguments.
|
||||
; The arguments are passed as argX. Return values are stored in resX registers.
|
||||
(call ROUTINE ARGS…)
|
||||
|
||||
; Exit the current routine with return values
|
||||
(ret VALS…)
|
||||
|
||||
; Deny jumps, skips and run across this address, producing a run-time fault with a message.
|
||||
(barrier)
|
||||
(barrier "message text")
|
||||
|
||||
; Generate a run-time fault with a debugger message
|
||||
(fault)
|
||||
(fault "message text")
|
||||
|
||||
; Copy value
|
||||
(ld DST SRC)
|
||||
|
||||
; Store status flags to a register
|
||||
(sst DST)
|
||||
|
||||
; Load status flags from a register
|
||||
(sld SRC)
|
||||
```
|
||||
|
||||
## Arithmetic Module
|
||||
|
||||
This module makes heavy use of status flags.
|
||||
|
||||
Many instructions have two forms:
|
||||
- 3 args ... explicit source and destination
|
||||
- 2 args ... destination is also used as the first argument
|
||||
|
||||
```
|
||||
; Test properties of a value - zero, positive, negative
|
||||
(tst SRC)
|
||||
|
||||
; Compare two values
|
||||
(cmp A B)
|
||||
|
||||
; Add A+B
|
||||
(add DST A B)
|
||||
(add DST B)
|
||||
|
||||
; Subtract A-B
|
||||
(sub DST A B)
|
||||
(sub DST B)
|
||||
|
||||
; Multiply A*B
|
||||
(mul DST A B)
|
||||
(mul DST B)
|
||||
|
||||
; Divide A/B
|
||||
(div DST A B)
|
||||
(div DST B)
|
||||
|
||||
; Divide and get remainder
|
||||
; Both DST and REM are output registers
|
||||
(divr DST REM A B)
|
||||
(divr DST REM B)
|
||||
|
||||
; Get remainder A%B
|
||||
; This is equivalent to (divr _ REM A B),
|
||||
; except status flags are updated by the remainder value
|
||||
(mod DST A B)
|
||||
(mod DST B)
|
||||
|
||||
; AND A&B
|
||||
(and DST A B)
|
||||
(and DST B)
|
||||
|
||||
; OR A|B
|
||||
(or DST A B)
|
||||
(or DST B)
|
||||
|
||||
; XOR A&B
|
||||
(xor DST A B)
|
||||
(xor DST B)
|
||||
|
||||
; CPL ~A (negate all bits)
|
||||
(cpl DST A)
|
||||
(cpl DST)
|
||||
|
||||
; Rotate right (wrap around)
|
||||
(ror DST A B)
|
||||
(ror DST B)
|
||||
|
||||
; Rotate left (wrap around)
|
||||
(rol DST A B)
|
||||
(rol DST B)
|
||||
|
||||
; Logical shift right (fill with zeros)
|
||||
(lsr DST A B)
|
||||
(lsr DST B)
|
||||
|
||||
; Logical shift left (fill with zeros)
|
||||
(lsl DST A B)
|
||||
(lsl DST B)
|
||||
|
||||
; Arithmetic shift right (copy sign bit)
|
||||
(asr DST A B)
|
||||
(asr DST B)
|
||||
|
||||
; Arithmetic shift left (this is identical to `lsl`, added for completeness)
|
||||
(asl DST A B)
|
||||
(asl DST B)
|
||||
```
|
||||
|
||||
@@ -114,6 +114,18 @@ impl AsmModule for BuiltinOpParser {
|
||||
}
|
||||
}
|
||||
|
||||
"sst" => {
|
||||
BuiltinOp::StoreStatus {
|
||||
dst: args.next_wr()?,
|
||||
}
|
||||
}
|
||||
|
||||
"sld" => {
|
||||
BuiltinOp::LoadStatus {
|
||||
src: args.next_rd()?,
|
||||
}
|
||||
}
|
||||
|
||||
"far" => {
|
||||
if let Some(Sexp::Atom(Atom::S(ref label))) = args.peek() {
|
||||
if let Some(label) = label.strip_prefix(':') {
|
||||
|
||||
@@ -81,8 +81,7 @@ fn main() {
|
||||
)
|
||||
(emptystack
|
||||
(:again)
|
||||
(pop _ arg0 (z? (ret)))
|
||||
(j :again)
|
||||
(pop _ arg0 (nz? (j :again)))
|
||||
(ret)
|
||||
)
|
||||
)
|
||||
@@ -96,7 +95,7 @@ fn main() {
|
||||
let parsed = crsn::asm::assemble(program, parsers.as_slice()).unwrap();
|
||||
|
||||
let mut thread1 = RunThread::new(ThreadToken(0), parsed.clone(), Addr(0), &[]);
|
||||
thread1.set_speed(Duration::from_millis(1000));
|
||||
thread1.set_speed(Duration::from_millis(250));
|
||||
|
||||
let thread2 = RunThread::new(ThreadToken(1), parsed.clone(), Addr(0), &[]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user