Operation codes#

enum UNIT_OperationCode#

Enumerated type containing the ID for all UNIT stack-based instructions.

Constants#

enumerator UNIT_OP_LOAD_INTEGER#

Push a constant integer onto the stack.

Stack effect: -- value

The operand is the integer value to push.

Example#
// Push 42 onto the stack
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 42);
enumerator UNIT_OP_LOAD_STRING#

Push a constant string onto the stack. Emitted by UNIT_Procedure_AddStringLoad() rather than UNIT_Procedure_AddOperation().

Stack effect: -- pointer

Example#
UNIT_Procedure_AddStringLoad(&proc, "hello world");

Arithmetic#

All arithmetic instructions pop two values and push the result. The first value pushed is the left operand.

enumerator UNIT_OP_ADD#

Stack effect: a b -- a+b

UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 10);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 20);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_ADD, 0);
// stack: [30]
enumerator UNIT_OP_SUBTRACT#

Stack effect: a b -- a-b

enumerator UNIT_OP_MULTIPLY#

Stack effect: a b -- a*b

enumerator UNIT_OP_DIVIDE#

Integer division, truncated toward zero.

Stack effect: a b -- a/b

enumerator UNIT_OP_MODULO#

Integer remainder.

Stack effect: a b -- a%b

Local Variables#

Locals are created with UNIT_Procedure_CreateLocal() and accessed by index. Named access is also available via UNIT_Procedure_AddStoreName() and UNIT_Procedure_AddLoadName().

enumerator UNIT_OP_STORE_LOCAL#

Pop the top of the stack into a local variable.

Stack effect: value --

The operand is the local variable index.

Example#
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 42);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_STORE_LOCAL, 0);
enumerator UNIT_OP_LOAD_LOCAL#

Push the value of a local variable onto the stack.

Stack effect: -- value

The operand is the local variable index.

UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_LOCAL, 0);
enumerator UNIT_OP_ADDRESS_OF#

Push the memory address of a local variable onto the stack. This is used for pointer operations with UNIT_OP_READ_BYTES and UNIT_OP_WRITE_BYTES.

Stack effect: -- address

The operand is the local variable index.

Memory Access#

These instructions read and write raw memory at arbitrary addresses. The address is typically obtained from UNIT_OP_ADDRESS_OF or from a call to an external function like malloc.

enumerator UNIT_OP_READ_BYTES#

Pop an address, read the specified number of bytes from it, and push the value. Valid sizes are 1, 2, 4, and 8.

Stack effect: address -- value

The operand is the number of bytes to read.

Example#
// Read a single byte from the pointer on the stack
UNIT_Procedure_AddOperation(&proc, UNIT_OP_READ_BYTES, 1);
enumerator UNIT_OP_WRITE_BYTES#

Pop an address and a value, write the specified number of bytes to the address. Valid sizes are 1, 2, 4, and 8.

Stack effect: address value --

The operand is the number of bytes to write.

Example#
// Write a single byte to the pointer on the stack
UNIT_Procedure_AddOperation(&proc, UNIT_OP_WRITE_BYTES, 1);

Comparisons#

All comparison instructions pop two values and push a comparison result. The result is consumed by UNIT_OP_JUMP_IF_TRUE or UNIT_OP_JUMP_IF_FALSE. The first value pushed is the left operand.

enumerator UNIT_OP_COMPARE_EQUAL#

Stack effect: a b -- result

Example#
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 5);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 5);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_COMPARE_EQUAL, 0);
// result: true (5 == 5)
enumerator UNIT_OP_COMPARE_NOT_EQUAL#

Stack effect: a b -- result

enumerator UNIT_OP_COMPARE_LESS#

Stack effect: a b -- result

enumerator UNIT_OP_COMPARE_LESS_EQUAL#

Stack effect: a b -- result

enumerator UNIT_OP_COMPARE_GREATER#

Stack effect: a b -- result

enumerator UNIT_OP_COMPARE_GREATER_EQUAL#

Stack effect: a b -- result

Control Flow#

enumerator UNIT_OP_JUMP#

Unconditionally jump to a label. Emitted by UNIT_Procedure_AddJump().

