Author: Bruno Haible
Keywords: Lisp, Common Lisp, CLISP, bytecode, Virtual Machine

# The CLISP bytecode specification

The CLISP compiler compiles Lisp programs into an instruction code for a virtual processor. This bytecode is optimized for saving space in the most common cases of Lisp programs. The main advantages/drawbacks of this approach, compared to native code compilation, are:

- Bytecode compiled programs are a lot smaller than when compiled to native code. This results in better use of CPU caches, and in less virtual memory paging. Users perceive this as good responsiveness.
- Maximum execution speed (throughput in tight loops) is limited.
- Since no bytecode instructions are provided for "unsafe" operations (like unchecked array accesses, or "fast" `car`/`cdr`), programs run with all safety checks enabled even when compiled.
- Execution speed of a program can easily be understood by looking at the output of the `disassemble` function. A rule of thumb is that every elementary instruction costs 1 time unit, whereas a function call costs 3 to 4 time units.
- Needing to do no type inference, the compiler is pretty straightforward and fast. As a consequence, the definition of CLOS generic functions, which needs to compile small pieces of generated code, is not perceived to be slow.
- The compiler is independent from the hardware CPU. Different backends, one for each hardware CPU, are not needed. As a consequence, the compiler is fairly small (and would be easily maintainable if written in a less kludgy way...), and it is impossible for the compiler writer to introduce CPU dependent bugs.

## The virtual machine

The bytecode can be thought as being interpreted by a virtual processor. The engine which actually interprets the bytecode (the "implementation of the virtual machine") is actually a C function, but it could as well be a just-in-time compiler which translates a function's bytecode into hardware CPU instructions the first time said function is called.

The virtual machine is a stack machine with two stacks:

- STACK, a stack for Lisp objects and frames.
- SP, a stack for other data and pointers.

This two-stack architecture permits to save an unlimited number of Lisp objects on the STACK (needed for handling of Common Lisp multiple values), without consing. Also, in a world with a compacting no-ambiguous-roots garbage collector, STACK must only hold Lisp objects, and SP can hold all the other data belonging to a frame, which would not fit into STACK without tagging/untagging overhead.

