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.

6.2 KiB


Croissant (or Crsn for short) is an extensible runtime emulating a weird microcomputer.


What is this for?


What if I don't enjoy writing assembly that looks like Lisp?

maybe go play fortnite instead



  • 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,


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:

    (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

; Stop execution

; Mark a jump target.

; 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

; 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

; Skip if a flag is set

; 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.

; 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 "message text")

; Generate a run-time fault with a debugger message
(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 DST A B)
(and DST B)

; OR A|B
(or DST A B)
(or DST 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)