Croissant Runtime
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
crsn/README.md

482 lines
12 KiB

4 years ago
# CROISSANT VIRTUAL MACHINE
Croissant (or *crsn* for short) is an extensible runtime emulating a weird microcomputer (or not so micro, that depends on what extensions you install).
4 years ago
## FAQ
### What is this for?
F U N
### How is the performance?
Silly fast, actually. 60fps animations are perfectly doable if that's your thing.
It's probably faster than you need for most things, actually.
You can slow it down using the `-C` argument, or using sleep instructions.
4 years ago
#### What if I don't enjoy writing assembly that looks like weird Lisp?
4 years ago
4 years ago
Maybe this is not for you
4 years ago
# Architecture
4 years ago
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.
4 years ago
## Registers
- 8 general purpose registers `r0`-`r7`
4 years ago
- 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.
4 years ago
8-, 16-, 32-bit and floating point arithmetic is not currently implemented, but will be added later. Probably. Maybe.
4 years ago
4 years ago
## Status flags
4 years ago
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
4 years ago
- Carry … Arithmetic carry *this is currently unused*
4 years ago
4 years ago
### Status tests (conditions)
4 years ago
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.
4 years ago
## Program
4 years ago
A program has this format:
```
(
4 years ago
...<instructions and routines>...
4 years ago
)
```
4 years ago
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
4 years ago
Instructions are written like this:
```
4 years ago
(<keyword> <args>... <conditional branches>...)
4 years ago
```
4 years ago
### 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
4 years ago
Args are either:
4 years ago
- 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
4 years ago
Conditonal branches are written like this:
```
4 years ago
(<cond>? <instructions>...)
4 years ago
```
4 years ago
- 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
4 years ago
4 years ago
A routine is defined as:
4 years ago
```
4 years ago
(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)))
4 years ago
(sub r0 arg0 1)
(call fac r0)
(mul r0 arg0 res0)
(ret r0)
)
```
4 years ago
It can also be written like this:
```
(proc fac num
...
)
```
...or by specifying both the arity and argument names:
```
(proc fac/1 num
...
)
```
4 years ago
# Instruction Set
Crsn instruction set is composed of extensions.
4 years ago
Extensions can define new instructions as well as new syntax, so long as it's composed of valid S-expressions.
4 years ago
## 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.
4 years ago
- 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.
4 years ago
4 years ago
Skipping across conditional branches may have *surprising results* - conditional branches are expanded
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.
4 years ago
## Built-in Instructions
```
; Do nothing
(nop)
; Stop execution
(halt)
; Mark a jump target.
(:LABEL)
4 years ago
; Numbered labels
(:#NUMBER)
4 years ago
; 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 that can be in another function
(fj :LABEL)
; Skip backward or forward
(s COUNT)
; Mark a routine entry point (call target).
(routine NAME)
4 years ago
(routine NAME/ARITY)
4 years ago
; Call a routine with arguments.
; The arguments are passed as argX. Return values are stored in resX registers.
4 years ago
(call ROUTINE ARGUMENTS...)
4 years ago
; Exit the current routine with return values
4 years ago
(ret VALUES...)
4 years ago
; Deny jumps, skips and run across this address, producing a run-time fault with a message.
(barrier)
(barrier "message text")
4 years ago
; 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)
4 years ago
; 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)
4 years ago
; 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)
4 years ago
```
## 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)
4 years ago
; 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)
4 years ago
```
4 years ago
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.
Colors use the `RRGGBB` hex format.
4 years ago
If input events are required, then make sure to periodically call `(sc-blit)` or `(sc-poll)`.
This may not be needed if the auto-blit function is enabled and the display is regularly written.
The default settings are 60 FPS and auto-blit enabled.
NOTE: Logging can significantly reduce crsn run speed.
Make sure the log level is at not set to "trace" when you need high-speed updates,
such as animations.
```
; Initialize the screen (opens a window)
(sc-init WIDTH HEIGHT)
; Erase the screen (fill with black)
(sc-erase)
; Fill with a custom color
(sc-erase 0xFF00FF)
; Set pixel color
(sc-px X Y COLOR)
; Set screen option
; 1 ... auto-blit (blit automatically on pixel write when needed to achieve the target FPS)
; 2 ... frame rate
(sc-opt OPTION VALUE)
; Blit (render the pixel buffer).
; This function also updates key and mouse states and handles the window close button
(sc-blit)
; Blit if needed (when the auto-blit function is enabled)
(sc-blit 0)
; Update key and mouse state, handle the window close button
(sc-poll)
; Read mouse position into two registers.
; Sets the overflow flag if the cursour is out of the window
(sc-mouse X Y)
; Check key status. Keys are 0-127. Reads 1 if the key is pressed, 0 if not.
; A list of supported keys can be found in the extension source code.
(sc-key PRESSED KEY)
; Check mouse button state
; 0-left, 1-right, 2-middle
(sc-mbtn PRESSED BTN)
```
## Stdio module
- This module currently defines two global handles: `@stdin` and `@stdout`.
- You can think of these handles as streams or SFRs (special function registers).
To use them, simply load data to or from the handles (e.g. `(ld r0 @stdin)`).
- They operate over unicode code points, which are a superset of ASCII.
You can use these special handles in almost all instructions:
```
(cmp @stdin 'y'
(eq? (ld @stdout '1'))
(ne? (ld @stdout '0')))
```
When you compile a program using such handles, you will get a strange looking assembly:
```
0000 : (ld @0x6372736e00000001 72)
0001 : (ld @0x6372736e00000001 101)
0002 : (ld @0x6372736e00000001 108)
```
These are unique constants assigned to the streams at compile time. They are not meant to be used
directly, but the value can be obtained by simply leaving out the '@' sign: `(ld r0 stdin)`.
That can be useful when these stream handles need to be passed to a function. Obviously this makes
more sense when there are different kinds of streams available, not just these two default ones.
.