simple-lisp/BYTECODE.md

268 lines
10 KiB
Markdown

# Bytecode Documentation
## Instructions
Each instruction consists of a mnemonic and some number of arguments. The
numeric value for each instruction is the same length, but the arguments can be
different lengths, and each instruction can have a different number of
arguments.
In this document, the layout for data is specified by a number of fields
separated by commas. When represented in raw bits, these fields are next to each
other with no padding in between. For example:
```text
length:u64, data:[i8]
```
represents an unsigned 64 bit integer field called "length" and a field called
"data" which is an array of signed 8 bit integers. The value right before an
array is the value which determines its length.
The argument types are as follows:
| Name | Description |
|:------------------|:--------------------------------------|
| i8, i16, i32, i64 | 8, 16, 32, or 64 bit signed integer |
| u8, u16, u32, u64 | 8, 16, 32, or 64 bit unsigned integer |
| double | Equivilant to the C type "double" |
| reg | register (format: type:u8,which:u32) |
| str | string (format: length:u64,data:[i8]) |
| bool | boolean (format: u8) |
### Registers
Most instructions take register numbers instead of direct arguments. Registers
take a type and a number. For example "lexenv2" is the third (counting from 0)
lexenv register or "arg0" is the first argument register.
| Mnemonic | ID | Description |
|:---------|:--:|:--------------------------------------------------------------|
| val | 0 | General value registers (clobbered by calls) |
| saved | 1 | Callee saved ragisters (val registers are clobbered by calls) |
| arg | 2 | Function argument registers (clobbered by calls) |
| ret | 3 | Function return value registers (clobbered by calls) |
### Instruction List
- NIL dest:reg
Load the literal nil into DEST
- T def:reg
Load the literal t into DEST
- STRING dest:reg, value:str
Load the string of LENGTH bytes from DATA into dest.
- INT dest:reg, value:i64
Convert VALUE into an int object and store it into DEST
- FLOAT dest:reg, value:double
Convert VALUE into a float object and store it into DEST
- CONS dest:reg, car:reg, cdr:reg
Create a cons object with CAR and CDR and store it into REG.
- LIST dest:reg, count:u64
Create a list from the first COUNT "arg" registers and store it into DEST.
- VECTOR dest:reg, count:u64
Create a vector from the first COUNT "arg" registers and store it into DEST.
- LENGTH dest:reg, thing:reg
Place the length of the THING, a string, vector, or list, into DEST.
- INTERN\_LIT reg:reg, name:str
INTERN\_DYN reg:reg, name:reg
These instructions convert the string literal or register containing a string
NAME into a symbol and store the symbol into REG.
- TYPE_OF dest:reg, thing:reg
Place a symbol representing the type of THING into DEST.
- SYMBOL\_NAME dest:reg, sym:reg
Store the name of SYM into DEST.
- MOV dest:reg, src:reg
Copy the value in the register SRC into the register DEST.
- FUNCALL reg:reg, argc:u64
Call the function in REG. This should either be a function object or a symbol
which has a value as a function. The return values are placed into the "ret"
registers. ARGC is the number of "arg" registers that have been set for this
function call.
- RETVAL_COUNT count:u8
Declare that first COUNT return values have been set (if they have not been
touched during this lexenv, they will be set to nil). Without this, the
highest "ret" register written to is the number to use. Without this, assume
one return value.
- GET\_RETVAL\_COUNT dest:reg
Place the count last set with RETVAL\_COUNT into DEST.
- ENTER\_LEXENV count:u64
Enter a new lexical environment. COUNT is the number of instructions that this
environment lasts.
- ENTER\_BLOCK name:str, count:u64
LEAVE\_BLOCK name:str
Enter a new named block which is identified by the symbol named NAME. The
block is COUNT instructions long. LEAVE\_BLOCK leaves the block identified by
NAME. LEAVE\_BLOCK will jump to the end of the block, and is not necessary
unless you want to exit the block early.
- SET\_VALUE sym:reg, value:reg
Set the value as a variable of SYM to VALUE.
- SET\_FUNCTION sym:reg, value:reg
Set the value as a function of SYM to VALUE (value must be an actual function,
not a symbol).
- GET\_VALUE dest:reg, sym:reg
Store the value as a variable of the symbol SYM into DEST.
- GET\_FUNCTION dest:reg, sym:reg
Store the value as a function of the symbol SYM into DEST.
- BOUNDP dest:reg, sym:reg
If SYM has a value as a variable, place t in DEST, otherwise, place nil in DEST.
- FUNCTIONP dest:reg, sym:reg
If SYM has a value as a function, place t in DEST, otherwise, place nil in DEST.
- NEWFUNCTION\_LIT dest:reg, nreq:u32, nopt:u32, nkey:u32,
aok:bool, rest:bool, count:u64
NEWFUNCTION\_DYN dest:reg, nreq:u32, nopt:u32, nkey:u32,
aok:bool, rest:bool, src:reg
Create a new function object and store it into DEST. If the first case the
next COUNT instructions are considered to be the function and are skipped. In
the second case SRC should be a list or vector containing the bytecode for the
function. NREQ, NOPT, and NKEY are the number of required, optional, and key
arguments required for this function. The names of the keyword arguments are
passed in the "arg" registers. The runtime will handle normalizing these for
you. AOK is allow other keys, REST is wether to allow more positional
arguments. Arguments are passed by the runtime in "arg" registers. The first
required argument is passed in the "arg0" register. After required are
optional arguments. These are passed in two registers. The first is either nil
or t weather or not he argument was actually passed. The second is its value,
or nil if it was not passed. The same is true for key arguments. Finally if
rest is passed, any extra arguments are passed as a list as the final element.
- PUT sym:reg, key:reg, value:reg
Associate KEY with VALUE in the plist of SYM.
- GET dest:reg, sym:reg, key:reg
Store the value associated with KEY in the plist of SYM into DEST.
- AND2 dest:reg, val1:reg, val2:reg
ANDN dest:reg, count:u64
Logical and VAL1 and VAL2, or the first COUNT "arg" registers, and place the
result in DEST.
- OR2 dest:reg, val1:reg, val2:reg
ORN dest:reg, count:u64
Logical or VAL1 and VAL2, or the first COUNT "arg" registers, and place the
result in DEST.
- XOR2 dest:reg, val1:reg, val2:reg
XORN dest:reg, count:u64
Logical xor VAL1 and VAL2, or the first COUNT "arg" registers, and place the
result in DEST.
- NOT dest:reg, value:reg
Take the logical negation of VALUE and place it in DEST.
- JMP offset:i64
CJMP cond:reg, offset:i64
If the value in COND is truthy (not nil), skip the next OFFSET
instructions. If OFFSET is negative, instead go back abs(OFFSET)
instructions. CJMP is NOT counted as an instruction for the purposes of
counting offsets. Therefore an OFFSET of -2 means restart execute at the
instruction above the instruction above this CJMP. JMP is like CJMP, but takes
no condition and always jumps.
- CAR dest:reg, cons:reg
CDR dest:reg, cons:reg
Store the car or cdr of CONS into DEST.
- SETCAR cons:reg, value:reg
SETCDR cons:reg, value:reg
Store VALUE into the car or cdr of CONS.
- GETELT\_LIT dest:reg, seq:reg, index:u64
GETELT\_DYN dest:reg, seq:reg, index:reg
Store the value at INDEX in SEQ (a list, string, or vector) into DEST.
- SETELT\_LIT seq:reg, index:u64, value:reg
SETELT\_DYN seq:reg, index:reg, value:reg
Store VALUE into the index numbered INDEX of SEQ (a list, string, or vector).
- EQ dest:reg, val1:reg, val2:reg
Compare VAL1 and VAL2, if they are the same object (or symbols with the same
name) store T into DEST, otherwise, store NIL.
- NUM\_GT dest:reg, val1:reg, val2:reg
NUM\_GE dest:reg, val1:reg, val2:reg
NUM\_EQ dest:reg, val1:reg, val2:reg
NUM\_LE dest:reg, val1:reg, val2:reg
NUM\_LT dest:reg, val1:reg, val2:reg
Compare VAL1 and VAL2, which must be numbers, and compare their values. If
they pass, store T into DEST, otherwise store NIL.
- ADD dest:reg, count:u64
Add the first COUNT "arg" registers and place the result into DEST.
- SUB dest:reg, val1:reg, val2:reg
Subtract the sum of the first COUNT - 1 "arg" registers starting from "arg1",
that is "arg1", "arg2", "arg3", etc., from "arg0". Store the result in DEST.
- MUL dest:reg, count:u64
Multiply the first COUNT "arg" registers and place the result in DEST.
- DIV dest:reg, count:u64
Divide "arg0" by the product of the COUNT - 1 "arg" registers starting from "arg1"
- INT_DIV dest:reg, val1:reg, val2:reg
Divide VAL1 by VAL2 and place the integer part of the result in DEST.
- RECIP dest:reg, val:reg
Divide 1 by VAL and place the result in DEST.
- MOD dest:reg, val1:reg, val2:reg
Divide VAL1 by VAL2 and place the remainder in DEST.
- SQRT dest:reg, val:reg
Take the square root of VAL and place it in DEST.
- POW dest:reg, base:reg, exp:reg
Raise BASE to the EXP power and place the result in DEST.
- LN dest:reg, val:reg
Take the natural log of VAL and place the result in DEST.
- EXP dest:reg, val:reg
Take the exponential function at VAL and place the result in DEST.
- SIN dest:reg, val:reg
COS dest:reg, val:reg
TAN dest:reg, val:reg
Take the sine, cosine, or tangent of VAL (in radians) and place it into DEST.
- ASIN dest:reg, val:reg
ACOS dest:reg, val:reg
ATAN dest:reg, val:reg
Take the inverse sine, cosine, or tangent of VAL and place the result (in
radians) into DEST.
- BITAND dest:reg, val1:reg, val2:reg
BITOR dest:reg, val1:reg, val2:reg
BITXOR dest:reg, val1:reg, val2:reg
BITNOR dest:reg, val1:reg, val2:reg
Perform the given bit-wise operation on VAL1 and VAL2 and place the result
into DEST.
- BITNEG dest:reg, val:reg
Negate each bit in VAL. That is, flip each bit in VAL.
- LSH dest:reg, val:reg, by:reg
ASH dest:reg, val:reg, by:reg
Either logically or arithmetically shift VAL by BY bits to the left (or right
is BY is negative). Put the result in DEST.