# 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). ## FAQ ### What is this for? F U N #### What if I don't enjoy writing assembly that looks like weird Lisp? 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 `r0`-`r7` - 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-, 32-bit and floating point arithmetic is not currently implemented, 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 currently unused* ### Status tests (conditions) 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. ## Program A program has this format: ``` ( ...... ) ``` 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: ``` ( ... ...) ``` ### 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) - 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: ``` (? ...) ``` - 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 A routine is defined as: ``` (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) (ret r0) ) ``` 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 define new instructions as well as new syntax, so long as it's composed of 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 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 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 ``` ; Do nothing (nop) ; Stop execution (halt) ; 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. (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) (routine NAME/ARITY) ; Call a routine with arguments. ; The arguments are passed as argX. Return values are stored in resX registers. (call ROUTINE ARGUMENTS...) ; Exit the current routine with return values (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") ; Copy value (ld DST SRC) ; Store status flags to a register (sst DST) ; 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 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) ; 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. Colors use the `RRGGBB` hex format. 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) ```