Procedures#

struct UNIT_Procedure#

A container of instructions representing a function that will eventually be compiled by UNIT.

UNIT_Context *context#

The context being used by this procedure.

const char *name#

The name of the procedure.

Lifecycle#

UNIT_Status UNIT_Procedure_Init(UNIT_Procedure *procedure, UNIT_Context *context, const char *name)#

Initialize a procedure. On success, UNIT_Procedure_Clear() must be called later to free memory allocated by this function.

Parameters:
  • procedure – A pointer to a procedure. Memory at this location will be overwritten.

  • context – The context that will be used when interacting with the procedure. This must be valid for the lifetime of the procedure.

  • name – A string indicating the name of the procedure. This string is copied internally.

Returns:

Indicator whether the call was successful. See UNIT_FAILED.

Example#
 1UNIT_Context context;
 2UNIT_Context_Init(&context);
 3
 4UNIT_Procedure procedure;
 5if (UNIT_FAILED(UNIT_Procedure_Init(&procedure, &context, "main"))) {
 6    UNIT_PrintError(&context, stderr);
 7    UNIT_Context_Clear(&context);
 8    return -1;
 9}
10
11// Must call UNIT_Procedure_Clear() later.
UNIT_Procedure *UNIT_Procedure_New(UNIT_Context *context, const char *name)#

Create a new heap-allocated procedure. On success, UNIT_Procedure_Free() must be called later.

Parameters:
  • context – The context that will be used when interacting with the procedure.

  • name – The name of the procedure. Copied internally.

Returns:

A heap-allocated procedure, or NULL on failure.

void UNIT_Procedure_Clear(UNIT_Procedure *procedure)#

Free memory allocated by UNIT_Procedure_Init().

Parameters:
  • procedure – The procedure to clear.

void UNIT_Procedure_Free(UNIT_Procedure *procedure)#

Free memory allocated by UNIT_Procedure_New(). Safe to call with NULL.

Parameters:
  • procedure – The procedure to free, or NULL.

Emitting instructions#

UNIT_Status UNIT_Procedure_AddOperation(UNIT_Procedure *procedure, UNIT_OperationCode instruction, int64_t argument)#

Add a stack instruction to the procedure. This is the general-purpose function for emitting most instructions. Some instructions require specialized functions instead (see below).

Parameters:
  • procedure – The procedure to add the instruction to.

  • instruction – The opcode. See Operation codes.

  • argument – The operand. Meaning depends on the instruction.

Returns:

Indicator whether the call was successful.

Example#
// return 42
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 42);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_RETURN_VALUE, 0);
UNIT_Status UNIT_Procedure_AddStringLoad(UNIT_Procedure *procedure, const char *str)#

Push a string constant onto the stack. The string is copied internally.

Parameters:
  • procedure – The procedure.

  • str – The string to push.

Example#
UNIT_Procedure_AddStringLoad(&proc, "hello %s\n");
UNIT_Status UNIT_Procedure_AddCallName(UNIT_Procedure *procedure, const char *name, UNIT_Size num_arguments)#

Call an external function by name. The top num_arguments stack items are passed as arguments (first pushed = first argument). The return value is pushed onto the stack.

This emits both UNIT_OP_PREPARE_CALL and UNIT_OP_CALL_NAME internally.

Parameters:
  • procedure – The procedure.

  • name – The function name. Resolved via dlsym at link time or JIT time. See UNIT_SymbolMap_RegisterSymbol() for custom resolution.

  • num_arguments – The number of arguments to pass.

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);
UNIT_Status UNIT_Procedure_AddCallProcedure(UNIT_Procedure *self, UNIT_Procedure *target, uint8_t nargs)#

Call another UNIT procedure. The subprocedure will be translated and compiled during compilation of this procedure.

Parameters:
  • self – The calling procedure.

  • target – The procedure to call.

  • nargs – The number of arguments to pass.

Example#
UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 5);
UNIT_Procedure_AddCallProcedure(&main_proc, &factorial_proc, 1);

Local Variables#

UNIT_Status UNIT_Procedure_CreateLocal(UNIT_Procedure *procedure, const char *name, UNIT_Local *local_ptr)#

Create a named local variable. The index is assigned automatically. The returned UNIT_Local handle can be used with UNIT_Procedure_AddStoreName() and UNIT_Procedure_AddLoadName().

Parameters:
  • procedure – The procedure.

  • name – A descriptive name for the variable. Copied internally.

  • local_ptr – Output parameter receiving the local handle.

Example#
UNIT_Local x;
UNIT_Procedure_CreateLocal(&proc, "x", &x);

UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 42);
UNIT_Procedure_AddStoreName(&proc, x);
UNIT_Procedure_AddLoadName(&proc, x);
struct UNIT_Local#

A handle to a local variable.

int32_t id#

The local variable index.

UNIT_Status UNIT_Procedure_AddStoreName(UNIT_Procedure *procedure, UNIT_Local local)#

