diff --git a/README.md b/README.md index dbfd542..ef0c163 100644 --- a/README.md +++ b/README.md @@ -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…) - … + ...... ) ``` +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…) +( ... ...) ``` +### Conditional instructions + +All instructions can be made conditional by appending `.` 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…) +(? ...) ``` -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 / 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 ... 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. +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. ## 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 + +.