Example#
UNIT_JumpLabel *end = UNIT_Procedure_CreateJumpLabel(&proc, "end");
UNIT_Procedure_AddJump(&proc, UNIT_OP_JUMP, end);
// ...
UNIT_Procedure_UseLabel(&proc, end);
enumerator UNIT_OP_JUMP_IF_TRUE#

Pop a comparison result. Jump to the label if the result is true. Emitted by UNIT_Procedure_AddJump().

Stack effect: comparison --

enumerator UNIT_OP_JUMP_IF_FALSE#

Pop a comparison result. Jump to the label if the result is false. Emitted by UNIT_Procedure_AddJump().

Stack effect: comparison --

Example#
UNIT_JumpLabel *else_branch = UNIT_Procedure_CreateJumpLabel(&proc, "else");

// if (a == b) ...
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_LOCAL, 0);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_LOCAL, 1);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_COMPARE_EQUAL, 0);
UNIT_Procedure_AddJump(&proc, UNIT_OP_JUMP_IF_FALSE, else_branch);

// then branch ...

UNIT_Procedure_UseLabel(&proc, else_branch);
// else branch ...

Function Calls#

enumerator UNIT_OP_PREPARE_CALL#

Collect arguments from the stack for a function call. This instruction is emitted automatically by UNIT_Procedure_AddCallName() and UNIT_Procedure_AddCallProcedure(). You should not emit it manually.

The operand is the number of arguments.

enumerator UNIT_OP_CALL_NAME#

Call a named external function and push the return value. Emitted by UNIT_Procedure_AddCallName().

Stack effect: arg1 arg2 ... argN -- result

Example#
// printf("%d\n", 42)
UNIT_Procedure_AddStringLoad(&proc, "%d\n");
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 42);
UNIT_Procedure_AddCallName(&proc, "printf", 2);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_POP, 0); // discard return
enumerator UNIT_OP_CALL_PROCEDURE#

Call another UNIT procedure. Emitted by UNIT_Procedure_AddCallProcedure(). This enables inlining during optimization.

Stack effect: arg1 arg2 ... argN -- result

enumerator UNIT_OP_LOAD_ARGUMENT#

Push a function argument onto the stack. Argument 0 is the first parameter passed to the procedure.

Stack effect: -- value

The operand is the argument index.

Example#
// Load the first two arguments and add them
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_ARGUMENT, 0);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_ARGUMENT, 1);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_ADD, 0);
enumerator UNIT_OP_RETURN_VALUE#

Pop the top of the stack and return it to the caller. Ends execution of the current procedure.

Stack effect: value --

Example#
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 0);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_RETURN_VALUE, 0);
enumerator UNIT_OP_EXIT#

Terminate the entire process with the value on top of the stack as the exit code.

Stack effect: code --

Stack Manipulation#

enumerator UNIT_OP_POP#

Discard the top of the stack.

Stack effect: value --

enumerator UNIT_OP_COPY#

Duplicate a stack item at the given depth. COPY 0 duplicates the top of the stack.

Stack effect: -- value

The operand is the depth (0 = top, 1 = second item, etc).

Example#
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 10);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 20);
// stack: [10, 20]
UNIT_Procedure_AddOperation(&proc, UNIT_OP_COPY, 1);
// stack: [10, 20, 20]
enumerator UNIT_OP_SWAP#

Swap the top of the stack with the item at the given depth.

Stack effect: unchanged (items rearranged)

The operand is the depth (1 = second item, 2 = third item, etc).

Example#
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 10);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 20);
// stack: [10, 20]
UNIT_Procedure_AddOperation(&proc, UNIT_OP_SWAP, 1);
// stack: [20, 10]

Type conversion#

enumerator UNIT_OP_CONVERT#

Convert the top of the stack to a different integer width. The operand is a UNIT_IntegerType value.

Stack effect: value -- converted

enum UNIT_IntegerType#

Integer types for UNIT_OP_CONVERT.

enumerator UNIT_TYPE_INT8#
enumerator UNIT_TYPE_INT16#
enumerator UNIT_TYPE_INT32#
enumerator UNIT_TYPE_INT64#
enumerator UNIT_TYPE_UINT8#
enumerator UNIT_TYPE_UINT16#
enumerator UNIT_TYPE_UINT32#
enumerator UNIT_TYPE_UINT64#