~bakpakin/janet

480c5b5e9d485ac8b2a375bbc04326e1923c84fa — Calvin Rose 3 months ago 8a394f2 compile-opt
Change how labels are recorded.

Disallow jumping to arbitrary instructions - instead, only allow jumps
to label ids. This will make various transformations and validations
easier since adding or remove instructions does not break jumps.
2 files changed, 236 insertions(+), 117 deletions(-)

M examples/sysir/frontend.janet
M src/core/sysir.c
M examples/sysir/frontend.janet => examples/sysir/frontend.janet +112 -17
@@ 45,7 45,8 @@
  t)

(defn setup-default-types
  [into]
  [ctx]
  (def into @[])
  (defn add-prim-type
    [name native-name]
    (array/push into ~(type-prim ,name ,native-name))


@@ 54,7 55,9 @@
  (add-prim-type 'double 'f64)
  (add-prim-type 'int 's32)
  (add-prim-type 'pointer 'pointer)
  (add-prim-type 'boolean 'boolean))
  (add-prim-type 'boolean 'boolean)
  (sysir/asm ctx into)
  ctx)

(defn type-extract
  "Given a symbol:type combination, extract the proper name and the type separately"


@@ 66,9 69,13 @@
(var do-binop nil)
(var do-comp nil)

###
### Inside functions
###

(defn visit1
  "Take in a form and compile code and put it into `into`. Return result slot."
  [code into]
  [code into &opt no-return]
  (cond

    # Compile a constant


@@ 79,6 86,14 @@
      (array/push into ~(constant ,slot ,code))
      slot)

    # Booleans
    (boolean? code)
    (let [slot (get-slot)
          slottype 'boolean]
      (array/push into ~(bind ,slot ,slottype))
      (array/push into ~(constant ,slot ,(if code -1 0)))
      slot)

    # Binding
    (symbol? code)
    (named-slot code)


@@ 136,13 151,27 @@
          (array/push into ~(move ,slot ,result))
          slot)

        # Named variables
        'var
        (do
          (assert (= 2 (length args)))
          (def [full-name value] args)
          (assert (symbol? full-name))
          (def [name tp] (type-extract full-name 'int))
          (def result (visit1 value into))
          (def slot (get-slot name))
          (when tp
            (array/push into ~(bind ,slot ,tp)))
          (array/push into ~(move ,slot ,result))
          slot)

        # Assignment
        'set
        (do
          (assert (= 2 (length args)))
          (def [to x] args)
          (def result (visit1 x into))
          (def toslot (get-slot to))
          (def toslot (named-slot to))
          (array/push into ~(move ,toslot ,result))
          toslot)



@@ 160,9 189,24 @@
        # Sequence of operations
        'do
        (do
          (var ret nil)
          (each form args (set ret (visit1 form into)))
          ret)
          (each form (slice args 0 -2) (visit1 form into true))
          (visit1 (last args) into))

        # While loop
        'while
        (do
          (def lab-test (keyword (gensym)))
          (def lab-exit (keyword (gensym)))
          (assert (< 1 (length args)))
          (def [cnd & body] args)
          (array/push into lab-test)
          (def condition-slot (visit1 cnd into))
          (array/push into ~(branch-not ,condition-slot ,lab-exit))
          (each code body
            (visit1 code into true))
          (array/push into ~(jump ,lab-test))
          (array/push into lab-exit)
          nil)

        # Branch
        'if


@@ 186,7 230,7 @@
        # Assume function call
        (do
          (def slots @[])
          (def ret (get-slot))
          (def ret (if no-return nil (get-slot)))
          (each arg args
            (array/push slots (visit1 arg into)))
          (array/push into ~(call ,ret ,op ,;slots))


@@ 236,27 280,78 @@
  result)

###
### Top level
###

(defn top
  "Visit and emit code for a top level form."
  [ctx form]
  (assert (tuple? form))
  (def [head & rest] form)
  (case head

    # Top level function definition
    'defn
    (do
      # TODO doc strings
      (table/clear name-to-slot)
      (array/clear slot-to-name)
      (def [name args & body] rest)
      (assert (tuple? args))
      (def [fn-name fn-tp] (type-extract name 'int))
      (def pcount (length args)) #TODO - more complicated signatures
      (def ir-asm
        @[~(link-name ,(string fn-name))
          ~(parameter-count ,pcount)])
      (each arg args
        (def [name tp] (type-extract arg 'int))
        (def slot (get-slot name))
        (array/push ir-asm ~(bind ,slot ,tp)))
      (each part body
        (visit1 part ir-asm true))
      (eprintf "%.99M\n" ir-asm)
      (sysir/asm ctx ir-asm))

    (errorf "unknown form %v" form)))

###
###
###

(def myprog
  '(do
  '(defn myprog []
     (def xyz:int (+ 1 2 3))
     (def abc:int (* 4 5 6))
     (def x:boolean (= 5 7))
     (the int (printf "hello, world!\n%d\n" (the int (if x abc xyz))))
     (var i:int 0)
     (while (< i 10)
       (set i (+ 1 i))
       (printf "i = %d\n" (the int i)))
     (printf "hello, world!\n%d\n" (the int (if x abc xyz)))
     (return (/ abc xyz))))

(def doloop
  '(defn doloop [x:int y:int]
     (var i:int x)
     (while (< i y)
       (set i (+ 1 i))
       (printf "i = %d\n" (the int i)))
     (return x)))

(def main-fn
  '(defn main:int []
     (var x:int 10)
     (doloop 10 20)
     (printf "done!\n")
     (return (the int 0))))

(defn dotest
  []
  (def ctx (sysir/context))
  (def ir-asm
    @['(link-name "main")
      '(parameter-count 0)])
  (setup-default-types ir-asm)
  (visit1 myprog ir-asm)
  (eprintf "%.99M" ir-asm)
  (sysir/asm ctx ir-asm)
  (setup-default-types ctx)
  #(top ctx myprog)
  (top ctx doloop)
  (top ctx main-fn)
  (print (sysir/to-c ctx)))

(dotest)

M src/core/sysir.c => src/core/sysir.c +124 -100
@@ 51,7 51,7 @@
 * [x] support for stack allocation of arrays
 * [ ] more math intrinsics
 * [x] source mapping (using built in Janet source mapping metadata on tuples)
 * [ ] unit type or void type
 * [x] unit type or void type
 * [ ] (typed) function pointer types and remove calling untyped pointers
 * [x] APL array semantics for binary operands (maybe?)
 * [ ] a few built-in array combinators (maybe?)


@@ 66,7 66,6 @@
#include <janet.h>
#include "util.h"
#include "vector.h"
#include <math.h>
#endif

typedef enum {


@@ 85,6 84,7 @@ typedef enum {
    JANET_PRIM_STRUCT,
    JANET_PRIM_UNION,
    JANET_PRIM_ARRAY,
    JANET_PRIM_VOID,
    JANET_PRIM_UNKNOWN
} JanetPrim;



@@ 109,6 109,7 @@ static const JanetPrimName prim_names[] = {
    {"u64", JANET_PRIM_U64},
    {"u8", JANET_PRIM_U8},
    {"union", JANET_PRIM_UNION},
    {"void", JANET_PRIM_VOID},
};

typedef enum {


@@ 139,6 140,7 @@ typedef enum {
    JANET_SYSOP_RETURN,
    JANET_SYSOP_JUMP,
    JANET_SYSOP_BRANCH,
    JANET_SYSOP_BRANCH_NOT,
    JANET_SYSOP_ADDRESS,
    JANET_SYSOP_CALLK,
    JANET_SYSOP_TYPE_PRIMITIVE,


@@ 153,6 155,7 @@ typedef enum {
    JANET_SYSOP_TYPE_UNION,
    JANET_SYSOP_POINTER_ADD,
    JANET_SYSOP_POINTER_SUBTRACT,
    JANET_SYSOP_LABEL
} JanetSysOp;

typedef struct {


@@ 170,6 173,7 @@ static const JanetSysInstrName sys_op_names[] = {
    {"bnot", JANET_SYSOP_BNOT},
    {"bor", JANET_SYSOP_BOR},
    {"branch", JANET_SYSOP_BRANCH},
    {"branch-not", JANET_SYSOP_BRANCH_NOT},
    {"bxor", JANET_SYSOP_BXOR},
    {"call", JANET_SYSOP_CALL},
    {"cast", JANET_SYSOP_CAST},


@@ 180,6 184,7 @@ static const JanetSysInstrName sys_op_names[] = {
    {"gt", JANET_SYSOP_GT},
    {"gte", JANET_SYSOP_GTE},
    {"jump", JANET_SYSOP_JUMP},
    {"label", JANET_SYSOP_LABEL},
    {"link-name", JANET_SYSOP_LINK_NAME},
    {"load", JANET_SYSOP_LOAD},
    {"lt", JANET_SYSOP_LT},


@@ 235,37 240,33 @@ typedef struct {
            uint32_t dest;
            uint32_t callee;
            uint32_t arg_count;
            uint32_t has_dest;
        } call;
        struct {
            uint32_t dest;
            uint32_t constant;
            uint32_t arg_count;
            uint32_t has_dest;
        } callk;
        struct {
            uint32_t dest;
            uint32_t src;
        } two;
        struct {
            uint32_t src;
        } one;
        struct {
            union {
                uint32_t to;
                Janet temp_label;
            };
            uint32_t to;
        } jump;
        struct {
            uint32_t cond;
            union {
                uint32_t to;
                Janet temp_label;
            };
            uint32_t to;
        } branch;
        struct {
            uint32_t dest;
            uint32_t constant;
        } constant;
        struct {
            uint32_t dest;
            uint32_t constant;
            uint32_t arg_count;
        } callk;
        struct {
            uint32_t dest_type;
            uint32_t prim;
        } type_prim;


@@ 294,6 295,9 @@ typedef struct {
            uint32_t type;
            uint64_t fixed_count;
        } array;
        struct {
            uint32_t id;
        } label;
    };
    int32_t line;
    int32_t column;


@@ 328,12 332,11 @@ typedef struct {
    uint32_t constant_count;
    uint32_t return_type;
    uint32_t parameter_count;
    uint32_t label_count;
    uint32_t *types;
    JanetSysInstruction *instructions;
    JanetString *register_names;
    Janet *constants;

    /* Can/should we remove this info after initial compilation? */
    JanetTable *register_name_lookup;
    JanetTable *labels;
} JanetSysIR;


@@ 406,16 409,21 @@ static uint64_t instr_read_u64(Janet x, JanetSysIR *ir) {
    return janet_getuinteger64(&x, 0);
}

static uint32_t instr_read_type_operand(Janet x, JanetSysIR *ir) {
static uint32_t instr_read_type_operand(Janet x, JanetSysIR *ir, int is_definition) {
    JanetSysIRLinkage *linkage = ir->linkage;
    if (janet_checktype(x, JANET_SYMBOL)) {
        Janet check = janet_table_get(linkage->type_name_lookup, x);
        if (janet_checktype(check, JANET_NUMBER)) {
            if (is_definition) {
                janet_panicf("cannot redefine type %v", x);
            }
            return (uint32_t) janet_unwrap_number(check);
        } else {
        } else if (is_definition) {
            uint32_t operand = linkage->type_def_count++;
            janet_table_put(linkage->type_name_lookup, x, janet_wrap_number(operand));
            return operand;
        } else {
            janet_panicf("unknown type %v", x);
        }
    }
    if (!janet_checkuint(x)) janet_panicf("expected non-negative integer operand, got %v", x);


@@ 440,15 448,13 @@ static JanetPrim instr_read_prim(Janet x) {
}

static uint32_t instr_read_label(JanetSysIR *sysir, Janet x) {
    (void) sysir;
    uint32_t ret = 0;
    Janet check = janet_table_get(sysir->labels, x);
    if (!janet_checktype(check, JANET_NIL)) {
        ret = (uint32_t) janet_unwrap_number(check);
    if (janet_checktype(check, JANET_NIL)) {
        ret = sysir->label_count++;
        janet_table_put(sysir->labels, x, janet_wrap_number(ret));
    } else {
        if (janet_checktype(x, JANET_KEYWORD)) janet_panicf("unknown label %v", x);
        if (!janet_checkuint(x)) janet_panicf("expected non-negative integer label, got %v", x);
        ret = (uint32_t) janet_unwrap_number(x);
        ret = (uint32_t) janet_unwrap_number(check);
    }
    return ret;
}


@@ 465,8 471,22 @@ static void janet_sysir_init_instructions(JanetSysIR *out, JanetView instruction
    Janet x = janet_wrap_nil();
    for (int32_t i = 0; i < instructions.len; i++) {
        x = instructions.items[i];
        /* Shorthand for labels */
        if (janet_checktype(x, JANET_KEYWORD)) {
            janet_table_put(labels, x, janet_wrap_number(janet_v_count(ir)));
            /* Check for existing label */
            JanetSysInstruction instruction;
            instruction.opcode = JANET_SYSOP_LABEL;
            instruction.line = -1;
            instruction.column = -1;
            instruction.label.id = instr_read_label(out, x);
            Janet label_id = janet_wrap_number(instruction.label.id);
            Janet check_defined = janet_table_get(labels, label_id);
            if (janet_checktype(check_defined, JANET_NIL)) {
                janet_table_put(labels, label_id, janet_wrap_number(janet_v_count(ir)));
            } else {
                janet_panicf("label %v already defined", x);
            }
            janet_v_push(ir, instruction);
            continue;
        }
        if (!janet_checktype(x, JANET_TUPLE)) {


@@ 540,7 560,13 @@ static void janet_sysir_init_instructions(JanetSysIR *out, JanetView instruction
                break;
            case JANET_SYSOP_CALL:
                instr_assert_min_length(tuple, 2, opvalue);
                instruction.call.dest = instr_read_operand(tuple[1], out);
                if (janet_checktype(tuple[1], JANET_NIL)) {
                    instruction.call.dest = 0;
                    instruction.call.has_dest = 0;
                } else {
                    instruction.call.dest = instr_read_operand(tuple[1], out);
                    instruction.call.has_dest = 1;
                }
                Janet c = tuple[2];
                if (janet_checktype(c, JANET_SYMBOL)) {
                    instruction.callk.arg_count = janet_tuple_length(tuple) - 3;


@@ 599,14 625,15 @@ static void janet_sysir_init_instructions(JanetSysIR *out, JanetView instruction
                janet_v_push(ir, instruction);
                break;
            case JANET_SYSOP_BRANCH:
            case JANET_SYSOP_BRANCH_NOT:
                instr_assert_length(tuple, 3, opvalue);
                instruction.branch.cond = instr_read_operand(tuple[1], out);
                instruction.branch.temp_label = tuple[2];
                instruction.branch.to = instr_read_label(out, tuple[2]);
                janet_v_push(ir, instruction);
                break;
            case JANET_SYSOP_JUMP:
                instr_assert_length(tuple, 2, opvalue);
                instruction.jump.temp_label = tuple[1];
                instruction.jump.to = instr_read_label(out, tuple[1]);
                janet_v_push(ir, instruction);
                break;
            case JANET_SYSOP_CONSTANT: {


@@ 626,22 653,22 @@ static void janet_sysir_init_instructions(JanetSysIR *out, JanetView instruction
            }
            case JANET_SYSOP_TYPE_PRIMITIVE: {
                instr_assert_length(tuple, 3, opvalue);
                instruction.type_prim.dest_type = instr_read_type_operand(tuple[1], out);
                instruction.type_prim.dest_type = instr_read_type_operand(tuple[1], out, 1);
                instruction.type_prim.prim = instr_read_prim(tuple[2]);
                janet_v_push(ir, instruction);
                break;
            }
            case JANET_SYSOP_TYPE_POINTER: {
                instr_assert_length(tuple, 3, opvalue);
                instruction.pointer.dest_type = instr_read_type_operand(tuple[1], out);
                instruction.pointer.type = instr_read_type_operand(tuple[2], out);
                instruction.pointer.dest_type = instr_read_type_operand(tuple[1], out, 1);
                instruction.pointer.type = instr_read_type_operand(tuple[2], out, 0);
                janet_v_push(ir, instruction);
                break;
            }
            case JANET_SYSOP_TYPE_ARRAY: {
                instr_assert_length(tuple, 4, opvalue);
                instruction.array.dest_type = instr_read_type_operand(tuple[1], out);
                instruction.array.type = instr_read_type_operand(tuple[2], out);
                instruction.array.dest_type = instr_read_type_operand(tuple[1], out, 1);
                instruction.array.type = instr_read_type_operand(tuple[2], out, 0);
                instruction.array.fixed_count = instr_read_u64(tuple[3], out);
                janet_v_push(ir, instruction);
                break;


@@ 649,7 676,7 @@ static void janet_sysir_init_instructions(JanetSysIR *out, JanetView instruction
            case JANET_SYSOP_TYPE_STRUCT:
            case JANET_SYSOP_TYPE_UNION: {
                instr_assert_min_length(tuple, 1, opvalue);
                instruction.type_types.dest_type = instr_read_type_operand(tuple[1], out);
                instruction.type_types.dest_type = instr_read_type_operand(tuple[1], out, 1);
                instruction.type_types.arg_count = janet_tuple_length(tuple) - 2;
                janet_v_push(ir, instruction);
                for (int32_t j = 2; j < janet_tuple_length(tuple); j += 3) {


@@ 663,7 690,7 @@ static void janet_sysir_init_instructions(JanetSysIR *out, JanetView instruction
                    int32_t remaining = janet_tuple_length(tuple) - j;
                    if (remaining > 3) remaining = 3;
                    for (int32_t k = 0; k < remaining; k++) {
                        arginstr.arg.args[k] = instr_read_type_operand(tuple[j + k], out);
                        arginstr.arg.args[k] = instr_read_type_operand(tuple[j + k], out, 0);
                    }
                    janet_v_push(ir, arginstr);
                }


@@ 672,7 699,24 @@ static void janet_sysir_init_instructions(JanetSysIR *out, JanetView instruction
            case JANET_SYSOP_TYPE_BIND: {
                instr_assert_length(tuple, 3, opvalue);
                instruction.type_bind.dest = instr_read_operand(tuple[1], out);
                instruction.type_bind.type = instr_read_type_operand(tuple[2], out);
                instruction.type_bind.type = instr_read_type_operand(tuple[2], out, 0);
                janet_v_push(ir, instruction);
                break;
            }
            case JANET_SYSOP_LABEL: {
                instr_assert_length(tuple, 2, opvalue);
                Janet x = tuple[1];
                if (!janet_checktype(x, JANET_KEYWORD)) {
                    janet_panicf("expected keyword label, got %v", x);
                }
                instruction.label.id = instr_read_label(out, x);
                Janet label_id = janet_wrap_number(instruction.label.id);
                Janet check_defined = janet_table_get(labels, label_id);
                if (janet_checktype(check_defined, JANET_NIL)) {
                    janet_table_put(labels, label_id, janet_wrap_number(janet_v_count(ir)));
                } else {
                    janet_panicf("label %v already defined", x);
                }
                janet_v_push(ir, instruction);
                break;
            }


@@ 705,7 749,7 @@ static void janet_sysir_init_instructions(JanetSysIR *out, JanetView instruction
    }
    int32_t lasti = ircount - 1;
    if ((ir[lasti].opcode != JANET_SYSOP_JUMP) && (ir[lasti].opcode != JANET_SYSOP_RETURN)) {
        janet_panicf("last instruction must be jump or return, got %v", x);
        janet_panicf("last instruction must be jump or return, got %q", x);
    }




@@ 716,24 760,6 @@ static void janet_sysir_init_instructions(JanetSysIR *out, JanetView instruction
                     out->register_count, out->parameter_count);
    }

    /* Fix up labels */
    for (uint32_t i = 0; i < ircount; i++) {
        JanetSysInstruction instruction = out->instructions[i];
        uint32_t label_target;
        switch (instruction.opcode) {
            default:
                break;
            case JANET_SYSOP_BRANCH:
                label_target = instr_read_label(out, instruction.branch.temp_label);
                out->instructions[i].branch.to = label_target;
                break;
            case JANET_SYSOP_JUMP:
                label_target = instr_read_label(out, instruction.jump.temp_label);
                out->instructions[i].jump.to = label_target;
                break;
        }
    }

    /* Build constants */
    out->constant_count = next_constant;
    out->constants = next_constant ? janet_malloc(sizeof(Janet) * out->constant_count) : NULL;


@@ 986,6 1012,7 @@ static int tcheck_cast_type(JanetSysIR *sysir, uint32_t td, uint32_t ts) {
            case JANET_PRIM_UNKNOWN:
            case JANET_PRIM_ARRAY:
            case JANET_PRIM_POINTER:
            case JANET_PRIM_VOID:
                return -1;
        }
    }


@@ 1069,7 1096,7 @@ static void tcheck_pointer_math(JanetSysIR *sysir, uint32_t dest, uint32_t lhs, 
static JanetString rname(JanetSysIR *sysir, uint32_t regid) {
    JanetString name = sysir->register_names[regid];
    if (NULL == name) {
        return janet_formatc("value%u", regid);
        return janet_formatc("value[%u]", regid);
    }
    return name;
}


@@ 1150,6 1177,7 @@ static void janet_sysir_type_check(JanetSysIR *sysir) {
                tcheck_pointer(sysir, sysir->types[instruction.two.dest]);
                break;
            case JANET_SYSOP_BRANCH:
            case JANET_SYSOP_BRANCH_NOT:
                tcheck_boolean(sysir, sysir->types[instruction.branch.cond]);
                if (instruction.branch.to >= sysir->instruction_count) {
                    janet_panicf("label outside of range [0, %u), got %u", sysir->instruction_count, instruction.branch.to);


@@ 1201,12 1229,8 @@ static void janet_sysir_type_check(JanetSysIR *sysir) {
            case JANET_SYSOP_ARG:
            case JANET_SYSOP_LINK_NAME:
            case JANET_SYSOP_PARAMETER_COUNT:
                break;
            case JANET_SYSOP_JUMP:
                ;
                if (instruction.jump.to >= sysir->instruction_count) {
                    janet_panicf("label outside of range [0, %u), got %u", sysir->instruction_count, instruction.jump.to);
                }
            case JANET_SYSOP_LABEL:
                break;
            case JANET_SYSOP_RETURN: {
                uint32_t ret_type = sysir->types[instruction.one.src];


@@ 1279,10 1303,8 @@ static void janet_sysir_type_check(JanetSysIR *sysir) {
                tcheck_pointer(sysir, sysir->types[instruction.two.dest]);
                break;
            case JANET_SYSOP_BRANCH:
            case JANET_SYSOP_BRANCH_NOT:
                tcheck_boolean(sysir, sysir->types[instruction.branch.cond]);
                if (instruction.branch.to >= sysir->instruction_count) {
                    janet_panicf("label outside of range [0, %u), got %u", sysir->instruction_count, instruction.branch.to);
                }
                break;
            case JANET_SYSOP_CONSTANT:
                tcheck_constant(sysir, instruction.constant.dest, sysir->constants[instruction.constant.constant]);


@@ 1388,7 1410,7 @@ static void emit_binop(JanetSysIR *ir, JanetBuffer *buffer, JanetBuffer *tempbuf

    /* Add nested for loops for any dimensionality of array */
    while (linkage->type_defs[operand_type].prim == JANET_PRIM_ARRAY) {
        janet_formatb(buffer, "for (size_t _j%u = 0; _j%u < %u; _j%u++) ",
        janet_formatb(buffer, "  for (size_t _j%u = 0; _j%u < %u; _j%u++) ",
                      index_index, index_index,
                      linkage->type_defs[operand_type].array.fixed_count,
                      index_index);


@@ 1403,14 1425,14 @@ static void emit_binop(JanetSysIR *ir, JanetBuffer *buffer, JanetBuffer *tempbuf
    }

    if (is_pointer) {
        janet_formatb(buffer, "*_r%u = *_r%u %s *_r%u;\n",
        janet_formatb(buffer, "  *_r%u = *_r%u %s *_r%u;\n",
                      instruction.three.dest,
                      instruction.three.lhs,
                      op,
                      instruction.three.rhs);
    } else {
        Janet index_part = janet_wrap_buffer(tempbuf);
        janet_formatb(buffer, "_r%u%V = _r%u%V %s _r%u%V;\n",
        janet_formatb(buffer, "  _r%u%V = _r%u%V %s _r%u%V;\n",
                      instruction.three.dest, index_part,
                      instruction.three.lhs, index_part,
                      op,


@@ 1492,23 1514,9 @@ void janet_sys_ir_lower_to_c(JanetSysIRLinkage *linkage, JanetBuffer *buffer) {
        /* Emit body */
        for (uint32_t i = 0; i < ir->instruction_count; i++) {
            JanetSysInstruction instruction = ir->instructions[i];
            /* Skip instruction label for some opcodes */
            switch (instruction.opcode) {
                case JANET_SYSOP_TYPE_PRIMITIVE:
                case JANET_SYSOP_TYPE_BIND:
                case JANET_SYSOP_TYPE_STRUCT:
                case JANET_SYSOP_TYPE_UNION:
                case JANET_SYSOP_TYPE_POINTER:
                case JANET_SYSOP_TYPE_ARRAY:
                    continue;
                default:
                    break;
            }
            janet_formatb(buffer, "_i%u:\n", i);
            if (instruction.line > 0) {
                janet_formatb(buffer, "#line %d\n  ", instruction.line);
            }
            janet_buffer_push_cstring(buffer, "  ");
            switch (instruction.opcode) {
                case JANET_SYSOP_TYPE_PRIMITIVE:
                case JANET_SYSOP_TYPE_BIND:


@@ 1520,22 1528,29 @@ void janet_sys_ir_lower_to_c(JanetSysIRLinkage *linkage, JanetBuffer *buffer) {
                case JANET_SYSOP_LINK_NAME:
                case JANET_SYSOP_PARAMETER_COUNT:
                    break;
                case JANET_SYSOP_LABEL: {
                    janet_formatb(buffer, "\n_label_%u:\n", instruction.label.id);
                    break;
                }
                case JANET_SYSOP_CONSTANT: {
                    uint32_t cast = ir->types[instruction.two.dest];
                    janet_formatb(buffer, "_r%u = (_t%u) %j;\n", instruction.two.dest, cast, ir->constants[instruction.two.src]);
                    janet_formatb(buffer, "  _r%u = (_t%u) %j;\n", instruction.two.dest, cast, ir->constants[instruction.two.src]);
                    break;
                }
                case JANET_SYSOP_ADDRESS:
                    janet_formatb(buffer, "_r%u = (char *) &_r%u;\n", instruction.two.dest, instruction.two.src);
                    janet_formatb(buffer, "  _r%u = (char *) &_r%u;\n", instruction.two.dest, instruction.two.src);
                    break;
                case JANET_SYSOP_JUMP:
                    janet_formatb(buffer, "goto _i%u;\n", instruction.jump.to);
                    janet_formatb(buffer, "  goto _label_%u;\n", instruction.jump.to);
                    break;
                case JANET_SYSOP_BRANCH:
                    janet_formatb(buffer, "if (_r%u) goto _i%u;\n", instruction.branch.cond, instruction.branch.to);
                    janet_formatb(buffer, "  if (_r%u) goto _label_%u;\n", instruction.branch.cond, instruction.branch.to);
                    break;
                case JANET_SYSOP_BRANCH_NOT:
                    janet_formatb(buffer, "  if (!_r%u) goto _label_%u;\n", instruction.branch.cond, instruction.branch.to);
                    break;
                case JANET_SYSOP_RETURN:
                    janet_formatb(buffer, "return _r%u;\n", instruction.one.src);
                    janet_formatb(buffer, "  return _r%u;\n", instruction.one.src);
                    break;
                case JANET_SYSOP_ADD:
                case JANET_SYSOP_POINTER_ADD:


@@ 1584,51 1599,60 @@ void janet_sys_ir_lower_to_c(JanetSysIRLinkage *linkage, JanetBuffer *buffer) {
                case JANET_SYSOP_SHR:
                    EMITBINOP(">>");
                    break;
                case JANET_SYSOP_CALL:
                    janet_formatb(buffer, "_r%u = _r%u(", instruction.call.dest, instruction.call.callee);
                case JANET_SYSOP_CALL: {
                    if (instruction.call.has_dest) {
                        janet_formatb(buffer, "  _r%u = _r%u(", instruction.call.dest, instruction.call.callee);
                    } else {
                        janet_formatb(buffer, "  _r%u(", instruction.call.callee);
                    }
                    for (uint32_t j = 0; j < instruction.call.arg_count; j++) {
                        uint32_t offset = j / 3 + 1;
                        uint32_t index = j % 3;
                        JanetSysInstruction arg_instruction = ir->instructions[i + offset];
                        janet_formatb(buffer, j ? ", _r%u" : "_r%u", arg_instruction.arg.args[index]);
                    }
                    janet_formatb(buffer, ");\n");
                    janet_formatb(buffer, "); // CALL\n");
                    break;
                }
                case JANET_SYSOP_CALLK:
                    janet_formatb(buffer, "_r%u = %j(", instruction.callk.dest, ir->constants[instruction.callk.constant]);
                    if (instruction.callk.has_dest) {
                        janet_formatb(buffer, "  _r%u = %v(", instruction.callk.dest, ir->constants[instruction.callk.constant]);
                    } else {
                        janet_formatb(buffer, "  %v(", ir->constants[instruction.callk.constant]);
                    }
                    for (uint32_t j = 0; j < instruction.callk.arg_count; j++) {
                        uint32_t offset = j / 3 + 1;
                        uint32_t index = j % 3;
                        JanetSysInstruction arg_instruction = ir->instructions[i + offset];
                        janet_formatb(buffer, j ? ", _r%u" : "_r%u", arg_instruction.arg.args[index]);
                    }
                    janet_formatb(buffer, ");\n");
                    janet_formatb(buffer, "); // CALLK\n");
                    break;
                case JANET_SYSOP_CAST: {
                    uint32_t to = ir->types[instruction.two.dest];
                    janet_formatb(buffer, "_r%u = (_t%u) (_r%u);\n", instruction.two.dest, to, instruction.two.src);
                    janet_formatb(buffer, "  _r%u = (_t%u) (_r%u);\n", instruction.two.dest, to, instruction.two.src);
                    break;
                }
                case JANET_SYSOP_MOVE:
                    janet_formatb(buffer, "_r%u = _r%u;\n", instruction.two.dest, instruction.two.src);
                    janet_formatb(buffer, "  _r%u = _r%u;\n", instruction.two.dest, instruction.two.src);
                    break;
                case JANET_SYSOP_BNOT:
                    janet_formatb(buffer, "_r%u = ~_r%u;\n", instruction.two.dest, instruction.two.src);
                    janet_formatb(buffer, "  _r%u = ~_r%u;\n", instruction.two.dest, instruction.two.src);
                    break;
                case JANET_SYSOP_LOAD:
                    janet_formatb(buffer, "_r%u = *(_r%u);\n", instruction.two.dest, instruction.two.src);
                    janet_formatb(buffer, "  _r%u = *(_r%u);\n", instruction.two.dest, instruction.two.src);
                    break;
                case JANET_SYSOP_STORE:
                    janet_formatb(buffer, "*(_r%u) = _r%u;\n", instruction.two.dest, instruction.two.src);
                    janet_formatb(buffer, "  *(_r%u) = _r%u;\n", instruction.two.dest, instruction.two.src);
                    break;
                case JANET_SYSOP_FIELD_GETP:
                    janet_formatb(buffer, "_r%u = &(_r%u._f%u);\n", instruction.field.r, instruction.field.st, instruction.field.field);
                    janet_formatb(buffer, "  _r%u = &(_r%u._f%u);\n", instruction.field.r, instruction.field.st, instruction.field.field);
                    break;
                case JANET_SYSOP_ARRAY_GETP:
                    janet_formatb(buffer, "_r%u = &(_r%u.els[_r%u]);\n", instruction.three.dest, instruction.three.lhs, instruction.three.rhs);
                    janet_formatb(buffer, "  _r%u = &(_r%u.els[_r%u]);\n", instruction.three.dest, instruction.three.lhs, instruction.three.rhs);
                    break;
                case JANET_SYSOP_ARRAY_PGETP:
                    janet_formatb(buffer, "_r%u = &(_r%u->els[_r%u]);\n", instruction.three.dest, instruction.three.lhs, instruction.three.rhs);
                    janet_formatb(buffer, "  _r%u = &(_r%u->els[_r%u]);\n", instruction.three.dest, instruction.three.lhs, instruction.three.rhs);
                    break;
            }
        }