simple-lisp/BYTECODE.md

10 KiB

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:

    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.