Pop the top of the stack into a local variable by handle. This is equivalent to UNIT_OP_STORE_LOCAL but uses the handle from UNIT_Procedure_CreateLocal().

Parameters:
  • procedure – The procedure.

  • local – The local variable handle.

UNIT_Status UNIT_Procedure_AddLoadName(UNIT_Procedure *procedure, UNIT_Local local)#

Push a local variable onto the stack by handle.

Parameters:
  • procedure – The procedure.

  • local – The local variable handle.

Jump Labels#

struct UNIT_JumpLabel#

A jump target, created by UNIT_Procedure_CreateJumpLabel().

const char *name#

The label name (heap-allocated, owned by the procedure).

int32_t id#

The label ID.

UNIT_JumpLabel *UNIT_Procedure_CreateJumpLabel(UNIT_Procedure *procedure, const char *name)#

Create a jump target. The label must later be placed with UNIT_Procedure_UseLabel().

Parameters:
  • procedure – The procedure.

  • name – A descriptive name for the label. Copied internally.

Returns:

A pointer to the label, or NULL on failure. The label is owned by the procedure and must not be freed by the caller.

UNIT_Status UNIT_Procedure_UseLabel(UNIT_Procedure *procedure, UNIT_JumpLabel *jump_label)#

Place a label at the current position in the instruction stream. All jumps to this label will target the next instruction emitted.

Parameters:
  • procedure – The procedure.

  • jump_label – The label to place.

UNIT_Status UNIT_Procedure_AddJump(UNIT_Procedure *procedure, UNIT_OperationCode instruction, UNIT_JumpLabel *jump_label)#

Emit a jump instruction targeting the given label. The instruction must be one of UNIT_OP_JUMP, UNIT_OP_JUMP_IF_TRUE, or UNIT_OP_JUMP_IF_FALSE.

Parameters:
  • procedure – The procedure.

  • instruction – The jump opcode.

  • jump_label – The target label.

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

UNIT_Procedure_AddOperation(&proc, UNIT_OP_LOAD_INTEGER, 0);
UNIT_Procedure_AddOperation(&proc, UNIT_OP_COMPARE_EQUAL, 0);
UNIT_Procedure_AddJump(&proc, UNIT_OP_JUMP_IF_TRUE, end);

// ... code skipped when condition is true ...

UNIT_Procedure_UseLabel(&proc, end);
// ... continues here ...

Flags#

void UNIT_Procedure_SetFlags(UNIT_Procedure *procedure, UNIT_Flags flags)#

Set procedure flags. Flags can be combined with bitwise OR.

Parameters:
  • procedure – The procedure.

  • flags – Bitwise OR of flag constants.

UNIT_Flags UNIT_Procedure_GetFlags(const UNIT_Procedure *procedure)#

Return the current procedure flags.

Parameters:
  • procedure – The procedure.

Returns:

The current flags.

UNIT_FLAG_NONE#

No flags. This is always 0.

UNIT_FLAG_NO_OPTIMIZE_TRANSLATION#

Skip register IR optimization (move coalescing, dead move elimination, forward copy propagation) during compilation.

UNIT_FLAG_FORCE_NO_INLINE#

Prevent this procedure from being inlined into callers, regardless of size.

UNIT_FLAG_FORCE_INLINE#

Always inline this procedure into callers, regardless of size.

UNIT_FLAG_PRINT_TRANSLATION_PREOP#

Print the register IR to stderr before optimization runs. Useful for debugging.

UNIT_FLAG_PRINT_TRANSLATION_POSTOP#

Print the register IR to stderr after optimization runs. Useful for debugging.

Example#
UNIT_Procedure_SetFlags(&proc, UNIT_FLAG_FORCE_NO_INLINE | UNIT_FLAG_NO_OPTIMIZE_TRANSLATION);

Optimization#

UNIT_Status UNIT_Procedure_Optimize(UNIT_Procedure *procedure)#

Run stack IR optimization passes on the procedure. This includes: inlining, constant folding, dead store elimination, and local variable optimization. Call this before UNIT_Compile().

Multiple iterations are run automatically until no further changes occur.

Parameters:
  • procedure – The procedure to optimize.

Example#
UNIT_Procedure_Optimize(&proc);

Debugging#

UNIT_Status UNIT_Procedure_PrintInstructions(const UNIT_Procedure *procedure, FILE *stream, int8_t visualize_stack_effect)#

Print the stack IR to a file stream.

Parameters:
  • procedure – The procedure.

  • stream – The output stream (e.g. stdout).

  • visualize_stack_effect – If nonzero, show the stack state after each instruction.

Example#
UNIT_Procedure_PrintInstructions(&proc, stdout, /*visualize_stack_effect=*/1);
const char *UNIT_OperationCode_GetName(UNIT_OperationCode instruction)#

Return the name of an opcode as a string (e.g. "LOAD_INTEGER").

Parameters:
  • instruction – The opcode.

Returns:

A static string. Must not be freed.