|
|
|
@ -1,6 +1,6 @@ |
|
|
|
|
# CROISSANT VIRTUAL MACHINE |
|
|
|
|
|
|
|
|
|
Croissant (or Crsn for short) is an extensible runtime emulating a weird microcomputer. |
|
|
|
|
Croissant (or Crsn for short) is an extensible runtime emulating a weird microcomputer (or not so micro, that depends on what extensions you install). |
|
|
|
|
|
|
|
|
|
## FAQ |
|
|
|
|
|
|
|
|
@ -8,12 +8,20 @@ Croissant (or Crsn for short) is an extensible runtime emulating a weird microco |
|
|
|
|
|
|
|
|
|
F U N |
|
|
|
|
|
|
|
|
|
#### What if I don't enjoy writing assembly that looks like Lisp? |
|
|
|
|
#### What if I don't enjoy writing assembly that looks like weird Lisp? |
|
|
|
|
|
|
|
|
|
maybe go play fortnite instead |
|
|
|
|
Maybe this is not for you |
|
|
|
|
|
|
|
|
|
# Architecture |
|
|
|
|
|
|
|
|
|
The runtime is built as a register machine with a stack and status flags. |
|
|
|
|
|
|
|
|
|
- All mutable state (registers and status), called "execution frame", is local to the running routine or the root of the program. |
|
|
|
|
- A call pushes the active frame onto a frame stack and a clean frame is created for the callee. |
|
|
|
|
- The frame stack is not accessible to the running program, it is entirely handled by the runtime. |
|
|
|
|
- When a call is made, the new frame's argument registers are pre-filled with arguments passed by the caller. |
|
|
|
|
- Return values are inserted into the callee's frame's result registers before its execution resumes. |
|
|
|
|
|
|
|
|
|
## Registers |
|
|
|
|
|
|
|
|
|
- 8 general purpose registers `reg0`-`reg7` |
|
|
|
@ -23,10 +31,9 @@ maybe go play fortnite instead |
|
|
|
|
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. |
|
|
|
|
8-, 16-, 32-bit and floating point arithmetic is not currently implemented, but will be added later. Probably. Maybe. |
|
|
|
|
|
|
|
|
|
## Status Flags |
|
|
|
|
## Status flags |
|
|
|
|
|
|
|
|
|
Arithmetic and other operations set status flags that can be used for conditional jumps. |
|
|
|
|
|
|
|
|
@ -38,9 +45,9 @@ Arithmetic and other operations set status flags that can be used for conditiona |
|
|
|
|
- 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* |
|
|
|
|
- Carry … Arithmetic carry *this is currently unused* |
|
|
|
|
|
|
|
|
|
### Status Tests |
|
|
|
|
### Status tests (conditions) |
|
|
|
|
|
|
|
|
|
These keywords (among others) are used in conditional branches to specify flag tests: |
|
|
|
|
|
|
|
|
@ -71,47 +78,96 @@ 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. |
|
|
|
|
|
|
|
|
|
## Program |
|
|
|
|
|
|
|
|
|
A program has this format: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
( |
|
|
|
|
(RoutineName Instructions…) |
|
|
|
|
… |
|
|
|
|
...<instructions and routines>... |
|
|
|
|
) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
e.g. |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
( |
|
|
|
|
(ld r0 100) ; load value into a register |
|
|
|
|
(:again) ; a label |
|
|
|
|
(sub r0 1 ; subtract from a register |
|
|
|
|
(nz? ; conditional branch "not zero?" |
|
|
|
|
(j :again))) ; jump to the label :again |
|
|
|
|
) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
The same program can be written in a compact form: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
((ld r0 100)(:again)(sub r0 1 (nz? (j :again)))) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
## Instruction |
|
|
|
|
|
|
|
|
|
Instructions are written like this: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
(Keyword Args… ConditionalBranches…) |
|
|
|
|
(<keyword> <args>... <conditional branches>...) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
### Conditional instructions |
|
|
|
|
|
|
|
|
|
All instructions can be made conditional by appending `.<cond>` to the keyword, i.e. `j.ne` means "jump if not equal". |
|
|
|
|
This is used internally by the assembler when translating conditional branches to executable code. |
|
|
|
|
|
|
|
|
|
### Instruction arguments |
|
|
|
|
|
|
|
|
|
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... |
|
|
|
|
- One of the registers (`reg0`, `arg3` etc) |
|
|
|
|
- Names of constants defined earlier in the program (e.g. `SCREEN_WIDTH`) |
|
|
|
|
- Symbols defined as register aliases (e.g. `x`) |
|
|
|
|
- The "discard register" `_` to discard an output value. That is used when you only care about side effects or status flags. |
|
|
|
|
- Literal values (decimal, hex or binary) |
|
|
|
|
- Label or routine name (e.g. `factorial`, `:again`) |
|
|
|
|
- ...or anything else an installed crsn extension supports |
|
|
|
|
|
|
|
|
|
### Conditional branches |
|
|
|
|
|
|
|
|
|
Conditonal branches are written like this: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
(Condition? Instructions…) |
|
|
|
|
(<cond>? <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. |
|
|
|
|
- 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 most flags are cleared by instructions that affects flags. |
|
|
|
|
|
|
|
|
|
## Routines |
|
|
|
|
|
|
|
|
|
Example routine to calculate the factorial of `arg0`: |
|
|
|
|
A routine is defined as: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
(fac |
|
|
|
|
(cmp arg0 2 |
|
|
|
|
(eq? (ret 2))) |
|
|
|
|
(proc <name>/<arity> instructions...) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
- `name` is a unique routine name |
|
|
|
|
- `arity` is the number of arguments it takes, e.g. `3`. |
|
|
|
|
- you can define multiple routines with the same name and different arities, the correct one will be used depending on how it's called |
|
|
|
|
|
|
|
|
|
Or, with named arguments: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
(proc <name> <arguments>... instructions...) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
Arguments are simply aliases for the argument registers that can then be used inside the routine. |
|
|
|
|
|
|
|
|
|
Here is an example routine to calculate the factorial of `arg0`: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
(proc fac/1 |
|
|
|
|
(cmp arg0 2 (eq? (ret 2))) |
|
|
|
|
(sub r0 arg0 1) |
|
|
|
|
(call fac r0) |
|
|
|
|
(mul r0 arg0 res0) |
|
|
|
@ -119,11 +175,27 @@ Example routine to calculate the factorial of `arg0`: |
|
|
|
|
) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
It can also be written like this: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
(proc fac num |
|
|
|
|
... |
|
|
|
|
) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
...or by specifying both the arity and argument names: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
(proc fac/1 num |
|
|
|
|
... |
|
|
|
|
) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
# 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. |
|
|
|
|
Extensions can define new instructions as well as new syntax, so long as it's composed of valid S-expressions. |
|
|
|
|
|
|
|
|
|
## Labels, jumps and barriers |
|
|
|
|
|
|
|
|
@ -133,11 +205,13 @@ These are defined as part of the built-in instruction set (see below). |
|
|
|
|
- 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. |
|
|
|
|
- Skips - cannot cross a barrier, similar to a jump but without explicitly defining a label. |
|
|
|
|
All local jumps are turned into skips by the assembler. |
|
|
|
|
|
|
|
|
|
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. |
|
|
|
|
to a varying number of skips and conditional instructions by the assembler. Only use skips if you really know what you're doing. |
|
|
|
|
|
|
|
|
|
Jumping to a label is always safer than a manual skip. |
|
|
|
|
|
|
|
|
|
## Built-in Instructions |
|
|
|
|
|
|
|
|
@ -150,6 +224,8 @@ These are defined as part of the built-in instruction set (see below). |
|
|
|
|
|
|
|
|
|
; Mark a jump target. |
|
|
|
|
(:LABEL) |
|
|
|
|
; Numbered labels |
|
|
|
|
(:#NUMBER) |
|
|
|
|
|
|
|
|
|
; Mark a far jump target (can be jumped to from another routine). |
|
|
|
|
; This label is preserved in optimized code. |
|
|
|
@ -158,35 +234,32 @@ These are defined as part of the built-in instruction set (see below). |
|
|
|
|
; 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) |
|
|
|
|
(routine NAME/ARITY) |
|
|
|
|
|
|
|
|
|
; Call a routine with arguments. |
|
|
|
|
; The arguments are passed as argX. Return values are stored in resX registers. |
|
|
|
|
(call ROUTINE ARGS…) |
|
|
|
|
(call ROUTINE ARGUMENTS...) |
|
|
|
|
|
|
|
|
|
; Exit the current routine with return values |
|
|
|
|
(ret VALS…) |
|
|
|
|
(ret VALUES...) |
|
|
|
|
|
|
|
|
|
; Deny jumps, skips and run across this address, producing a run-time fault with a message. |
|
|
|
|
(barrier) |
|
|
|
|
(barrier "message text") |
|
|
|
|
|
|
|
|
|
; Block barriers are used for routines. They are automatically skipped in execution |
|
|
|
|
; and the whole pair can be jumped *across* |
|
|
|
|
(barrier-open LABEL) |
|
|
|
|
(barrier-close LABEL) |
|
|
|
|
|
|
|
|
|
; Generate a run-time fault with a debugger message |
|
|
|
|
(fault) |
|
|
|
|
(fault "message text") |
|
|
|
@ -199,6 +272,12 @@ These are defined as part of the built-in instruction set (see below). |
|
|
|
|
|
|
|
|
|
; Load status flags from a register |
|
|
|
|
(sld SRC) |
|
|
|
|
|
|
|
|
|
; Define a register alias. The alias is only valid in the current routine or in the root of the program. |
|
|
|
|
(sym ALIAS REGISTER) |
|
|
|
|
|
|
|
|
|
; Define a constant. These are valid in the whole program. |
|
|
|
|
(def NAME VALUE) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
## Arithmetic Module |
|
|
|
@ -282,5 +361,38 @@ Many instructions have two forms: |
|
|
|
|
; Arithmetic shift left (this is identical to `lsl`, added for completeness) |
|
|
|
|
(asl DST A B) |
|
|
|
|
(asl DST B) |
|
|
|
|
|
|
|
|
|
; Delete an object by its handle. Objects are used by some extensions. |
|
|
|
|
(drop @REG) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
## Stacks Module |
|
|
|
|
|
|
|
|
|
This module defines data stacks. Stacks can be shared by routines by passing a handle. |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
; Create a stack. The register then contains the stack handle. |
|
|
|
|
(stack REG) |
|
|
|
|
|
|
|
|
|
; Push to a stack (insert to the end) |
|
|
|
|
(push @REG VALUE) |
|
|
|
|
|
|
|
|
|
; Pop from a stack (remove from the end) |
|
|
|
|
(pop DST @REG) |
|
|
|
|
|
|
|
|
|
; Reverse push to a stack (insert to the beginning) |
|
|
|
|
(rpush @REG VALUE) |
|
|
|
|
|
|
|
|
|
; Reverse pop from a stack (remove from the beginning) |
|
|
|
|
(rpop DST @REG) |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
To delete a stack, drop its handle - `(drop @REG)` |
|
|
|
|
|
|
|
|
|
## Screen module |
|
|
|
|
|
|
|
|
|
This module uses the minifb rust crate to provide a framebuffer with key and mouse input. |
|
|
|
|
|
|
|
|
|
Documentation TBD, see the extension's source code or the example programs |
|
|
|
|
|
|
|
|
|
. |
|
|
|
|