The scope of STACK and SP is only valid for a given function invocation. Whereas the amount of STACK space needed for executing a function (excluding other function calls) is unlimited, the amount of SP space needed is known a priori, at compile time. When a function is called, no relation is specified between the caller's STACK and the callee's STACK, and between the caller's SP and the callee's SP. The bytecode is designed so that outgoing arguments on the caller's STACK can be shared by the caller's incoming arguments area (on the callee's STACK), but a virtual machine implementation may also copy outgoing arguments to incoming arguments instead of sharing them.

The virtual machine has a special data structure, `values`, containing the "top of stack", specially adapted to Common Lisp multiple values:

- `mv_count`, an unsigned integer.
- `value1`, the first value, a Lisp object. If `mv_count` = 0, this is `nil`.
- `mv_space`, all values except the first one, an array of Lisp objects.

The contents of `values` is short-lived. It does not survive a function call, not even a garbage collection.

The interpretation of some bytecode instructions depends on a constant, jmpbufsize. This is a CPU dependent number, the value of `system::*jmpbuf-size*`. In C, it is defined as `ceiling(sizeof(jmp_buf),sizeof(void*))`.

## The structure of compiled functions

A compiled function consists of two objects: The function itself, containing the references to all Lisp objects needed for the bytecode, and a byte vector containing only immediate data, including the bytecode proper.

Typically, the byte vector is about twice as large as the function vector. The separation thus helps the garbage collector (since the byte vector does not need to be scanned for pointers).

The function looks like this (cf. the C type `Cclosure`):

name
  This is the name of the function, normally a symbol or a list of the form `(setf symbol)`. It is used for printing the function and for error messages. This field is immutable.
codevec
  This is the byte vector. It is a `simple-bit-vector` (because that's the simplest type in CLISP which contains immediate data -- note that `(simple-vector (unsigned-byte 8))` is more complex than this). This field is immutable.
consts[]
  The remaining fields in the function object are references to other Lisp objects. These references are immutable, which is why they are called "constants". (The referenced Lisp objects can be mutable objects, such as conses or vectors, however.)

There is actually one exception to the immutability rule: When a generic function's dispatch code is installed, the codevec and consts fields are destructively modified.

Some of the consts can play special roles. A function's looks like this, in more detail:

name
  see above
codevec
  see above
venv-const*
  At most one object, representing the closed-up variables, representing the variables of the lexical environment in which this function was defined. It is a simple-vector, looking like this: `#(next value1 ... valuen)` where value1, ..., valuen are the values of the closed-up variables, and next is either `nil` or a simple-vector having the same structure.
block-const*
  Objects representing closed-up `block` tags, representing the `block` tags of the lexical environment in which this function was defined. Each is a cons containing in the cdr part: either a frame pointer to the block frame, or `#<DISABLED>` if the block has already been left. The car is the block's name, only for error message purposes.
tagbody-const*
  Objects representing closed-up `tagbody` tags, representing the `tagbody` tags of the lexical environment in which this function was defined. Each is a cons containing in the cdr part: either a frame pointer to the tagbody frame, or `#<DISABLED>` if the tagbody has already been left. The car is a simple-vector containing the names of the `tagbody` tags, only for error message purposes.
keyword-const*
  If the function was defined with a lambda list containing `&key`, here come the symbols ("keywords"), in their correct order. They are used by the interpreter during function call.
other-const*
  Other objects needed by the function's bytecode.

If venv-const, block-const, tagbody-const are all absent, the function is called autonomous. This is the case if the function does not refer to lexical variables, blocks or tags defined in compile code outside of the function. In particular, it is the case if the function is defined in a null lexical environment.

If some venv-const, block-const, or tagbody-const are present, the function (a "closure") is created at runtime. The compiler only generates a prototype, containing `nil` values instead of each venv-const, block-const, tagbody-const. At runtime, a function is created by copying this prototype and replacing the `nil` values by the definitive ones.

The list `(keyword-const* other-const*)` normally doesn't contain duplicates, because the compiler removes duplicates when possible. (Duplicates can occur nevertheless, through the use of `load-time-value`.)

The codevec looks like this (cf. C type `Codevec`):

spdepth_1 (2 bytes)
  The 1 part of the maximal SP depth.
spdepth_jmpbufsize (2 bytes)
  The jmpbufsize part of the maximal SP depth. The maximal SP depth (precomputed by the compiler) is given by spdepth_1 + spdepth_jmpbufsize * jmpbufsize.
numreq (2 bytes)
  Number of required parameters.
numopt (2 bytes)
  Number of optional parameters.
flags (1 byte)
  bit 0
    set if the function has an `&rest` parameter
  bit 7
    set if the function has `&key` parameters
  bit 6
    set if the function has `&allow-other-keys`
  bit 4
    set if the function is a generic function
  bit 3
    set if the function is a generic function and its effective method shall be returned (instead of being executed)
signature (1 byte)
  An abbreviation code depending on numreq, numopt, flags. It is used for speeding up the function call.
numkey (2 bytes, only if the function has `&key`)
  The number of `&key` parameters.
keyconsts (2 bytes, only if the function has `&key`)
  The offset of the keyword-const in the function.
byte* (any number of bytes)
  The bytecode instructions.

## The general structure of the instructions

All instructions consist of one byte, denoting the opcode, and some number of operands.

The conversion from a byte (in the range 0..255) to the opcode is performed by lookup in the table contained in the file `bytecode.d`.

There are the following types of operands, denoted by different letters:

k, n, m, l
  A (nonnegative) numeric operand. The next byte is read. If its bit 7 is zero, then the bits 6..0 give the value (7 bits). If its bit 7 is one, then the bits 6..0 and the subsequent byte together form the value (15 bits).
b
  A (nonnegative) 1-byte operand. The next byte is read and is the value.
label
  A label operand. A signed numeric operand is read: The next byte is read. If its bit 7 is zero, then the bits 6..0 give the value (7 bits, sign-extended). If its bit 7 is one, then the bits 6..0 and the subsequent byte together form the value (15 bits, sign-extended). If the latter 15-bit result is zero, then four more bytes are read and put together (32 bits, sign-extended). Finally, the bytecode pointer for the target is computed as the current bytecode pointer (pointing after the operand just read), plus the signed numeric operand.

## The instruction set

### 1. Instructions for constants

mnemonic | description | semantics
--- | --- | ---
`(NIL)` | Load `nil` into values. | value1 := `nil`, mv_count := 1
`(PUSH-NIL n)` | Push n `nil`s into the STACK. | n times do: *--STACK := `nil`, values undefined
`(T)` | Load `t` into values. | value1 := `t`, mv_count := 1
`(CONST n)` | Load the function's nth constant into values. | value1 := consts[n], mv_count := 1

### 2. Instructions for lexical variables

mnemonic | description | semantics
--- | --- | ---
`(LOAD n)` | Load a directly accessible local variable into values. | value1 := *(STACK+n), mv_count := 1
`(LOADI k1 k2 n)` | Load an indirectly accessible local variable into values. | k := k1 + jmpbufsize * k2, value1 := *(*(SP+k)+n), mv_count := 1
`(LOADC n m)` | Load a closed-up variable, defined in the same function and directly accessible, into values. | value1 := svref(*(STACK+n),1+m), mv_count := 1
`(LOADV k m)` | Load a closed-up variable, defined in an outer function, into values. | vector := venv-const, m times do: vector := svref(vector,0), value1 := svref(vector,m), mv_count := 1
`(LOADIC k1 k2 n m)` | Load a closed-up variable, defined in the same function and indirectly accessible, into values. | k := k1 + jmpbufsize * k2, value1 := svref(*(*(SP+k)+n),1+m), mv_count := 1
`(STORE n)` | Store values into a directly accessible local variable. | *(STACK+n) := value1, mv_count := 1
`(STOREI k1 k2 n)` | Store values into an indirectly accessible local variable. | k := k1 + jmpbufsize * k2, *(*(SP+k)+n) := value1, mv_count := 1
`(STOREC n m)` | Store values into a closed-up variable, defined in the same function and directly accessible. | svref(*(STACK+n),1+m) := value1, mv_count := 1
`(STOREV k m)` | Store values into a closed-up variable, defined in an outer function. | vector := venv-const, m times do: vector := svref(vector,0), svref(vector,m) := value1, mv_count := 1
`(STOREIC k1 k2 n m)` | Store values into a closed-up variable, defined in the same function and indirectly accessible. | k := k1 + jmpbufsize * k2, svref(*(*(SP+k)+n),1+m) := value1, mv_count := 1

### 3. Instructions for dynamic variables

mnemonic | description | semantics
--- | --- | ---
`(GETVALUE n)` | Load a symbol's value into values. | value1 := symbol-value(consts[n]), mv_count := 1
`(SETVALUE n)` | Store values into a symbol's value. | symbol-value(consts[n]) := value1, mv_count := 1
`(BIND n)` | Bind a symbol dynamically. | Bind the value of the symbol consts[n] to value1, implicitly STACK -= 3, values undefined
`(UNBIND1)` | Dissolve one binding frame. | Unbind the binding frame STACK is pointing to, implicitly STACK += 1+2*x
`(UNBIND n)` | Dissolve n binding frames. | n times do: Unbind the binding frame STACK is pointing to, thereby incrementing STACK
`(PROGV)` | Bind a set of symbols dynamically to a set of values. | symbols := *STACK++, *--SP := STACK, build a single binding frame binding the symbols in symbols to the values in value1, values undefined

### 4. Instructions for stack operations

mnemonic | description | semantics
--- | --- | ---
`(PUSH)` | Push one object onto the STACK. | *--STACK := value1, values undefined
`(POP)` | Pop one object from the STACK, into values. | value1 := *STACK++, mv_count := 1
`(SKIP n)` | Restore a previous STACK pointer. Remove n objects from the STACK. | STACK := STACK + n
`(SKIPI k1 k2 n)` | Restore a previous STACK pointer. Remove an unknown number of objects from the STACK. | k := k1 + jmpbufsize * k2, STACK := *(SP+k), SP := SP+k+1
`(SKIPSP k1 k2)` | Restore a previous SP pointer. | k := k1 + jmpbufsize * k2, SP := SP+k

### 5. Instructions for control flow, jumps

mnemonic | description | semantics
--- | --- | ---
`(SKIP&RET n)` | Clean up the STACK, and return from the function. | STACK := STACK+n, return from the function, returning values.
`(SKIP&RETGF n)` | Clean up the STACK, and return from the generic function. | If bit 3 is set in the function's flags, then STACK := STACK+n, mv_count := 1, and return from the function. Otherwise: if the current function has no `&rest` argument, then STACK := STACK+n-numreq, apply value1 to the numreq arguments still on the STACK, and return from the function. Else STACK := STACK+n-numreq-1, apply value1 to the numreq arguments and the `&rest` argument, all still on the STACK, and return from the function.
`(JMP label)` | Jump to label. | PC := label.
`(JMPIF label)` | Jump to label, if value1 is true. | If value1 is not `nil`, PC := label.
`(JMPIFNOT label)` | Jump to label, if value1 is false. | If value1 is `nil`, PC := label.
`(JMPIF1 label)` | Jump to label and forget secondary values, if value1 is true. | If value1 is not `nil`, mv_count := 1, PC := label.
`(JMPIFNOT1 label)` | Jump to label and forget secondary values, if value1 is false. | If value1 is `nil`, mv_count := 1, PC := label.
`(JMPIFATOM label)` | Jump to label, if value1 is not a cons. | If value1 is not a cons, PC := label. values undefined
`(JMPIFCONSP label)` | Jump to label, if value1 is a cons. | If value1 is a cons, PC := label. values undefined
`(JMPIFEQ label)` | Jump to label, if value1 is `eq` to the top-of-stack. | If eq(value1,*STACK++), PC := label. values undefined
`(JMPIFNOTEQ label)` | Jump to label, if value1 is not `eq` to the top-of-stack. | If not eq(value1,*STACK++), PC := label. values undefined
`(JMPIFEQTO n label)` | Jump to label, if the top-of-stack is `eq` to a constant. | If eq(*STACK++,consts[n]), PC := label. values undefined
`(JMPIFNOTEQTO n label)` | Jump to label, if the top-of-stack is not `eq` to a constant. | If not eq(*STACK++,consts[n]), PC := label. values undefined
`(JMPHASH n label)` | Table-driven jump, depending on value1. | Lookup value1 in the hash table consts[n]. (The hash table's test is either EQ or EQL.) If found, the hash table value is a signed fixnum, jump to it: PC := PC + value. Else jump to label. values undefined
`(JMPHASHV n label)` | Table-driven jump, depending on value1, inside a generic function. | Lookup value1 in the hash table svref(consts[0],n). (The hash table's test is either EQ or EQL.) If found, the hash table value is a signed fixnum, jump to it: PC := PC + value. Else jump to label. values undefined
`(JSR label)` | Subroutine call. | *--STACK := function. Then start interpreting the bytecode at label, with values undefined. When a `(RET)` is encountered, program execution is resumed at the instruction after `(JSR label)`.
`(JMPTAIL m n label)` | Tail subroutine call. | n >= m. The STACK frame of size n is reduced to size m: {*(STACK+n-m), ..., *(STACK+n-1)} := {*STACK, ..., *(STACK+m-1)}. STACK += n-m. *--STACK := function. Then jump to label, with values undefined.

### 6. Instructions for lexical environment, creation of closures

mnemonic | description | semantics
--- | --- | ---
`(VENV)` | Load the venv-const into values. | value1 := consts[0], mv_count := 1.
`(MAKE-VECTOR1&PUSH n)` | Create a simple-vector used for closed-up variables. | vector := new simple-vector of size n+1. svref(vector,0) := value1. *--STACK := vector. values undefined
`(COPY-CLOSURE m n)` | Create a closure by copying the prototype and filling in the lexical environment. | func := copy-function(consts[m]). For i=0,..,n-1: func_consts[i] := *(STACK+n-1-i). STACK += n. value1 := func, mv_count := 1

### 7. Instructions for function calls

mnemonic | description | semantics
--- | --- | ---
`(CALL k n)` | Calls a constant function with k arguments. | The function consts[n] is called with the arguments *(STACK+k-1), ..., *(STACK+0). STACK += k. The returned values go into values.
`(CALL0 n)` | Calls a constant function with 0 arguments. | The function consts[n] is called with 0 arguments. The returned values go into values.
`(CALL1 n)` | Calls a constant function with 1 argument. | The function consts[n] is called with one argument *STACK. STACK += 1. The returned values go into values.
`(CALL2 n)` | Calls a constant function with 2 arguments. | The function consts[n] is called with two arguments *(STACK+1) and *(STACK+0). STACK += 2. The returned values go into values.
`(CALLS1 b)` | Calls a system function with no `&rest`. | Calls the system function FUNTAB[b]. The right number of arguments is already on the STACK (including `#<UNBOUND>`s in place of absent `&optional` or `&key` parameters). The arguments are removed from the STACK. The returned values go into values.
`(CALLS2 b)` | Calls a system function with no `&rest`. | Calls the system function FUNTAB[256+b]. The right number of arguments is already on the STACK (including `#<UNBOUND>`s in place of absent `&optional` or `&key` parameters). The arguments are removed from the STACK. The returned values go into values.
`(CALLSR m b)` | Calls a system function with `&rest`. | Calls the system function FUNTABR[b]. The minimum number of arguments is already on the STACK, and m additional arguments as well. The arguments are removed from the STACK. The returned values go into values.
`(CALLC)` | Calls a computed compiled function with no `&key`. | Calls the compiled function value1. The right number of arguments is already on the STACK (including `#<UNBOUND>`s in place of absent `&optional` parameters). The arguments are removed from the STACK. The returned values go into values.
`(CALLCKEY)` | Calls a computed compiled function with `&key`. | Calls the compiled function value1. The right number of arguments is already on the STACK (including `#<UNBOUND>`s in place of absent `&optional` or `&key` parameters). The arguments are removed from the STACK. The returned values go into values.
`(FUNCALL n)` | Calls a computed function. | Calls the function *(STACK+n) with the arguments *(STACK+n-1), ..., *(STACK+0). STACK += n+1. The returned values go into values.
`(APPLY n)` | Calls a computed function with an unknown number of arguments. | Calls the function *(STACK+n) with the arguments *(STACK+n-1), ..., *(STACK+0) and a list of additional arguments value1. STACK += n+1. The returned values go into values.

### 8. Instructions for optional and keyword parameters

mnemonic | description | semantics
--- | --- | ---
`(PUSH-UNBOUND n)` | Push n `#<UNBOUND>`s into the STACK. | n times do: *--STACK := `#<UNBOUND>`. values undefined
`(UNLIST n m)` | Destructure a proper list. | 0 <= m <= n. n times do: *--STACK := car(value1), value1 := cdr(value1). During the last m iterations, the list value1 may already have reached its end; in this case, *--STACK := `#<UNBOUND>`. At the end, value1 must be `nil`. values undefined
`(UNLIST* n m)` | Destructure a proper or dotted list. | 0 <= m <= n, n > 0. n times do: *--STACK := car(value1), value1 := cdr(value1). During the last m iterations, the list value1 may already have reached its end; in this case, *--STACK := `#<UNBOUND>`. At the end, after n cdrs, *--STACK := value1. values undefined
`(JMPIFBOUNDP n label)` | Jump to label, if a local variable is not unbound. | If *(STACK+n) is not `#<UNBOUND>`, value1 := *(STACK+n), mv_count := 1, PC := label. Else: values undefined.
`(BOUNDP n)` | Load `t` or `nil` into values, depending on whether a local variables is not unbound. | If *(STACK+n) is not `#<UNBOUND>`, value1 := `t`, mv_count := 1. Else: value1 := `nil`, mv_count := 1.
`(UNBOUND->NIL n)` | If a local variable is unbound, assign a default value `nil` to it. | If *(STACK+n) is `#<UNBOUND>`, *(STACK+n) := `nil`.

### 9. Instructions for multiple values

mnemonic | description | semantics
--- | --- | ---
`(VALUES0)` | Load no values into values. | value1 := `nil`, mv_count := 0
`(VALUES1)` | Forget secondary values. | mv_count := 1
`(STACK-TO-MV n)` | Pop the first n objects from STACK into values. | Load values(*(STACK+n-1),...,*(STACK+0)) into values. STACK += n.
`(MV-TO-STACK)` | Save values on STACK. | Push the mv_count values onto the STACK (in order: value1 comes first). STACK -= mv_count. values undefined
`(NV-TO-STACK n)` | Save n values on STACK. | Push the first n values onto the STACK (in order: value1 comes first). STACK -= n. values undefined
`(MV-TO-LIST)` | Convert multiple values into a list. | value1 := list of values, mv_count := 1
`(LIST-TO-MV)` | Convert a list into multiple values. | Call the function `values-list` with value1 as argument. The returned values go into values.
`(MVCALLP)` | Start a multiple-value-call invocation. | *--SP := STACK. *--STACK := value1.
`(MVCALL)` | Finish a multiple-value-call invocation. | newSTACK := *SP++. Call the function *(newSTACK-1), passing it *(newSTACK-2), ..., *(STACK+0) as arguments. STACK := newSTACK. The returned values go into values.

### 10. Instructions for block and return-from

mnemonic | description | semantics
--- | --- | ---
`(BLOCK-OPEN n label)` | Create a block frame. | Create a block frame, STACK -= 3, SP -= 2+jmpbufsize. The topmost (third) object in the block frame is cons(consts[n],frame-pointer). Upon a return-from to this frame, execution will continue at label.
`(BLOCK-CLOSE)` | Dissolve a block frame. | Dissolve the block frame at STACK, STACK += 3, SP += 2+jmpbufsize. Mark the block-cons as invalid.
`(RETURN-FROM n)` | Leave a block whose block-cons is given. | block-cons := consts[n]. If cdr(block-cons) = `#<DISABLED>`, signal an error. Else cdr(block-cons) is a frame-pointer. Unwind the stack up to this frame, pass it values.
`(RETURN-FROM-I k1 k2 n)` | Leave a block whose block-cons is indirectly accessible. | k := k1 + jmpbufsize * k2, block-cons := *(*(SP+k)+n). If cdr(block-cons) = `#<DISABLED>`, signal an error. Else cdr(block-cons) is a frame-pointer. Unwind the stack up to this frame, pass it values.

### 11. Instructions for tagbody and go

mnemonic | description | semantics
--- | --- | ---
`(TAGBODY-OPEN n label1 ... labelm)` | Create a tagbody frame. | Fetch consts[n], this is a simple-vector with m elements, then decode m label operands. Create a tagbody frame, STACK -= 3+m, SP -= 1+jmpbufsize. The third object in the tagbody frame is cons(consts[n],frame-pointer). Upon a go to tag l of this frame, execution will continue at labell. values undefined
`(TAGBODY-CLOSE-NIL)` | Dissolve a tagbody frame, and load `nil` into values. | Dissolve the tagbody frame at STACK, STACK += 3+m, SP += 1+jmpbufsize. Mark the tagbody-cons as invalid. value1 := `nil`, mv_count := 1.
`(TAGBODY-CLOSE)` | Dissolve a tagbody frame. | Dissolve the tagbody frame at STACK, STACK += 3+m, SP += 1+jmpbufsize. Mark the tagbody-cons as invalid.
`(GO n l)` | Jump into a tagbody whose tagbody-cons is given. | tagbody-cons := consts[n]. If cdr(tagbody-cons) = `#<DISABLED>`, signal an error. Else cdr(tagbody-cons) is a frame-pointer. Unwind the stack up to this frame, pass it the number l.
`(GO-I k1 k2 n l)` | Jump into a tagbody whose tagbody-cons is indirectly accessible. | k := k1 + jmpbufsize * k2, tagbody-cons := *(*(SP+k)+n). If cdr(tagbody-cons) = `#<DISABLED>`, signal an error. Else cdr(tagbody-cons) is a frame-pointer. Unwind the stack up to this frame, pass it the number l.

### 12. Instructions for catch and throw

mnemonic | description | semantics
--- | --- | ---
`(CATCH-OPEN label)` | Create a catch frame. | Create a catch frame, with value1 as tag. STACK -= 3, SP -= 2+jmpbufsize. Upon a throw to this tag execution continues at label.
`(CATCH-CLOSE)` | Dissolve a catch frame. | Dissolve the catch frame at STACK. STACK += 3, SP += 2+jmpbufsize.
`(THROW)` | Non-local exit to a catch frame. | tag := *STACK++. Search the innermost catch frame with tag tag on the STACK, unwind the stack up to it, pass it values.

### 13. Instructions for unwind-protect

mnemonic | description | semantics
--- | --- | ---
`(UNWIND-PROTECT-OPEN label)` | Create an unwind-protect frame. | Create an unwind-protect frame. STACK -= 2, SP -= 2+jmpbufsize. When the stack will be unwound by a non-local exit, values will be saved on STACK, and execution will be transferred to label.
`(UNWIND-PROTECT-NORMAL-EXIT)` | Dissolve an unwind-protect frame, and start the cleanup code. | Dissolve the unwind-protect frame at STACK. STACK += 2, SP += 2+jmpbufsize. *--SP := 0, *--SP := 0, *--SP := STACK. Save the values on the STACK, STACK -= mv_count.
`(UNWIND-PROTECT-CLOSE)` | Terminate the cleanup code. | newSTACK := *SP++. Load values(*(newSTACK-1), ..., *(STACK+0)) into values. STACK := newSTACK. SPword1 := *SP++, SPword2 := *SP++. Continue depending on SPword1 and SPword2. If both are 0, simply continue execution. If SPword2 is 0 but SPword1 is nonzero, interpret it as a label and jump to it.
`(UNWIND-PROTECT-CLEANUP)` | Dissolve an unwind-protect frame, and execute the cleanup code like a subroutine call. | Dissolve the unwind-protect frame at STACK, get label out of the frame. STACK += 2, SP += 2+jmpbufsize. *--SP := 0, *--SP := PC, *--SP := STACK. Save the values on the STACK, STACK -= mv_count. PC := label.

### 14. Instructions for handler-bind

mnemonic | description | semantics
--- | --- | ---
`(HANDLER-OPEN n)` | Create a handler frame. | Create a handler frame, using consts[n] which contains the condition types, the corresponding labels and the current SP depth (= function entry SP - current SP).
`(HANDLER-BEGIN&PUSH)` | Start a handler. | Restore the same SP state as after the HANDLER-OPEN. value1 := the condition that was passed to the handler, mv_count := 1. *--STACK := value1.

### 15. Instructions for some inlined functions

mnemonic | description | semantics
--- | --- | ---
`(NOT)` | Inlined call to NOT. | value1 := not(value1), mv_count := 1.
`(EQ)` | Inlined call to EQ. | value1 := eq(*STACK++,value1), mv_count := 1.
`(CAR)` | Inlined call to CAR. | value1 := car(value1), mv_count := 1.
`(CDR)` | Inlined call to CDR. | value1 := cdr(value1), mv_count := 1.
`(CONS)` | Inlined call to CONS. | value1 := cons(*STACK++,value1), mv_count := 1.
`(SYMBOL-FUNCTION)` | Inlined call to SYMBOL-FUNCTION. | value1 := symbol-function(value1), mv_count := 1.
`(SVREF)` | Inlined call to SVREF. | value1 := svref(*STACK++,value1), mv_count := 1.
`(SVSET)` | Inlined call to SYSTEM::SVSTORE. | arg1 := *(STACK+1), arg2 := *(STACK+0), STACK += 2. svref(arg2,value1) := arg1. value1 := arg1, mv_count := 1.
`(LIST n)` | Inlined call to LIST. | value1 := list(*(STACK+n-1),...,*(STACK+0)), mv_count := 1, STACK += n.
`(LIST* n)` | Inlined call to LIST*. | value1 := list*(*(STACK+n-1),...,*(STACK+0),value1), mv_count := 1, STACK += n.

### 16. Combined instructions

The most frequent short sequences of instructions have an equivalent combined instruction. They are only present for space and speed optimization. The only exception is `FUNCALL&SKIP&RETGF`, whose existence is needed for generic functions.

mnemonic | equivalent
--- | ---
`(NIL&PUSH)` | `(NIL) (PUSH)`
`(T&PUSH)` | `(T) (PUSH)`
`(CONST&PUSH n)` | `(CONST n) (PUSH)`
`(LOAD&PUSH n)` | `(LOAD n) (PUSH)`
`(LOADI&PUSH k1 k2 n)` | `(LOADI k1 k2 n) (PUSH)`
`(LOADC&PUSH n m)` | `(LOADC n m) (PUSH)`
`(LOADV&PUSH k m)` | `(LOADV k m) (PUSH)`
`(POP&STORE n)` | `(POP) (STORE n)`
`(GETVALUE&PUSH n)` | `(GETVALUE n) (PUSH)`
`(JSR&PUSH label)` | `(JSR label) (PUSH)`
`(COPY-CLOSURE&PUSH m n)` | `(COPY-CLOSURE m n) (PUSH)`
`(CALL&PUSH k n)` | `(CALL k n) (PUSH)`
`(CALL1&PUSH n)` | `(CALL1 n) (PUSH)`
`(CALL2&PUSH n)` | `(CALL2 n) (PUSH)`
`(CALLS1&PUSH b)` | `(CALLS1 b) (PUSH)`
`(CALLS2&PUSH b)` | `(CALLS2 b) (PUSH)`
`(CALLSR&PUSH m n)` | `(CALLSR m n) (PUSH)`
`(CALLC&PUSH)` | `(CALLC) (PUSH)`
`(CALLCKEY&PUSH)` | `(CALLCKEY) (PUSH)`
`(FUNCALL&PUSH n)` | `(FUNCALL n) (PUSH)`
`(APPLY&PUSH n)` | `(APPLY n) (PUSH)`
`(CAR&PUSH)` | `(CAR) (PUSH)`
`(CDR&PUSH)` | `(CDR) (PUSH)`
`(CONS&PUSH)` | `(CONS) (PUSH)`
`(LIST&PUSH n)` | `(LIST n) (PUSH)`
`(LIST*&PUSH n)` | `(LIST* n) (PUSH)`
`(NIL&STORE n)` | `(NIL) (STORE n)`
`(T&STORE n)` | `(T) (STORE n)`
`(LOAD&STOREC k n m)` | `(LOAD k) (STOREC n m)`
`(CALLS1&STORE b k)` | `(CALLS1 b) (STORE k)`
`(CALLS2&STORE b k)` | `(CALLS2 b) (STORE k)`
`(CALLSR&STORE m n k)` | `(CALLSR m n) (STORE k)`
`(LOAD&CDR&STORE n)` | `(LOAD n) (CDR) (STORE n)`
`(LOAD&CONS&STORE n)` | `(LOAD n+1) (CONS) (STORE n)`
`(LOAD&INC&STORE n)` | `(LOAD n) (CALL1 #'1+) (STORE n)`
`(LOAD&DEC&STORE n)` | `(LOAD n) (CALL1 #'1-) (STORE n)`
`(LOAD&CAR&STORE m n)` | `(LOAD m) (CAR) (STORE n)`
`(CALL1&JMPIF n label)` | `(CALL1 n) (JMPIF label)`
`(CALL1&JMPIFNOT n label)` | `(CALL1 n) (JMPIFNOT label)`
`(CALL2&JMPIF n label)` | `(CALL2 n) (JMPIF label)`
`(CALL2&JMPIFNOT n label)` | `(CALL2 n) (JMPIFNOT label)`
`(CALLS1&JMPIF b label)` | `(CALLS1 b) (JMPIF label)`
`(CALLS1&JMPIFNOT b label)` | `(CALLS1 b) (JMPIFNOT label)`
`(CALLS2&JMPIF b label)` | `(CALLS2 b) (JMPIF label)`
`(CALLS2&JMPIFNOT b label)` | `(CALLS2 b) (JMPIFNOT label)`
`(CALLSR&JMPIF m n label)` | `(CALLSR m n) (JMPIF label)`
`(CALLSR&JMPIFNOT m n label)` | `(CALLSR m n) (JMPIFNOT label)`
`(LOAD&JMPIF n label)` | `(LOAD n) (JMPIF label)`
`(LOAD&JMPIFNOT n label)` | `(LOAD n) (JMPIFNOT label)`
`(LOAD&CAR&PUSH n)` | `(LOAD n) (CAR) (PUSH)`
`(LOAD&CDR&PUSH n)` | `(LOAD n) (CDR) (PUSH)`
`(LOAD&INC&PUSH n)` | `(LOAD n) (CALL1 #'1+) (PUSH)`
`(LOAD&DEC&PUSH n)` | `(LOAD n) (CALL1 #'1-) (PUSH)`
`(CONST&SYMBOL-FUNCTION n)` | `(CONST n) (SYMBOL-FUNCTION)`
`(CONST&SYMBOL-FUNCTION&PUSH n)` | `(CONST n) (SYMBOL-FUNCTION) (PUSH)`
`(CONST&SYMBOL-FUNCTION&STORE n k)` | `(CONST n) (SYMBOL-FUNCTION) (STORE k)`
`(APPLY&SKIP&RET n k)` | `(APPLY n) (SKIP&RET k)`
`(FUNCALL&SKIP&RETGF n k)` | `(FUNCALL n) (SKIP&RETGF k)`

### 17. Shortcut instructions

There are special one-byte instructions (without explicit operands) for the following frequent instructions:

mnemonic | operand range
--- | ---
`(LOAD n)` | 0 <= n < 15
`(LOAD&PUSH n)` | 0 <= n < 25
`(CONST n)` | 0 <= n < 21
`(CONST&PUSH n)` | 0 <= n < 30
`(STORE n)` | 0 <= n < 8

Last modified: 19 September 1998.