diff --git a/README.md b/README.md new file mode 100644 index 0000000..dbfd542 --- /dev/null +++ b/README.md @@ -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) +``` + diff --git a/crsn/src/builtin/parse.rs b/crsn/src/builtin/parse.rs index 41ee337..da63ec4 100644 --- a/crsn/src/builtin/parse.rs +++ b/crsn/src/builtin/parse.rs @@ -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(':') { diff --git a/launcher/src/main.rs b/launcher/src/main.rs index 805019f..2f23048 100644 --- a/launcher/src/main.rs +++ b/launcher/src/main.rs @@ -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), &[]);