#include #include #include #include "ast.h" #include "gen.h" #include "gen/data.h" #include "gen/emit.h" #include "io.h" #include "limits.h" #include "module.h" #include "options.h" #include "ralloc.h" #include "riscv.h" #include "resolver.h" #include "symtab.h" #include "types.h" /* Shorthand: get node pointer array from span in current module's parser. */ #define SPAN(g, s) nodespan_ptrs(&(g)->mod->parser, (s)) static void gen_assign(gen_t *g, node_t *n); static void gen_return(gen_t *g, node_t *n); static void gen_fn(gen_t *g, node_t *n); static value_t gen_array_index(gen_t *g, node_t *n, bool ref); static value_t gen_array_slice(gen_t *g, value_t array_val, node_t *range); static value_t gen_array_literal(gen_t *g, node_t *n); static value_t gen_array_repeat(gen_t *g, node_t *n); static value_t gen_expr(gen_t *g, node_t *n, bool lvalue); static void gen_expr_stmt(gen_t *g, node_t *n); static void gen_match(gen_t *g, node_t *n); static void gen_block(gen_t *g, node_t *n); static void gen_if(gen_t *g, node_t *n); static void gen_if_let(gen_t *g, node_t *n); static value_t gen_if_expr(gen_t *g, node_t *n); static void gen_loop(gen_t *g, node_t *n); static void gen_break(gen_t *g, node_t *n); static void gen_var(gen_t *g, node_t *n); static void gen_const(gen_t *g, node_t *n); static void gen_static(gen_t *g, node_t *n); static void gen_nop(gen_t *g, node_t *n); static value_t gen_deref(gen_t *g, node_t *n, value_t ref_val, bool lval); static void gen_ecall(gen_t *g, node_t *n); static void gen_ebreak(gen_t *g, node_t *n); static void gen_panic(gen_t *g, node_t *n); static void gen_mod(gen_t *g, node_t *n); static void gen_use(gen_t *g, node_t *n); static value_t gen_as_cast(gen_t *g, node_t *n); static value_t gen_union_constructor(gen_t *g, node_t *n); static value_t gen_record_lit(gen_t *g, node_t *n); static void gen_throw(gen_t *g, node_t *n); static value_t gen_try(gen_t *g, node_t *n); static value_t gen_union_store( gen_t *g, type_t *union_type, symbol_t *variant_sym, value_t payload ); static void useval(gen_t *g, value_t val); static void freeval(gen_t *g, value_t val); static value_t value_none(void); i32 tval_payload_offset(type_t *container); /* Convert a value into a tagged value by calculating its offsets. */ tval_t tval_from_val(gen_t *g, value_t val) { /* For unions with payloads, we don't know the value type in advance. */ type_t *val_typ = NULL; if (val.type->cls == TYPE_OPT) { val_typ = val.type->info.opt.elem; } else if (val.type->cls == TYPE_RESULT) { val_typ = val.type->info.res.payload; } i32 val_off = tval_payload_offset(val.type); tval_t tval = { 0 }; tval.tag = (value_t){ .type = g->types->type_u8, .loc = val.loc, .as = val.as }; tval.typ = val.type; tval.val = (value_t){ .type = val_typ, .loc = val.loc, .as = val.as, }; if (val.loc == LOC_STACK) { tval.val.as.off.offset = val.as.off.offset + val_off; } else if (val.loc == LOC_ADDR) { tval.val.as.adr.offset = val.as.adr.offset + val_off; } else if (val.loc == LOC_REG) { /* Register contains the address of the optional in memory */ tval.tag.loc = LOC_STACK; tval.tag.as.off.base = val.as.reg; tval.tag.as.off.offset = 0; tval.val.loc = LOC_STACK; tval.val.as.off.base = val.as.reg; tval.val.as.off.offset = val_off; } else { bail("cannot load tagged value from location %d", val.loc); } return tval; } /* Return the byte offset of the payload within a tagged value. */ i32 tval_payload_offset(type_t *container) { return container->size > TAG_SIZE ? align(TAG_SIZE, container->align) : TAG_SIZE; } /* Return the number of payload bytes to zero before writing a new value. */ i32 tval_payload_zero_size(type_t *container) { switch (container->cls) { case TYPE_OPT: return container->size - tval_payload_offset(container); case TYPE_UNION: return container->size - tval_payload_offset(container); default: return 0; } } void tval_store(gen_t *g, value_t dest, value_t value, i32 tag) { /* Optional values treat tag 0 as nil; everything else always stores a * payload area. */ bool nil = (dest.type->cls == TYPE_OPT && tag == 0); /* Compute base/offset for tag and payload. For addresses, materialize a * temporary base register so regstore/memzero can operate safely. */ reg_t base = ZERO; i32 tag_off = 0; bool base_temp = false; switch (dest.loc) { case LOC_STACK: base = dest.as.off.base; tag_off = dest.as.off.offset; break; case LOC_ADDR: base = nextreg(g); base_temp = true; emit_li(g, base, dest.as.adr.base); tag_off = dest.as.adr.offset; break; case LOC_REG: { /* Register holds the address; copy into a reserved temp. */ base = nextreg(g); base_temp = true; emit_mv(g, base, dest.as.reg); tag_off = 0; break; } default: bail("cannot store tagged value at location %d", dest.loc); } i32 payload_off = tag_off + tval_payload_offset(dest.type); /* Store tag (1 byte) */ reg_t rd = nextreg(g); emit_li(g, rd, tag); emit_regstore(g, rd, base, tag_off, g->types->type_u8); freereg(g, rd); /* Zero padding between tag byte and payload start so that byte-level * equality comparisons of tagged values work correctly. */ i32 pad_off = tag_off + TAG_SIZE; i32 pad_size = payload_off - pad_off; if (pad_size > 0) { emit_memzero(g, OFFSET(base, pad_off), pad_size); } /* Clear payload region before writing a new value (or when nil). */ i32 payload_size = tval_payload_zero_size(dest.type); emit_memzero(g, OFFSET(base, payload_off), payload_size); if (!nil && value.type && value.type->cls != TYPE_VOID) { emit_store(g, value, base, payload_off); } if (base_temp) freereg(g, base); } /* Helper function to create an optional value from a primitive immediate. */ static value_t optval_from_prim(gen_t *g, type_t *opt_type, value_t prim_val) { i32 offset = reserve(g, opt_type); value_t opt_val = value_stack(OFFSET(FP, offset), opt_type); tval_store(g, opt_val, prim_val, 1); return opt_val; } static value_t optval_from_value(gen_t *g, type_t *opt_type, value_t value) { if (value.type == opt_type) return value; i32 offset = reserve(g, opt_type); value_t opt_val = value_stack(OFFSET(FP, offset), opt_type); tval_store(g, opt_val, value, 1); return opt_val; } /* Load the tag of tagged value into a register. */ static reg_t tval_load_tag(gen_t *g, value_t opt_val) { tval_t opt = tval_from_val(g, opt_val); return emit_load(g, opt.tag); } /* Helper to bind a union variant payload to a variable. */ static void bind_union_value(gen_t *g, value_t union_src, node_t *bound_var) { symbol_t *var_sym = bound_var->sym; /* Allocate storage for the bound variable if not already allocated */ if (var_sym->e.var.val.loc == LOC_NONE) { i32 off = reserve_aligned(g, var_sym->e.var.typ, var_sym->e.var.align); var_sym->e.var.val = value_stack(OFFSET(FP, off), var_sym->e.var.typ); } /* Create a value pointing to the value part of the union (after the tag) */ type_t *union_type = union_src.type; if (union_type->cls == TYPE_PTR) union_type = union_type->info.ptr.target; i32 val_off = tval_payload_offset(union_type); value_t union_val_part = union_src; union_val_part.type = bound_var->type; if (union_src.loc == LOC_STACK) { union_val_part.as.off.offset += val_off; } else if (union_src.loc == LOC_ADDR) { union_val_part.as.adr.offset += val_off; } else { bail("cannot bind union value from this location"); } /* Copy the union payload to the bound variable */ emit_replace(g, var_sym->e.var.val, union_val_part); } /* Copy the value part of an optional to a destination */ static void optval_copy_value(gen_t *g, value_t opt_src, value_t value_dest) { tval_t opt = tval_from_val(g, opt_src); emit_replace(g, value_dest, opt.val); } /* Generate a union constructor call like Expr::number(42) */ static value_t gen_union_constructor(gen_t *g, node_t *call) { type_t *variant_type = call->sym->node->type; value_t payload = value_none(); if (variant_type->cls != TYPE_VOID) { node_t *arg_node = SPAN(g, call->val.call.args)[0]; node_t *arg_expr = arg_node->val.call_arg.expr; payload = gen_expr(g, arg_expr, false); } return gen_union_store(g, call->type, call->sym, payload); } static value_t gen_union_store( gen_t *g, type_t *union_type, symbol_t *variant_sym, value_t payload ) { i32 tag = variant_sym->node->val.union_variant.value; /* Allocate space for the union on the stack */ i32 offset = reserve(g, union_type); value_t union_val = value_stack(OFFSET(FP, offset), union_type); /* Store the union value */ useval(g, payload); tval_store(g, union_val, payload, tag); freeval(g, payload); return union_val; } /* Node type to generator function mapping. */ static void (*GENERATORS[])(gen_t *, node_t *) = { [NODE_TYPE] = NULL, [NODE_NUMBER] = NULL, [NODE_BOOL] = NULL, [NODE_STRING] = NULL, [NODE_CHAR] = NULL, [NODE_IDENT] = NULL, [NODE_BINOP] = NULL, [NODE_BLOCK] = gen_block, [NODE_CALL] = NULL, [NODE_CALL_ARG] = NULL, [NODE_VAR] = gen_var, [NODE_CONST] = gen_const, [NODE_STATIC] = gen_static, [NODE_ASSIGN] = gen_assign, [NODE_RETURN] = gen_return, [NODE_THROW] = gen_throw, [NODE_PANIC] = gen_panic, [NODE_WHILE] = NULL, [NODE_WHILE_LET] = NULL, [NODE_FOR] = NULL, [NODE_LOOP] = gen_loop, [NODE_IF] = gen_if, [NODE_IF_LET] = gen_if_let, [NODE_IF_CASE] = NULL, [NODE_GUARD_CASE] = NULL, [NODE_GUARD_LET] = NULL, [NODE_MATCH] = gen_match, [NODE_MATCH_CASE] = gen_nop, /* Cases are handled by gen_match */ [NODE_FN] = gen_fn, [NODE_BREAK] = gen_break, [NODE_RECORD] = gen_nop, [NODE_UNION] = gen_nop, [NODE_EXPR_STMT] = gen_expr_stmt, [NODE_MOD] = gen_mod, [NODE_USE] = gen_use, }; /* Built-in functions */ static const struct { const char *name; usize length; void (*gen)(gen_t *, node_t *); } BUILTINS[] = { { "std::intrinsics::ecall", 22, gen_ecall }, { "std::intrinsics::ebreak", 23, gen_ebreak }, { NULL, 0, NULL }, }; /******************************************************************************/ value_t value_addr(usize addr, i32 off, type_t *ty) { return (value_t){ .type = ty, .loc = LOC_ADDR, .as.adr.base = addr, .as.adr.offset = off, }; } value_t value_stack(offset_t off, type_t *ty) { return (value_t){ .type = ty, .loc = LOC_STACK, .as.off.base = off.base, .as.off.offset = off.offset, }; } value_t value_reg(reg_t r, type_t *ty) { return (value_t){ .temp = true, .type = ty, .loc = LOC_REG, .as.reg = r, }; } value_t value_imm(imm_t imm, type_t *ty) { return (value_t){ .type = ty, .loc = LOC_IMM, .as.imm = imm, }; } static value_t value_none(void) { return (value_t){ .type = NULL, .loc = LOC_NONE, }; } i32 align_stack(i32 addr, i32 alignment) { /* Verify alignment is a power of 2. */ /* For negative addresses (stack growth downward), * we round down to the next multiple of alignment. */ return addr & ~(alignment - 1); } i32 jump_offset(usize from, usize to) { return ((i32)to - (i32)from) * INSTR_SIZE; } /* Provide a sentinel patch so callers can keep a uniform interface. */ static branch_patch_t branch_patch_invalid(void) { return (branch_patch_t){ .pc = (usize)-1, .tramp_pc = (usize)-1, .op = I_BEQ, .rs1 = ZERO, .rs2 = ZERO, .valid = false, }; } /* Reserve space for the branch and a fallback trampoline in one call. */ static branch_patch_t branch_patch_make( gen_t *g, iname_t op, reg_t rs1, reg_t rs2 ) { branch_patch_t patch = { .pc = emit(g, NOP), .tramp_pc = emit(g, NOP), .op = op, .rs1 = rs1, .rs2 = rs2, .valid = true, }; return patch; } /* Flip a branch opcode so the trampoline executes on the opposite outcome. */ static iname_t branch_op_inverse(iname_t op) { switch (op) { case I_BEQ: return I_BNE; case I_BNE: return I_BEQ; case I_BLT: return I_BGE; case I_BGE: return I_BLT; case I_BLTU: return I_BGEU; case I_BGEU: return I_BLTU; default: return 0; } } /* Finalize the branch, rewriting to a long-range form when necessary. */ static void branch_patch_apply(gen_t *g, branch_patch_t patch, usize target) { if (!patch.valid) return; i32 imm = jump_offset(patch.pc, target); if (is_branch_imm(imm)) { g->instrs[patch.pc] = instr(patch.op, ZERO, patch.rs1, patch.rs2, imm); g->instrs[patch.tramp_pc] = NOP; return; } usize fallthrough = patch.tramp_pc + 1; i32 skip_imm = jump_offset(patch.pc, fallthrough); iname_t inv = branch_op_inverse(patch.op); g->instrs[patch.pc] = instr(inv, ZERO, patch.rs1, patch.rs2, skip_imm); i32 jmp_imm = jump_offset(patch.tramp_pc, target); g->instrs[patch.tramp_pc] = JMP(jmp_imm); } i32 reserve(gen_t *g, type_t *ty) { return reserve_aligned(g, ty, ty->align); } static void useval(gen_t *g, value_t val) { if (val.loc == LOC_REG) { usereg(g, val.as.reg); } else if (val.loc == LOC_STACK) { usereg(g, val.as.off.base); } } static void freeval(gen_t *g, value_t val) { if (val.loc == LOC_REG && val.temp) { freereg(g, val.as.reg); } } /******************************************************************************/ /* Patch all break statements for a loop. */ static void patch_break_stmts(gen_t *g) { for (usize i = 0; i < g->fn.nbrkpatches; i++) { ctpatch_t *p = &g->fn.brkpatches[i]; if (!p->applied && p->loop == g->loop.current) { /* Calculate jump offset to the loop end, and apply patch. */ i32 offset = jump_offset(p->pc, g->loop.end); g->instrs[p->pc] = JAL(ZERO, offset); p->applied = true; } } } /******************************************************************************/ /* Generate code for a node. */ static void gen_node(gen_t *g, node_t *n) { if (!n) return; if (!GENERATORS[n->cls]) bail("unsupported node type '%s'", node_names[n->cls]); /* Restore register allocation state between statements to avoid leaking */ bool regs[RALLOC_NREGS] = { false }; ralloc_save(&g->regs, regs); GENERATORS[n->cls](g, n); ralloc_restore(&g->regs, regs); } /* System call (ecall): Takes four arguments (A0, A1, A2, A3) */ static void gen_ecall(gen_t *g, node_t *n) { node_t **cargs = SPAN(g, n->val.call.args); node_t *num = cargs[0]; node_t *arg0 = cargs[1]; node_t *arg1 = cargs[2]; node_t *arg2 = cargs[3]; node_t *arg3 = cargs[4]; value_t numval = gen_expr(g, num->val.call_arg.expr, false); value_t arg0val = gen_expr(g, arg0->val.call_arg.expr, false); value_t arg1val = gen_expr(g, arg1->val.call_arg.expr, false); value_t arg2val = gen_expr(g, arg2->val.call_arg.expr, false); value_t arg3val = gen_expr(g, arg3->val.call_arg.expr, false); /* Move the arguments to the appropriate registers. Load higher-numbered * argument registers first so we don't overwrite values that are still * needed for lower-numbered arguments (e.g. when the source value lives in * A0). */ usereg(g, A7); emit_load_into(g, A7, numval); /* Syscall number is stored in A7 */ usereg(g, A3); emit_load_into(g, A3, arg3val); usereg(g, A2); emit_load_into(g, A2, arg2val); usereg(g, A1); emit_load_into(g, A1, arg1val); usereg(g, A0); emit_load_into(g, A0, arg0val); emit(g, ECALL); freereg(g, A3); freereg(g, A2); freereg(g, A1); freereg(g, A7); } /* Emit an EBREAK instruction */ static void gen_ebreak(gen_t *g, node_t *n) { (void)n; emit(g, EBREAK); } /* Generate panic statement */ static void gen_panic(gen_t *g, node_t *n) { (void)n; emit(g, EBREAK); } static void gen_expr_stmt(gen_t *g, node_t *n) { /* Generate the expression as a statement; result will be discarded. */ value_t result = gen_expr(g, n->val.expr_stmt, false); /* For non-void expressions, we free any allocated registers */ if (result.loc == LOC_REG) { freereg(g, result.as.reg); } } /* Generate conditional branch code. */ static void gen_branch(gen_t *g, node_t *cond, node_t *lbranch) { binop_t op = cond->val.binop.op; value_t lval = gen_expr(g, cond->val.binop.left, false); value_t rval = gen_expr(g, cond->val.binop.right, false); reg_t left = emit_load(g, lval); reg_t right = emit_load(g, rval); iname_t branch_op = I_BEQ; reg_t rs1 = left; reg_t rs2 = right; bool is_unsigned = false; if (cond->val.binop.left->type) { is_unsigned = type_is_unsigned(cond->val.binop.left->type->cls); } /* Select the appropriate branch instruction based on the comparison * operator. Nb. we're branching if the condition is *false*, so we use the * opposite branch instruction. */ switch (op) { case OP_EQ: branch_op = I_BNE; break; case OP_LT: branch_op = is_unsigned ? I_BGEU : I_BGE; break; case OP_GT: branch_op = is_unsigned ? I_BGEU : I_BGE; rs1 = right; rs2 = left; break; case OP_LE: branch_op = is_unsigned ? I_BLTU : I_BLT; rs1 = right; rs2 = left; break; case OP_GE: branch_op = is_unsigned ? I_BLTU : I_BLT; break; case OP_NE: /* For not equals, branch if they are equal. */ branch_op = I_BEQ; break; case OP_AND: case OP_OR: case OP_ADD: case OP_SUB: case OP_DIV: case OP_MUL: case OP_MOD: case OP_BAND: case OP_BOR: case OP_XOR: case OP_SHL: case OP_SHR: abort(); } branch_patch_t patch = branch_patch_make(g, branch_op, rs1, rs2); freereg(g, left); freereg(g, right); /* Generate code for the left (true) branch. */ gen_block(g, lbranch); /* Patch the branch to jump past the left branch when false. */ branch_patch_apply(g, patch, g->ninstrs); } /* Generate code for an if/else condition with arbitrary condition and branches. * This function is used both for regular if statements and for match cases. */ static void gen_if_else( gen_t *g, value_t condition_val, /* Condition value to test */ node_t *lbranch, /* Code to execute if condition is true */ node_t *rbranch /* Code to execute if condition is false */ ) { /* Load the condition value into a register */ reg_t condreg = emit_load(g, condition_val); /* Emit a conditional branch: if condition is zero (false), * jump past the left branch. */ branch_patch_t lb_branch = branch_patch_make(g, I_BEQ, condreg, ZERO); /* Nb. we free this register here even though the register _name_ is used * lower, because it's only used for patching the instruction above. */ freereg(g, condreg); /* Generate code for the true branch. */ gen_block(g, lbranch); if (rbranch) { /* If we have an else branch, emit jump to skip over it. */ const usize lb_end = emit(g, NOP); const usize rb_start = g->ninstrs; /* Patch the branch instruction to jump to else. */ branch_patch_apply(g, lb_branch, rb_start); /* Generate code for the false branch. */ gen_block(g, rbranch); /* Patch the jump past else. */ const usize rb_end = g->ninstrs; g->instrs[lb_end] = JMP(jump_offset(lb_end, rb_end)); } else { /* No false branch, just patch the conditional branch to jump to the * end. */ const usize end = g->ninstrs; branch_patch_apply(g, lb_branch, end); } } /* Generate guard check for a match case. Updates ctrl->guard_branch if guard * present. */ static void gen_case_guard(gen_t *g, node_t *n, match_case_ctrl_t *ctrl) { if (n->val.match_case.guard) { value_t guard_val = gen_expr(g, n->val.match_case.guard, false); reg_t guard_reg = emit_load(g, guard_val); ctrl->guard_branch = branch_patch_make(g, I_BEQ, guard_reg, ZERO); freereg(g, guard_reg); } } /* Bind a pattern variable to a record field value. Allocates stack space for * the variable if needed and copies the field value into it. * For ref matches (variable type is pointer to field type), stores the address * of the field instead of copying its value. */ static void bind_var_to_field( gen_t *g, value_t record_val, symbol_t *field_sym, symbol_t *var_sym ) { if (var_sym->e.var.val.loc == LOC_NONE) { i32 off = reserve_aligned(g, var_sym->e.var.typ, var_sym->e.var.align); var_sym->e.var.val = value_stack(OFFSET(FP, off), var_sym->e.var.typ); } value_t field_val = record_val; field_val.type = field_sym->e.field.typ; if (field_val.loc == LOC_STACK) field_val.as.off.offset += field_sym->e.field.offset; else if (field_val.loc == LOC_ADDR) field_val.as.adr.offset += field_sym->e.field.offset; else if (field_val.loc == LOC_REG) { /* Register holds the address of the record. Convert to LOC_STACK * so that the field offset is applied when loading. */ reg_t base_reg = field_val.as.reg; field_val.loc = LOC_STACK; field_val.as.off.base = base_reg; field_val.as.off.offset = field_sym->e.field.offset; } /* Check if this is a ref match (variable is pointer to field type) */ type_t *var_typ = var_sym->e.var.typ; if (var_typ->cls == TYPE_PTR && var_typ->info.ptr.target == field_sym->e.field.typ) { /* Store address of field instead of copying value */ reg_t addr_reg = nextreg(g); if (field_val.loc == LOC_STACK) { emit_addr_offset( g, addr_reg, field_val.as.off.base, field_val.as.off.offset ); } else if (field_val.loc == LOC_ADDR) { emit_li( g, addr_reg, field_val.as.adr.base + field_val.as.adr.offset ); } else if (field_val.loc == LOC_REG) { emit( g, ADDI(addr_reg, field_val.as.reg, field_sym->e.field.offset) ); } else { bail("cannot take address of field for ref match"); } /* Store the address register into the variable's stack location */ emit_regstore( g, addr_reg, var_sym->e.var.val.as.off.base, var_sym->e.var.val.as.off.offset, var_sym->e.var.typ ); freereg(g, addr_reg); } else { emit_replace(g, var_sym->e.var.val, field_val); } } /* Bind fields from a record value to pattern variables. Handles both * tuple-style patterns like `S(x, y)` and labeled patterns like `T { x, y }`. */ static void gen_bind_record_fields( gen_t *g, value_t record_val, node_t *pattern, type_t *record_type ) { if (pattern->cls == NODE_CALL) { for (usize i = 0; i < pattern->val.call.args.len; i++) { node_t *arg_node = SPAN(g, pattern->val.call.args)[i]; node_t *arg = (arg_node->cls == NODE_CALL_ARG) ? arg_node->val.call_arg.expr : arg_node; if (arg->cls == NODE_IDENT && arg->sym) { bind_var_to_field( g, record_val, record_type->info.srt.fields[i], arg->sym ); } } } else if (pattern->cls == NODE_RECORD_LIT) { node_t **fields = nodespan_ptrs(&g->mod->parser, pattern->val.record_lit.fields); for (usize i = 0; i < pattern->val.record_lit.fields.len; i++) { node_t *binding = fields[i]->val.record_lit_field.value; if (binding->cls == NODE_IDENT && binding->sym) { bind_var_to_field(g, record_val, fields[i]->sym, binding->sym); } } } } static match_case_ctrl_t gen_match_case_union_payload( gen_t *g, value_t match_val, node_t *n ) { match_case_ctrl_t ctrl = { .skip_body = 0, .guard_branch = branch_patch_invalid(), }; /* Array to store jumps to body when a pattern matches */ branch_patch_t jumps[MAX_CASE_PATTERNS]; usize njumps = 0; /* union pattern matching - generate tag comparisons */ node_t **patterns = nodespan_ptrs(&g->mod->parser, n->val.match_case.patterns); for (usize p = 0; p < n->val.match_case.patterns.len; p++) { node_t *patt_node = patterns[p]; node_t *callee = NULL; if (patt_node->cls == NODE_CALL) { callee = patt_node->val.call.callee; } else if (patt_node->cls == NODE_RECORD_LIT) { callee = patt_node->val.record_lit.type; } else { callee = patt_node; } /* Use the stored variant index */ node_t *variant_ident = callee->val.access.rval; usize variant_tag = variant_ident->sym->node->val.union_variant.value; /* Generate tag comparison. * For ref matching (pointer-to-union), the register holds an address * we need to load from. */ reg_t tag_reg; if (match_val.loc == LOC_REG && match_val.type->cls == TYPE_PTR) { /* Load tag byte from address in register */ tag_reg = nextreg(g); emit(g, LBU(tag_reg, match_val.as.reg, 0)); } else { value_t tag_val = match_val; tag_val.type = g->types->type_u8; tag_reg = emit_load(g, tag_val); } reg_t variant_idx_reg = nextreg(g); emit(g, ADDI(variant_idx_reg, ZERO, variant_tag)); jumps[njumps++] = branch_patch_make(g, I_BEQ, tag_reg, variant_idx_reg); freereg(g, variant_idx_reg); freereg(g, tag_reg); } /* If none of the patterns match, jump past the body */ ctrl.skip_body = emit(g, NOP); /* Will be patched later */ usize body_start = g->ninstrs; /* Body starts here */ /* Patch all the pattern match jumps to point to the body start */ for (usize p = 0; p < njumps; p++) { branch_patch_apply(g, jumps[p], body_start); } /* Set up bound variable for payload binding */ if (n->val.match_case.variable) { /* If variable doesn't have a symbol, it's likely a placeholder, * eg. `_`, so we don't bind anything. */ if (n->val.match_case.variable->sym) { bind_union_value(g, match_val, n->val.match_case.variable); } } /* Handle record literal pattern field bindings */ if (n->val.match_case.patterns.len == 1) { node_t *patt = patterns[0]; if (patt->cls == NODE_RECORD_LIT) { node_t *callee = patt->val.record_lit.type; node_t *variant_node = callee->val.access.rval; type_t *payload_type = variant_node->sym->node->type; /* Create a value pointing to the payload (after tag). * When matching on a reference, match_val.type is a pointer; * dereference to get the underlying union type. */ type_t *union_type = match_val.type; if (union_type->cls == TYPE_PTR) union_type = union_type->info.ptr.target; i32 val_off = tval_payload_offset(union_type); value_t payload = match_val; if (payload.loc == LOC_STACK) { payload.as.off.offset += val_off; } else if (payload.loc == LOC_ADDR) { payload.as.adr.offset += val_off; } else if (payload.loc == LOC_REG) { /* Register contains union address; add offset to get payload */ reg_t payload_reg = nextreg(g); emit(g, ADDI(payload_reg, payload.as.reg, val_off)); payload = value_reg(payload_reg, payload_type); } payload.type = payload_type; gen_bind_record_fields(g, payload, patt, payload_type); } } gen_case_guard(g, n, &ctrl); return ctrl; } /* Generate code for a match case with a standalone record pattern. * Record patterns always match (no tag comparison), so we just bind fields. */ static match_case_ctrl_t gen_match_case_record( gen_t *g, value_t match_val, node_t *n ) { match_case_ctrl_t ctrl = { 0, branch_patch_invalid() }; node_t **patterns = nodespan_ptrs(&g->mod->parser, n->val.match_case.patterns); if (n->val.match_case.patterns.len >= 1) gen_bind_record_fields(g, match_val, patterns[0], match_val.type); gen_case_guard(g, n, &ctrl); return ctrl; } static match_case_ctrl_t gen_match_case(gen_t *g, reg_t match_reg, node_t *n) { match_case_ctrl_t ctrl = { .skip_body = 0, .guard_branch = branch_patch_invalid(), }; /* Array to store jumps to body when a pattern matches */ branch_patch_t jumps[MAX_CASE_PATTERNS]; usize njumps = 0; /* Regular pattern matching (non-payload types) */ node_t **patterns = nodespan_ptrs(&g->mod->parser, n->val.match_case.patterns); for (usize p = 0; p < n->val.match_case.patterns.len; p++) { node_t *patt_node = patterns[p]; value_t patt_val = gen_expr(g, patt_node, false); reg_t patt_reg = emit_load(g, patt_val); /* If this pattern matches, jump to the body * (Will be patched later) */ jumps[njumps++] = branch_patch_make(g, I_BEQ, match_reg, patt_reg); freereg(g, patt_reg); } /* If none of the patterns match, jump past the body */ ctrl.skip_body = emit(g, NOP); /* Will be patched later */ usize body_start = g->ninstrs; /* Body starts here */ /* Patch all the pattern match jumps to point to the body start */ for (usize p = 0; p < njumps; p++) { branch_patch_apply(g, jumps[p], body_start); } gen_case_guard(g, n, &ctrl); return ctrl; } /* Generate code for a match statement by converting it to a series of * equality comparisons */ static void gen_match(gen_t *g, node_t *n) { /* If there are no cases, nothing to do */ if (n->val.match_stmt.cases.len == 0) return; /* Generate code for the match operand and load it into a register */ value_t match_val = gen_expr(g, n->val.match_stmt.expr, false); reg_t match_reg = emit_load(g, match_val); /* Track jump locations to the end of the match */ usize end_jumps[MAX_SWITCH_CASES]; usize nend_jumps = 0; /* Process each case from first to last */ node_t **cases = nodespan_ptrs(&g->mod->parser, n->val.match_stmt.cases); for (usize i = 0; i < n->val.match_stmt.cases.len; i++) { node_t *cn = cases[i]; if (!cn->val.match_case.patterns.len) { /* Default/else case: generate block body */ gen_node(g, cn->val.match_case.body); break; } /* For cases with patterns, we need to: * 1. Generate pattern tests with jumps to the body if matching * 2. Jump to the next case if no patterns match * 3. Generate the body * 4. Jump to the end of the match after the body */ type_t *match_type = n->val.match_stmt.expr->type; match_case_ctrl_t ctrl; /* Check if matching on a pointer to a union */ type_t *union_type = match_type; if (match_type->cls == TYPE_PTR && type_is_union_with_payload(match_type->info.ptr.target)) { union_type = match_type->info.ptr.target; } if (type_is_union_with_payload(union_type)) { ctrl = gen_match_case_union_payload(g, match_val, cn); } else if (union_type->cls == TYPE_RECORD) { ctrl = gen_match_case_record(g, match_val, cn); } else { ctrl = gen_match_case(g, match_reg, cn); } /* Generate the case body */ gen_node(g, cn->val.match_case.body); /* Jump to end of the match after the body (patched later) */ end_jumps[nend_jumps++] = emit(g, NOP); /* Patch the jump over the body (skip_body=0 means no patching needed) */ if (ctrl.guard_branch.valid) { branch_patch_apply(g, ctrl.guard_branch, g->ninstrs); } if (ctrl.skip_body) { g->instrs[ctrl.skip_body] = JMP(jump_offset(ctrl.skip_body, g->ninstrs)); } } /* Patch all jumps to the end of the match */ usize end = g->ninstrs; for (usize i = 0; i < nend_jumps; i++) { g->instrs[end_jumps[i]] = JMP(jump_offset(end_jumps[i], end)); } freeval(g, match_val); } /* Generate code for an `if` statement. */ static void gen_if(gen_t *g, node_t *n) { node_t *cond = n->val.if_stmt.cond; node_t *lbranch = n->val.if_stmt.lbranch; node_t *rbranch = n->val.if_stmt.rbranch; /* Special case for comparison operations. */ if (node_is_comp(cond)) { /* If there's no else branch, use the simple branch generation, * but only for primitive types that are compatible with BEQ or BNE. */ if (!rbranch && type_is_primitive(cond->val.binop.left->type)) { gen_branch(g, cond, lbranch); return; } } gen_if_else(g, gen_expr(g, cond, false), lbranch, rbranch); } /* Generate code for an if expression */ static value_t gen_if_expr(gen_t *g, node_t *n) { /* Allocate space for the result value */ i32 result_off = reserve(g, n->type); value_t result_val = value_stack(OFFSET(FP, result_off), n->type); /* Generate condition */ value_t cond_val = gen_expr(g, n->val.if_stmt.cond, false); reg_t cond_reg = emit_load(g, cond_val); /* Branch to else if condition is false */ branch_patch_t else_branch = branch_patch_make(g, I_BEQ, cond_reg, ZERO); freereg(g, cond_reg); /* Generate then branch and store result */ value_t then_val = gen_expr(g, n->val.if_stmt.lbranch, false); emit_store(g, then_val, result_val.as.off.base, result_val.as.off.offset); /* Jump over else branch */ usize end_jump = emit(g, NOP); /* Placeholder for unconditional jump */ /* Patch else branch jump */ usize else_start = g->ninstrs; branch_patch_apply(g, else_branch, else_start); /* Generate else branch and store result */ value_t else_val = gen_expr(g, n->val.if_stmt.rbranch, false); emit_store(g, else_val, result_val.as.off.base, result_val.as.off.offset); /* Patch end jump */ usize end = g->ninstrs; g->instrs[end_jump] = JMP(jump_offset(end_jump, end)); return result_val; } /* Generate code for an `if let` statement. * This checks if an optional value has content and binds it to a variable if * so. */ static void gen_if_let(gen_t *g, node_t *n) { /* Generate the optional expression */ value_t opt_val = gen_expr(g, n->val.if_let_stmt.expr, false); /* Load the tag to check if optional has a value */ reg_t tag_reg = tval_load_tag(g, opt_val); /* Set up conditional branch: if `exists` is 0, skip the left branch */ branch_patch_t lb_branch = branch_patch_make(g, I_BEQ, tag_reg, ZERO); /* Create and allocate the bound variable (unless it's a placeholder) */ if (n->val.if_let_stmt.var->cls != NODE_PLACEHOLDER) { symbol_t *val_sym = n->val.if_let_stmt.var->sym; i32 val_off = reserve_aligned(g, val_sym->e.var.typ, val_sym->e.var.align); val_sym->e.var.val = value_stack(OFFSET(FP, val_off), val_sym->e.var.typ); /* Copy the value part from the optional to the local variable */ optval_copy_value(g, opt_val, val_sym->e.var.val); } /* If there's a guard condition, evaluate it */ branch_patch_t guard_branch = branch_patch_invalid(); if (n->val.if_let_stmt.guard) { value_t guard_val = gen_expr(g, n->val.if_let_stmt.guard, false); reg_t guard_reg = emit_load(g, guard_val); /* If guard is false, jump to else branch */ guard_branch = branch_patch_make(g, I_BEQ, guard_reg, ZERO); /* Will patch later */ freereg(g, guard_reg); } /* Generate code for the left branch */ gen_block(g, n->val.if_let_stmt.lbranch); if (n->val.if_let_stmt.rbranch) { /* If we have an else branch, emit jump to skip over it */ const usize lb_end = emit(g, NOP); const usize rb_start = g->ninstrs; /* Patch the branch instruction to jump to else in *none* case */ branch_patch_apply(g, lb_branch, rb_start); /* Patch guard condition branch if it exists */ if (guard_branch.valid) { branch_patch_apply(g, guard_branch, rb_start); } freereg(g, tag_reg); /* Generate code for the else branch */ gen_block(g, n->val.if_let_stmt.rbranch); /* Patch the jump instruction to skip over the else branch */ usize rb_end = g->ninstrs; g->instrs[lb_end] = JMP(jump_offset(lb_end, rb_end)); } else { /* No else branch, just patch the branch to skip the then branch */ usize lb_end = g->ninstrs; branch_patch_apply(g, lb_branch, lb_end); /* Patch guard condition branch if it exists */ if (guard_branch.valid) { branch_patch_apply(g, guard_branch, lb_end); } freereg(g, tag_reg); } } /* Generate code for a forever loop. */ static void gen_loop(gen_t *g, node_t *n) { /* Save the outer loop context and setup new context with a new loop id. */ loop_t outer = g->loop; g->loop.current = n; g->loop.start = g->ninstrs; /* Generate code for the loop body. */ gen_block(g, n->val.loop_stmt.body); /* Jump back to the beginning of the loop. */ emit_jump(g, g->loop.start); /* Mark this position as the loop end for break statements */ g->loop.end = g->ninstrs; patch_break_stmts(g); g->loop = outer; } /* Generate code for a break statement. */ static void gen_break(gen_t *g, node_t *n) { (void)n; if (g->loop.current->cls != NODE_LOOP) { bail("`break` statement outside of loop"); } /* Instead of calculating the jump offset now, emit a placeholder * instruction that will be patched when we know where the loop ends. */ usize offset = emit(g, NOP); /* Record this location for patching. */ g->fn.brkpatches[g->fn.nbrkpatches++] = (ctpatch_t){ .pc = offset, .loop = g->loop.current, .applied = false, }; } static void gen_assign(gen_t *g, node_t *n) { node_t *lval = n->val.assign.lval; node_t *rval = n->val.assign.rval; switch (lval->cls) { case NODE_IDENT: { /* Handle normal variable assignment. */ symbol_t *sym = lval->sym; value_t left = sym->e.var.val; value_t right = gen_expr(g, rval, false); /* Nb. frees the right value if it's in a register. */ emit_replace(g, left, right); break; } case NODE_ACCESS: { /* Handle record field assignment (e.g., x.y = 1). */ value_t left = gen_expr(g, lval, true); value_t right = gen_expr(g, rval, false); /* Replace the field value with the right-hand side */ emit_replace(g, left, right); break; } case NODE_ARRAY_INDEX: { /* Array index assignment (e.g. `arr[0] = 1`). */ value_t left = gen_array_index(g, lval, true); value_t right = gen_expr(g, rval, false); /* Replace the array element value with the right-hand side. */ emit_replace(g, left, right); /* Free the address register from array indexing */ if (left.loc == LOC_STACK) { freereg(g, left.as.off.base); } break; } case NODE_UNOP: { /* Handle pointer dereference assignment */ if (lval->val.unop.op != OP_DEREF) { bail("unsupported unary operator in assignment target"); } value_t ptr_val = gen_expr(g, lval->val.unop.expr, true); value_t right = gen_expr(g, rval, false); /* `gen_deref` expects an lvalue when the pointer itself is the storage * we want to mutate (e.g., `*ptr = ...`). */ value_t left = gen_deref(g, lval, ptr_val, true); emit_replace(g, left, right); break; } default: bail("unsupported assignment target %s", node_names[lval->cls]); } } static void gen_return(gen_t *g, node_t *n) { type_t *ret_typ = g->fn.current->node->type->info.fun.ret; node_t *value = n->val.return_stmt.value; /* If there's a return value, evaluate the expression. * Then, store the expression, in the return register A0, * according to the RISC-V calling conventions. */ if (value) { value_t val = gen_expr(g, value, false); if (ret_typ->cls == TYPE_RESULT) { value_t dest; if (type_is_passed_by_ref(ret_typ)) { usereg(g, A0); dest = value_stack(OFFSET(A0, 0), ret_typ); } else { dest = value_reg(A0, ret_typ); } /* Returns are always for the "success" case. */ emit_result_store_success(g, dest, val); } else if (ret_typ->cls == TYPE_OPT && type_coercible(val.type, ret_typ->info.opt.elem)) { /* Wrap value in an optional */ usereg(g, A0); tval_store(g, value_stack(OFFSET(A0, 0), ret_typ), val, 1); } else if (ret_typ->cls == TYPE_OPT && val.type->cls == TYPE_OPT) { /* Value is already optional, copy it */ usereg(g, A0); emit_replace(g, value_stack(OFFSET(A0, 0), ret_typ), val); } else if (type_is_passed_by_ref(val.type)) { /* Aggregate returns go through the hidden sret pointer. */ usereg(g, A0); emit_replace(g, value_stack(OFFSET(A0, 0), val.type), val); } else { emit_load_into(g, A0, val); } freeval(g, val); } else { if (ret_typ->cls == TYPE_RESULT) { value_t dest; if (type_is_passed_by_ref(ret_typ)) { usereg(g, A0); dest = value_stack(OFFSET(A0, 0), ret_typ); } else { dest = value_reg(A0, ret_typ); } emit_result_store_success(g, dest, value_none()); } else { /* If there's no return value, we just store zero in A0. */ emit_load_into( g, A0, value_imm((imm_t){ .i = 0 }, g->types->type_i32) ); } } /* Instead of returning directly, emit a placeholder jump to the function * epilogue that will be patched later. This avoids duplicating epilogue * code for each return point. */ usize pc = emit(g, NOP); if (g->fn.nretpatches >= MAX_RET_PATCHES) bail("too many return statements in function"); /* Record this location for patching */ g->fn.retpatches[g->fn.nretpatches++] = (ctpatch_t){ .pc = pc, .applied = false, }; } /* Emit the control flow for `throw` * * 1. Evaluate the error expression * 2. Lay it out in the caller-visible result slot (A0 or *A0) * 3. Queue a jump to the epilogue so every throw shares the same return path */ static void gen_throw(gen_t *g, node_t *n) { type_t *fn_ret = g->fn.current->node->type->info.fun.ret; value_t err_val = gen_expr(g, n->val.throw_stmt.expr, false); value_t dest; if (type_is_passed_by_ref(fn_ret)) { usereg(g, A0); dest = value_stack(OFFSET(A0, 0), fn_ret); } else { dest = value_reg(A0, fn_ret); } emit_result_store_error(g, dest, err_val); freeval(g, err_val); /* Jump to function end (patch) */ usize pc = emit(g, NOP); if (g->fn.nretpatches >= MAX_RET_PATCHES) bail("too many return statements in function"); /* Patch to jump to function epilogue */ g->fn.retpatches[g->fn.nretpatches++] = (ctpatch_t){ .pc = pc, .applied = false, }; } /* Emit `try` * * 1. Evaluate the expression result * 2. Load its tag and branch past the error path when the tag is zero * 3. On error, normalize the tag/value into the function result slot and * enqueue a jump to the epilogue (mirroring an early return) * 4. On success, expose the payload location for the caller * * With catch block: * 1. Evaluate the expression result * 2. Load its tag and branch based on success/error * 3. On error: execute catch block (must diverge or return void) * 4. On success: use payload value */ static value_t gen_try(gen_t *g, node_t *n) { /* 1. */ value_t res_val = gen_expr(g, n->val.try_expr.expr, false); tval_t res = tval_from_val(g, res_val); /* Inspect the tag to determine whether the result is success or error. */ reg_t tag = nextreg(g); emit_regload( g, tag, res.tag.as.off.base, res.tag.as.off.offset, g->types->type_u8 ); type_t *payload = res_val.type->info.res.payload; type_t *result_type = n->type ? n->type : payload; /* Handle `try?` expressions */ if (n->val.try_expr.optional) { /* Allocate stack space for the optional result. */ i32 result_offset = reserve(g, result_type); value_t result = value_stack(OFFSET(FP, result_offset), result_type); /* Branch over the error path when the tag is zero (success). */ branch_patch_t success_branch = branch_patch_make(g, I_BEQ, tag, ZERO); freereg(g, tag); /* Error path: store nil (tag = 0). */ tval_store(g, result, (value_t){ 0 }, 0); /* Jump over success path. */ usize end_patch = emit(g, JMP(0)); /* Success path: store Some(payload) (tag = 1). */ branch_patch_apply(g, success_branch, g->ninstrs); value_t payload_val = res.val; payload_val.type = payload; tval_store(g, result, payload_val, 1); /* End: both paths converge here. */ g->instrs[end_patch] = JMP(jump_offset(end_patch, g->ninstrs)); return result; } /* Handle catch block */ if (n->val.try_expr.catch_expr) { node_t *catch_node = n->val.try_expr.catch_expr; node_t *catch_binding = catch_node->val.catch_clause.binding; node_t *catch_body = catch_node->val.catch_clause.body; /* Branch over the error path when the tag is zero (success). */ branch_patch_t success_branch = branch_patch_make(g, I_BEQ, tag, ZERO); freereg(g, tag); /* If there's a binding, store the error value to the variable. */ if (catch_binding && catch_binding->sym) { symbol_t *err_sym = catch_binding->sym; type_t *err_type = res_val.type->info.res.err; i32 err_off = reserve_aligned(g, err_type, err_sym->e.var.align); err_sym->e.var.val = value_stack(OFFSET(FP, err_off), err_type); /* Create a value pointing to the error slot (same as payload). */ value_t err_slot = res.val; err_slot.type = err_type; /* Copy the error to the bound variable. */ emit_replace(g, err_sym->e.var.val, err_slot); } gen_block(g, catch_body); branch_patch_apply(g, success_branch, g->ninstrs); if (catch_body->type && catch_body->type->cls == TYPE_NEVER) { value_t payload_val = res.val; payload_val.type = payload; return payload_val; } return value_none(); } /* Branch over the error path when the tag is zero (success). */ branch_patch_t success_branch = branch_patch_make(g, I_BEQ, tag, ZERO); if (n->val.try_expr.panic) { emit(g, EBREAK); branch_patch_apply(g, success_branch, g->ninstrs); freereg(g, tag); if (n->val.try_expr.handlers.len > 0) bail("catch clauses not supported in code generation"); if (!payload->size) { return value_none(); } value_t result = res.val; result.type = payload; return result; } type_t *fn_ret = g->fn.current->node->type->info.fun.ret; /* Prepare the function result slot so we can store an error in-place. */ value_t dest; if (type_is_passed_by_ref(fn_ret)) { usereg(g, A0); dest = value_stack(OFFSET(A0, 0), fn_ret); } else { dest = value_reg(A0, fn_ret); } /* Copy the error payload into the function result slot. */ value_t err_slot = res.val; err_slot.type = res_val.type->info.res.err; emit_result_store_error(g, dest, err_slot); usize ret_pc = emit(g, NOP); if (g->fn.nretpatches >= MAX_RET_PATCHES) bail("too many return statements in function"); g->fn.retpatches[g->fn.nretpatches++] = (ctpatch_t){ .pc = ret_pc, .applied = false, }; branch_patch_apply(g, success_branch, g->ninstrs); freereg(g, tag); if (n->val.try_expr.handlers.len > 0) bail("catch clauses not supported in code generation"); if (!payload->size) { return value_none(); } value_t result = res.val; result.type = payload; /* Unwrap payload */ return result; } static value_t gen_binop(gen_t *g, node_t *n) { value_t lval = gen_expr(g, n->val.binop.left, false); reg_t left = emit_load(g, lval); /* Ensure generation for the rval does not overwrite the lval. */ usereg(g, left); value_t rval = gen_expr(g, n->val.binop.right, false); reg_t right = emit_load(g, rval); reg_t result = left; switch (n->val.binop.op) { case OP_ADD: if (type_is_int(lval.type->cls)) { emit(g, ADDW(left, left, right)); } else { emit(g, ADD(left, left, right)); } break; case OP_SUB: if (type_is_int(lval.type->cls)) { emit(g, SUBW(left, left, right)); } else { emit(g, SUB(left, left, right)); } break; case OP_MUL: if (type_is_int(lval.type->cls)) { emit(g, MULW(left, left, right)); } else { emit(g, MUL(left, left, right)); } break; case OP_DIV: /* Check for division by zero (node is already set by gen_node) */ emit(g, BNE(right, ZERO, INSTR_SIZE * 2)); emit(g, EBREAK); if (type_is_unsigned(lval.type->cls)) { emit(g, DIVUW(left, left, right)); } else { emit(g, DIVW(left, left, right)); } break; case OP_MOD: if (type_is_int(lval.type->cls)) { /* Check for division by zero (node is already set by gen_node) */ emit(g, BNE(right, ZERO, INSTR_SIZE * 2)); emit(g, EBREAK); if (type_is_unsigned(lval.type->cls)) { emit(g, REMUW(left, left, right)); } else { emit(g, REMW(left, left, right)); } } else { bail("modulo operator is only supported for integers"); } break; case OP_EQ: case OP_NE: { bool invert = (n->val.binop.op == OP_NE); bool opt_left = (lval.type->cls == TYPE_OPT); bool opt_right = (rval.type->cls == TYPE_OPT); bool left_nil = (n->val.binop.left->cls == NODE_NIL); bool right_nil = (n->val.binop.right->cls == NODE_NIL); /* Fast-path for comparisons with `nil`. */ if (opt_left && opt_right && (left_nil || right_nil)) { if (left_nil && right_nil) { freereg(g, left); freereg(g, right); reg_t result_reg = nextreg(g); emit_li(g, result_reg, invert ? 0 : 1); return value_reg(result_reg, n->type); } reg_t opt_reg = left_nil ? right : left; reg_t nil_reg = left_nil ? left : right; freereg(g, nil_reg); reg_t tag_reg = nextreg(g); emit(g, LBU(tag_reg, opt_reg, 0)); emit(g, SLTIU(tag_reg, tag_reg, 1)); if (invert) emit(g, XORI(tag_reg, tag_reg, 1)); freereg(g, opt_reg); return value_reg(tag_reg, n->type); } if (opt_left != opt_right) { type_t *opt_type = opt_left ? lval.type : rval.type; value_t value_expr = opt_left ? rval : lval; value_t wrapped = optval_from_value(g, opt_type, value_expr); reg_t target_reg = opt_left ? right : left; emit_load_into(g, target_reg, wrapped); result = nextreg(g); emit_memequal(g, left, right, opt_type, result); if (invert) emit(g, XORI(result, result, 1)); } else if (type_is_primitive(lval.type)) { if (invert) { /* XOR will be non-zero if values differ. */ emit(g, XOR(left, left, right)); /* Set to 1 if result is non-zero (different). */ emit(g, SLTU(left, ZERO, left)); } else { /* Emits `result = left - right` */ if (type_is_int(lval.type->cls)) { emit(g, SUBW(left, left, right)); } else { emit(g, SUB(left, left, right)); } /* Emits `result = (result < 1) ? 1 : 0` */ emit(g, SLTIU(left, left, 1)); } } else { result = nextreg(g); emit_memequal(g, left, right, lval.type, result); if (invert) emit(g, XORI(result, result, 1)); } break; } case OP_LT: /* Emits `result = (left < right) ? 1 : 0` */ if (type_is_unsigned(lval.type->cls)) { emit(g, SLTU(left, left, right)); } else { emit(g, SLT(left, left, right)); } break; case OP_GT: /* Emits `result = (right < left) ? 1 : 0` */ if (type_is_unsigned(lval.type->cls)) { emit(g, SLTU(left, right, left)); } else { emit(g, SLT(left, right, left)); } break; case OP_LE: /* For `x <= y`, we can compute `!(x > y)`, which is `!(y < x)`, */ if (type_is_unsigned(lval.type->cls)) { emit(g, SLTU(left, right, left)); } else { emit(g, SLT(left, right, left)); } emit(g, XORI(left, left, 1)); break; case OP_GE: /* For `x >= y`, we can compute `!(x < y)`. */ if (type_is_unsigned(lval.type->cls)) { emit(g, SLTU(left, left, right)); } else { emit(g, SLT(left, left, right)); } emit(g, XORI(left, left, 1)); break; case OP_AND: /* Logical AND; both values must be 1 for the result to be 1. */ emit(g, AND(left, left, right)); break; case OP_OR: /* Logical OR; if either value is 1, the result is 1. */ emit(g, OR(left, left, right)); break; case OP_BAND: /* Bitwise AND */ emit(g, AND(left, left, right)); break; case OP_BOR: /* Bitwise OR */ emit(g, OR(left, left, right)); break; case OP_XOR: /* Bitwise XOR */ emit(g, XOR(left, left, right)); break; case OP_SHL: /* Left shift */ if (type_is_int(lval.type->cls)) { emit(g, SLLW(left, left, right)); } else { emit(g, SLL(left, left, right)); } break; case OP_SHR: /* Right shift */ if (type_is_int(lval.type->cls)) { emit(g, SRLW(left, left, right)); } else { emit(g, SRL(left, left, right)); } break; } /* Check if result needs to be coerced to optional type */ if (n->type->cls == TYPE_OPT) { i32 offset = reserve(g, n->type); value_t opt_val = value_stack(OFFSET(FP, offset), n->type); value_t result_val = value_reg(result, n->type->info.opt.elem); tval_store(g, opt_val, result_val, 1); lval = opt_val; /* Can free all registers since result is stored on stack */ freereg(g, left); freereg(g, right); freereg(g, result); } else { lval = value_reg(result, n->type); if (left != result) freereg(g, left); if (right != result) freereg(g, right); } return lval; } /* Generate code for record construction. Handles both labeled syntax like * `Point { x: 1, y: 2 }` (NODE_RECORD_LIT) and tuple syntax like `Pair(1, 2)` * (NODE_CALL with tuple record type). */ static value_t gen_record_lit(gen_t *g, node_t *n) { type_t *stype = n->type; int strct_off = reserve(g, stype); usize nfields = (n->cls == NODE_RECORD_LIT) ? n->val.record_lit.fields.len : n->val.call.args.len; node_t **fields = (n->cls == NODE_RECORD_LIT) ? nodespan_ptrs(&g->mod->parser, n->val.record_lit.fields) : NULL; for (usize i = 0; i < nfields; i++) { symbol_t *field; node_t *expr; if (n->cls == NODE_RECORD_LIT) { node_t *arg = fields[i]; field = arg->sym ? arg->sym : stype->info.srt.fields[i]; expr = arg->val.call_arg.expr; } else { node_t *arg = SPAN(g, n->val.call.args)[i]; field = stype->info.srt.fields[i]; expr = (arg->cls == NODE_CALL_ARG) ? arg->val.call_arg.expr : arg; } value_t argval = gen_expr(g, expr, false); emit_record_field_set(g, argval, FP, strct_off, field); freeval(g, argval); } return value_stack(OFFSET(FP, strct_off), stype); } static value_t gen_call_intrinsic( gen_t *g, node_t *n, void (*gen_intrinsic)(gen_t *, node_t *) ) { node_t *fn = n->sym->node; type_t *ret = fn->val.fn_decl.return_type ? fn->val.fn_decl.return_type->type : NULL; /* Call the specialized generator for this intrinsic. * It will handle argument processing in its own way. */ (*gen_intrinsic)(g, n); /* For void functions, return a void value */ if (!ret) { return (value_t){ .type = NULL, .loc = LOC_NONE }; } return value_reg(A0, ret); } static value_t gen_call(gen_t *g, node_t *n) { symbol_t *sym = n->sym; const char *name = sym->qualified; /* Get the return type. Fall back to the call node type when the symbol * does not carry a resolved function signature (eg. indirect calls). */ type_t *return_type = sym->node->type->info.fun.ret; if (!return_type && n->type) { return_type = n->type; } /* Keep track of registers we saved before the call. */ i32 saved_regs[REGISTERS] = { 0 }; value_t saved_vals[REGISTERS] = { 0 }; symbol_t *saved_syms[REGISTERS] = { 0 }; /* Save live registers to the stack, in case they get clobbered by * the callee. */ for (usize i = 0; i < RALLOC_NREGS; i++) { reg_t r = ralloc_regs[i]; /* Don't save registers that aren't caller-saved. */ if (!caller_saved_registers[r]) continue; /* Don't save registers that aren't in use. */ if (ralloc_is_free(&g->regs, r)) continue; /* Use a pointer-sized type for saving registers to the stack. */ static type_t dword = { .cls = TYPE_PTR, .size = WORD_SIZE, .align = WORD_SIZE }; saved_regs[r] = emit_regpush(g, r, &dword); /* We can free the register since it's on the stack. */ freereg(g, r); /* Parameters arrive in caller-saved registers; if we let the allocator * reuse that register (e.g. in emit_memzero), the parameter value gets * clobbered. When we spill the register here, rewrite the symbol to * point at the spill slot so later loads grab the preserved copy. */ node_t *fn_node = g->fn.current->node; for (usize p = 0; p < fn_node->val.fn_decl.params.len; p++) { node_t *param = SPAN(g, fn_node->val.fn_decl.params)[p]; symbol_t *param_sym = param->sym; value_t *param_val = ¶m_sym->e.var.val; if (param_val->loc == LOC_REG && param_val->as.reg == r) { saved_syms[r] = param_sym; saved_vals[r] = *param_val; param_sym->e.var.val = value_stack(OFFSET(FP, saved_regs[r]), param_val->type); param_sym->e.var.val.temp = false; break; } } } bool sret = type_is_passed_by_ref(return_type); reg_t arg0 = sret ? A1 : A0; usize avail_arg_regs = (usize)((A7 - arg0) + 1); if (n->val.call.args.len > avail_arg_regs) { bail( "function call '%s' requires %zu argument registers but only %zu " "are available", name, n->val.call.args.len, avail_arg_regs ); } /* Setup arguments in argument registers (A0..A7), shifting when a hidden * return pointer occupies A0. */ for (usize i = 0; i < n->val.call.args.len; i++) { /* Generate code for the expression part of the argument. */ node_t *arg = SPAN(g, n->val.call.args)[i]; value_t argval = gen_expr(g, arg->val.call_arg.expr, false); type_t *param_type = sym->node->type->info.fun.params[i]; if (param_type->cls == TYPE_OPT && argval.type->cls != TYPE_OPT) { argval = optval_from_value(g, param_type, argval); } /* Mark this register as in use for the duration of the call. */ reg_t arg_reg = arg0 + (reg_t)i; emit_load_into(g, usereg(g, arg_reg), argval); } /* Return value is in A0, by convention, whether or not an address was * passed into A0 by the caller. */ reg_t return_reg = A0; /* Return stack offset if we store it on the stack. */ i32 return_off = 0; i32 return_stack_off = 0; bool return_is_on_stack = false; /* For types that are passed by reference, allocate space in this * stack frame, and pass the address via A0, as a hidden first parameter. * Nb. The return record address is setup *after* the call arguments * are generated, to not clobber A0 in case one of the arguments is a * call, eg. `f(g())` where `f` is the current function call. */ if (return_type->cls == TYPE_VOID) { /* For void functions, no need to allocate space for return value */ } else if (sret) { return_off = reserve(g, return_type); /* Result-returning callees can legitimately skip rewriting the tag on * a fast-path success, so ensure the caller-visible slot starts zeroed. * Other pass-by-ref aggregates are always fully overwritten by the * callee, making a pre-emptive memset unnecessary work. */ if (return_type->cls == TYPE_RESULT) { emit_memzero(g, OFFSET(FP, return_off), return_type->size); } /* Store return address in return address register. */ usereg(g, return_reg); emit_addr_offset(g, return_reg, FP, return_off); } /* Call the function. */ if (sym->kind == SYM_VARIABLE) { /* Function pointer call: load address into S2 and call via JALR */ value_t fn_ptr_val = sym->e.var.val; if (fn_ptr_val.loc == LOC_REG && saved_regs[fn_ptr_val.as.reg]) { value_t spill = value_stack( OFFSET(FP, saved_regs[fn_ptr_val.as.reg]), fn_ptr_val.type ); emit_load_into(g, S2, spill); } else if (fn_ptr_val.loc == LOC_REG) { emit_mv(g, S2, fn_ptr_val.as.reg); } else { emit_load_into(g, S2, fn_ptr_val); } emit(g, JALR(RA, S2, 0)); } else if (sym->e.fn.attribs & ATTRIB_EXTERN) { /* External function. */ } else if (sym->e.fn.addr) { /* Direct call, address is already known. */ emit_call(g, sym->e.fn.addr); } else { if (g->nfnpatches >= MAX_FN_PATCHES) bail("too many function call patches"); /* Indirect call with patch later, address is not yet known. */ reg_t scratch = nextreg(g); usize pc = emit(g, NOP); usize tramp = emit(g, NOP); g->fnpatches[g->nfnpatches++] = (fnpatch_t){ .fn_name = sym->qualified, .pc = pc, .tramp_pc = tramp, .patch_type = PATCH_CALL, .target_reg = 0, .scratch_reg = scratch, }; freereg(g, scratch); } /* If the return register (A0) was in use before the function call, move the * return value to a fresh register so restored caller values do not wipe it * out. */ bool is_reg_return = (return_type->cls != TYPE_VOID) && !type_is_passed_by_ref(return_type); bool is_return_reg_saved = saved_regs[return_reg] != 0; if (is_reg_return && is_return_reg_saved) { return_stack_off = emit_regpush(g, return_reg, return_type); return_is_on_stack = true; } /* Restore all saved registers. */ for (usize i = 0; i < RALLOC_NREGS; i++) { reg_t dst = ralloc_regs[i]; i32 offset = saved_regs[dst]; if (!offset) continue; static type_t dword = { .cls = TYPE_PTR, .size = WORD_SIZE, .align = WORD_SIZE }; emit_regload(g, dst, FP, offset, &dword); usereg(g, dst); /* Undo the temporary rebinding so the parameter once again refers to * its original register value now that the spill has been reloaded. */ if (saved_syms[dst]) { saved_syms[dst]->e.var.val = saved_vals[dst]; saved_syms[dst]->e.var.val.temp = false; } } /* Restore argument registers that weren't in use before the call. */ for (usize i = 0; i < n->val.call.args.len; i++) { reg_t arg = arg0 + (reg_t)i; if (!saved_regs[arg]) freereg(g, arg); } /* For records, the return value is stored on the stack, and the return * register holds the address. For everything else, it's in a register. */ if (return_type->cls == TYPE_VOID) { /* For void functions, we don't return a value */ if (!is_return_reg_saved) freereg(g, return_reg); return (value_t){ .type = return_type, .loc = LOC_NONE }; } else if (type_is_passed_by_ref(return_type)) { return value_stack(OFFSET(FP, return_off), return_type); } else { if (return_is_on_stack) { if (!is_return_reg_saved) freereg(g, return_reg); return value_stack(OFFSET(FP, return_stack_off), return_type); } /* The return value is marked as temp, so the caller is responsible * for freeing the register when done with the value. Mark the register * as in use to prevent reallocation before the value is consumed. */ usereg(g, return_reg); return value_reg(return_reg, return_type); } } /* Generate code to access a slice field (len or ptr) given the slice value. */ static value_t gen_slice_field( gen_t *g, value_t slice_val, node_t *field, type_t *result_type ) { if (memcmp(field->val.ident.name, LEN_FIELD, LEN_FIELD_LEN) == 0) { reg_t len = emit_load_offset(g, slice_val, SLICE_FIELD_LEN_OFFSET); /* Slice lengths are stored as full dwords but typed as u32. * Zero-extend to clear any upper 32 bits so that 64-bit * comparisons (SLTU etc.) produce correct results on RV64. */ if (WORD_SIZE == 8) { emit(g, SLLI(len, len, 32)); emit(g, SRLI(len, len, 32)); } return value_reg(len, result_type); } if (memcmp(field->val.ident.name, PTR_FIELD, PTR_FIELD_LEN) == 0) { reg_t ptr = emit_load_offset(g, slice_val, SLICE_FIELD_PTR_OFFSET); return value_reg(ptr, result_type); } bail("unknown slice field"); } static value_t gen_access_ref(gen_t *g, node_t *n) { node_t *expr = n->val.access.lval; type_t *expr_typ = expr->type; type_t *target_type = deref_type(expr_typ); switch (target_type->cls) { case TYPE_RECORD: { value_t ptr_val = gen_expr(g, expr, true); symbol_t *field = n->sym; useval(g, ptr_val); /* For pointer access like ptr.field, we need to dereference first */ /* Create a temporary node for dereferencing */ node_t deref_node = { .cls = NODE_UNOP, .type = target_type, .val.unop.op = OP_DEREF, .val.unop.expr = expr, }; /* For pointer-to-record field access, keep the pointed-to record as an * lvalue so the field setter sees the original storage address. */ value_t record_val = gen_deref(g, &deref_node, ptr_val, true); freeval(g, ptr_val); return emit_record_field_get(record_val, field); } case TYPE_SLICE: { node_t *field = n->val.access.rval; /* Dereference to get the slice value, then access the field */ value_t ptr_val = gen_expr(g, expr, true); useval(g, ptr_val); node_t deref_node = { .cls = NODE_UNOP, .type = target_type, .val.unop.op = OP_DEREF, .val.unop.expr = expr, }; value_t slice_val = gen_deref(g, &deref_node, ptr_val, true); freeval(g, ptr_val); return gen_slice_field(g, slice_val, field, n->type); } case TYPE_ARRAY: { /* For pointer access like ptr[index], create a temporary array index * node */ /* and let gen_array_index handle the pointer dereferencing */ node_t array_index_node = { .cls = NODE_ARRAY_INDEX, .type = n->type, .val.access.lval = expr, .val.access.rval = n->val.access.rval }; return gen_array_index(g, &array_index_node, true); } default: bail( "cannot access field of reference to %s", type_names[target_type->cls] ); } } static value_t gen_access(gen_t *g, node_t *n, bool lval) { node_t *expr = n->val.access.lval; type_t *expr_typ = expr->type; node_t *field = n->val.access.rval; /* Handle non-reference types. */ switch (expr_typ->cls) { case TYPE_PTR: return gen_access_ref(g, n); case TYPE_RECORD: { /* Struct value and type. */ value_t sval = gen_expr(g, expr, lval); symbol_t *field = n->sym; return emit_record_field_get(sval, field); } case TYPE_SLICE: { value_t slice_val = gen_expr(g, expr, lval); return gen_slice_field(g, slice_val, field, n->type); } /* Fall through */ default: abort(); } } /* Generate code to obtain a function pointer for the given symbol */ static value_t gen_fn_ptr(gen_t *g, symbol_t *sym, type_t *type) { reg_t reg = nextreg(g); if (sym->e.fn.addr) { /* Direct function address is known - use AUIPC+ADDI for PC-relative * addressing since the program may be loaded at a non-zero base. */ emit_pc_rel_addr(g, reg, sym->e.fn.addr); return value_reg(reg, type); } /* Function address will be patched later - generate AUIPC+ADDI sequence */ usize pc = emit(g, NOP); /* Placeholder - will be patched with AUIPC */ emit(g, NOP); /* Second placeholder - will be patched with ADDI */ if (g->nfnpatches >= MAX_FN_PATCHES) bail("too many function address patches"); g->fnpatches[g->nfnpatches++] = (fnpatch_t){ .fn_name = sym->qualified, .pc = pc, .tramp_pc = pc + 1, .patch_type = PATCH_ADDRESS, .target_reg = reg, .scratch_reg = ZERO, }; return value_reg(reg, type); } static value_t gen_scope(gen_t *g, node_t *n) { symbol_t *sym = n->sym; /* Generate code based on the symbol type, not the lval */ switch (sym->kind) { case SYM_VARIABLE: break; case SYM_CONSTANT: if (sym->e.var.val.loc == LOC_NONE) { gen_const(g, sym->node); } return sym->e.var.val; case SYM_VARIANT: if (n->type->cls == TYPE_UNION) { if (type_is_union_with_payload(n->type)) { return gen_union_store(g, n->type, sym, value_none()); } return value_imm( (imm_t){ .i = sym->node->val.union_variant.value }, n->type ); } else { bail("variant of type %s is invalid", type_names[n->type->cls]); } break; case SYM_FUNCTION: return gen_fn_ptr(g, sym, n->type); default: break; } bail( "unhandled scope case for symbol kind %d, node kind %s", sym->kind, node_names[n->cls] ); } static value_t gen_ref(gen_t *g, node_t *n) { /* Slice literal */ if (n->val.ref.target->cls == NODE_ARRAY_LIT) { value_t ary = gen_array_literal(g, n->val.ref.target); return gen_array_slice(g, ary, NULL); } /* Ask for an lvalue so we get back the actual storage location. */ value_t target_val = gen_expr(g, n->val.ref.target, true); /* If the value is in a register, we need its address. * This requires the value to be moved to the stack first. */ if (target_val.loc == LOC_REG) { target_val = emit_push(g, target_val); } if (target_val.loc == LOC_STACK) { /* Turn the stack location into an address held in a register. */ reg_t addr = nextreg(g); emit_addr_offset( g, addr, target_val.as.off.base, target_val.as.off.offset ); return value_reg(addr, n->type); } if (target_val.loc == LOC_ADDR) { reg_t addr = nextreg(g); emit_li(g, addr, target_val.as.adr.base + target_val.as.adr.offset); return value_reg(addr, n->type); } /* For immediates and other types, we can't take a reference. */ bail("cannot take a reference to the target expression"); } static value_t gen_deref(gen_t *g, node_t *n, value_t ref_val, bool lval) { reg_t addr = ZERO; bool addr_from_load = false; /* Resolve the pointer value into a register. */ if (ref_val.loc == LOC_REG) { addr = ref_val.as.reg; } else if (ref_val.loc == LOC_STACK || ref_val.loc == LOC_ADDR) { addr = emit_load(g, ref_val); addr_from_load = true; } else { bail("cannot dereference expression at this location"); } value_t location = value_stack(OFFSET(addr, 0), n->type); if (lval || type_is_passed_by_ref(n->type)) { return location; } reg_t val_reg = emit_load(g, location); if (addr_from_load) freereg(g, addr); return value_reg(val_reg, n->type); } /* Generate an array literal. * * This function handles array literals like `[1, 2, 3]`. It allocates * space for the array on the stack, evaluates each element, and initializes * the array elements in memory. */ static value_t gen_array_literal(gen_t *g, node_t *n) { type_t *array_type = n->type; type_t *elem_type = array_type->info.ary.elem; usize length = array_type->info.ary.length; /* Reserve stack space for the array in the current frame. */ int array_off = reserve(g, array_type); /* Evaluate and store each element of the array. */ node_t **elems = nodespan_ptrs(&g->mod->parser, n->val.array_lit.elems); for (usize i = 0; i < length; i++) { node_t *elem = elems[i]; frame_t *frame = &g->fn.current->e.fn.frame; i32 saved_sp = frame->sp; value_t elem_val = gen_expr(g, elem, false); /* Calculate the offset for this element in the array. */ i32 elem_off = array_off + (i32)(i * elem_type->size); /* Store the element value at the calculated offset. */ emit_store(g, elem_val, FP, elem_off); freeval(g, elem_val); /* Only reclaim stack space if the element type doesn't contain * pointers. Slices and pointers may reference stack-allocated * temporaries that must remain live. */ if (!type_is_address(elem_type->cls)) { frame->sp = saved_sp; } } /* The initialized array is on the stack at the computed offset. */ return value_stack(OFFSET(FP, array_off), array_type); } /* Generate code for an array repeat literal (e.g. [0; 24]). */ static value_t gen_array_repeat(gen_t *g, node_t *n) { type_t *array_type = n->type; type_t *elem_type = array_type->info.ary.elem; usize length = array_type->info.ary.length; usize array_off = reserve(g, array_type); value_t elem_val = gen_expr(g, n->val.array_repeat_lit.value, false); /* Store the same value at each array position */ for (usize i = 0; i < length; i++) { i32 elem_off = array_off + (i32)(i * elem_type->size); emit_store(g, elem_val, FP, elem_off); } if (elem_val.loc == LOC_REG) freereg(g, elem_val.as.reg); return value_stack(OFFSET(FP, array_off), array_type); } /* Generate code for a slice with a range expression. */ static value_t gen_array_slice(gen_t *g, value_t array_val, node_t *range) { static type_t dword_type = { .cls = TYPE_PTR }; type_t *slice_type, *elem_type; if (array_val.type->cls == TYPE_ARRAY) { slice_type = array_val.type->slice; elem_type = slice_type->info.slc.elem; } else { /* TYPE_SLICE */ slice_type = array_val.type; elem_type = array_val.type->info.slc.elem; } /* Reserve stack space for the slice (pointer + length) */ i32 slice_off = reserve(g, slice_type); value_t slice_val = value_stack(OFFSET(FP, slice_off), slice_type); reg_t slice_start = ZERO; /* Start index */ /* 1. Store array pointer at slice offset `0`. * 2. Update slice offset `0` with slice start range. * 3. Compute slice length, based on range. * 4. Store slice length at slice offset `4`. */ /* Emit slice address information */ if (range && range->val.range.start) { /* Generate start expression and bounds check */ reg_t r = nextreg(g); value_t start_val = gen_expr(g, range->val.range.start, false); reg_t start_reg = emit_load(g, start_val); reg_t slice_adr = ZERO; if (array_val.type->cls == TYPE_ARRAY) { slice_adr = emit_load(g, array_val); } else { /* Load data pointer from slice (first word) */ slice_adr = emit_load_dword(g, array_val); } offset_t slice_off = slice_val.as.off; emit_li(g, r, elem_type->size); emit(g, MUL(r, r, start_reg)); /* Offset from array address */ emit(g, ADD(r, r, slice_adr)); /* Full address */ emit_regstore( g, r, slice_off.base, slice_off.offset, &dword_type ); /* Save */ slice_start = start_reg; /* Don't free start_reg yet - still needed as slice_start */ if (array_val.type->cls == TYPE_SLICE) { freereg(g, slice_adr); } freereg(g, r); } else { if (array_val.type->cls == TYPE_ARRAY) { /* For arrays, copy the array address */ emit_copy_by_ref(g, array_val, slice_val); } else { /* TYPE_SLICE */ /* For slices, copy the slice fat pointer */ emit_memcopy(g, array_val.as.off, slice_val.as.off, array_val.type); } } /* Emit slice length information */ if (range && range->val.range.end) { /* Generate end value */ value_t end_val = gen_expr(g, range->val.range.end, false); reg_t end_reg = emit_load(g, end_val); offset_t slice_off = slice_val.as.off; if (slice_start != ZERO) { /* Use SUBW on RV64 so the result is properly sign-extended * to 64 bits, keeping the upper 32 bits clean. */ emit(g, SUBW(end_reg, end_reg, slice_start)); } emit_regstore( g, end_reg, slice_off.base, slice_off.offset + WORD_SIZE, &dword_type ); freereg(g, end_reg); } else { reg_t r = nextreg(g); if (array_val.type->cls == TYPE_ARRAY) { emit_li(g, r, array_val.type->info.ary.length); } else { /* Slice */ /* Load length from slice (second word) */ r = emit_load_offset(g, array_val, SLICE_FIELD_LEN_OFFSET); } /* Slice length = array length - slice start */ offset_t slice_off = slice_val.as.off; if (slice_start != ZERO) { /* Use SUBW on RV64 so the result is properly sign-extended * to 64 bits, keeping the upper 32 bits clean. */ emit(g, SUBW(r, r, slice_start)); } emit_regstore( g, r, slice_off.base, slice_off.offset + WORD_SIZE, &dword_type ); freereg(g, r); } freereg(g, slice_start); return slice_val; } /* Generate array indexing. * * This function handles array indexing operations like `arr[i]` or `slice[i]`, * as well as slicing operations using ranges like `arr[..]` or `arr[0..5]`. */ static value_t gen_array_index(gen_t *g, node_t *n, bool lval) { /* Generate code for the array/slice expression. */ value_t array_val = gen_expr(g, n->val.access.lval, lval); type_t *array_type = array_val.type; if (array_type->cls == TYPE_PTR) { array_type = deref_type(array_type); } /* Check if this is a range expression (for slicing) */ node_t *idx_node = n->val.access.rval; if (idx_node->cls == NODE_RANGE) { return gen_array_slice(g, array_val, idx_node); } else { return emit_array_index( g, array_val, gen_expr(g, idx_node, false), lval ); } } static value_t gen_unop(gen_t *g, node_t *n, bool lval) { value_t expr_val = gen_expr(g, n->val.unop.expr, lval); switch (n->val.unop.op) { case OP_NOT: { /* Logical NOT; invert the boolean value. */ reg_t expr_reg = emit_load(g, expr_val); emit(g, NOT(expr_reg, expr_reg)); return value_reg(expr_reg, expr_val.type); } case OP_NEG: { /* Numerical negation. */ reg_t expr_reg = emit_load(g, expr_val); emit(g, NEG(expr_reg, expr_reg)); return value_reg(expr_reg, expr_val.type); } case OP_BNOT: { /* Bitwise NOT; invert all bits. */ reg_t expr_reg = emit_load(g, expr_val); emit(g, XORI(expr_reg, expr_reg, -1)); return value_reg(expr_reg, expr_val.type); } case OP_DEREF: return gen_deref(g, n, expr_val, lval); default: abort(); } } static value_t gen_string(gen_t *g, node_t *n) { /* Add the string to the data section and get its offset */ usize str_len = n->val.string_lit.length; usize str_off = data_string(&g->data, n->val.string_lit.data, str_len); /* Create a stack space for the string slice */ i32 slice_off = reserve(g, n->type); return emit_slice_lit(g, slice_off, str_off, str_len, n->type); } static value_t gen_expr(gen_t *g, node_t *n, bool lvalue) { assert(n->type); value_t val = (value_t){ .type = n->type }; switch (n->cls) { case NODE_UNOP: return gen_unop(g, n, lvalue); case NODE_BINOP: return gen_binop(g, n); case NODE_BOOL: if (n->type->cls == TYPE_OPT) { value_t inner_val = (value_t){ .type = n->type->info.opt.elem, .loc = LOC_IMM, .as.imm.b = n->val.bool_lit, }; return optval_from_prim(g, n->type, inner_val); } else { val.loc = LOC_IMM; val.as.imm.b = n->val.bool_lit; } break; case NODE_STRING: return gen_string(g, n); case NODE_CHAR: if (n->type->cls == TYPE_OPT) { value_t inner_val = (value_t){ .type = n->type->info.opt.elem, .loc = LOC_IMM, .as.imm.u = (u8)n->val.char_lit, }; return optval_from_prim(g, n->type, inner_val); } else { val.loc = LOC_IMM; val.as.imm.u = (u8)n->val.char_lit; } break; case NODE_NUMBER: val.loc = LOC_IMM; switch (n->type->cls) { case TYPE_I8: case TYPE_I16: case TYPE_I32: val.as.imm.i = n->val.number.value.i; break; case TYPE_U8: case TYPE_U16: case TYPE_U32: val.as.imm.u = n->val.number.value.u; break; case TYPE_OPT: { /* Number coerced to optional - create some(number) on stack */ type_t *elem_type = n->type->info.opt.elem; value_t inner_val = (value_t){ .type = elem_type, .loc = LOC_IMM }; switch (elem_type->cls) { case TYPE_I8: case TYPE_I16: case TYPE_I32: inner_val.as.imm.i = n->val.number.value.i; break; case TYPE_U8: case TYPE_U16: case TYPE_U32: inner_val.as.imm.u = n->val.number.value.u; break; default: break; } return optval_from_prim(g, n->type, inner_val); } default: break; } break; case NODE_ACCESS: return gen_access(g, n, lvalue); case NODE_SCOPE: return gen_scope(g, n); case NODE_TRY: return gen_try(g, n); case NODE_IDENT: if (n->sym->kind == SYM_FUNCTION) { /* Function identifier used as a value (function pointer) */ return gen_fn_ptr(g, n->sym, n->type); } /* For types that are passed by reference and held in registers * (function parameters), dereference the pointer to get the data */ if ((type_is_passed_by_ref(n->type)) && n->sym->e.var.val.loc == LOC_REG) { return value_stack(OFFSET(n->sym->e.var.val.as.reg, 0), n->type); } return n->sym->e.var.val; case NODE_CALL: { /* Check if this is a tuple record constructor call */ if (!n->sym && n->type && n->type->cls == TYPE_RECORD && n->type->info.srt.tuple) { return gen_record_lit(g, n); } assert(n->sym); /* Check if this is a union constructor call */ if (n->sym->kind == SYM_VARIANT && type_is_union_with_payload(n->type)) { return gen_union_constructor(g, n); } /* Function pointer call */ if (n->sym->kind == SYM_VARIABLE) { return gen_call(g, n); } /* Regular function call */ if (n->sym->e.fn.attribs & ATTRIB_EXTERN) { /* Check if it's a built-in function. */ for (usize i = 0; BUILTINS[i].name; i++) { if (strcmp(n->sym->qualified, BUILTINS[i].name) == 0) { return gen_call_intrinsic(g, n, BUILTINS[i].gen); } } } return gen_call(g, n); } case NODE_CALL_ARG: /* Unreachable. This is handled inside `NODE_CALL`. */ case NODE_RECORD_LIT: if (type_is_union_with_payload(n->type)) { type_t *payload_type = n->sym->node->type; node_t payload_lit = *n; payload_lit.type = payload_type; payload_lit.sym = NULL; value_t payload = gen_record_lit(g, &payload_lit); return gen_union_store(g, n->type, n->sym, payload); } return gen_record_lit(g, n); case NODE_ARRAY_LIT: return gen_array_literal(g, n); case NODE_ARRAY_REPEAT_LIT: return gen_array_repeat(g, n); case NODE_ARRAY_INDEX: return gen_array_index(g, n, lvalue); case NODE_REF: return gen_ref(g, n); case NODE_NIL: { /* Allocate space for the optional value and initialize as nil */ i32 off = reserve(g, n->type); val = value_stack(OFFSET(FP, off), n->type); tval_store(g, val, (value_t){ 0 }, 0); return val; } case NODE_UNDEF: { i32 off = reserve(g, n->type); val = value_stack(OFFSET(FP, off), n->type); return val; } case NODE_AS: return gen_as_cast(g, n); case NODE_IF: if (n->type->cls != TYPE_VOID) { return gen_if_expr(g, n); } else { gen_if(g, n); return value_none(); } case NODE_BUILTIN: { builtin_kind_t kind = n->val.builtin.kind; node_t **args = nodespan_ptrs(&g->mod->parser, n->val.builtin.args); switch (kind) { case BUILTIN_SLICE_OF: { /* @sliceOf(ptr, len) - construct a slice from a pointer and length. * Slices are fat pointers: 4 bytes for ptr, 4 bytes for len. */ node_t *ptr_expr = args[0]; node_t *len_expr = args[1]; /* Generate code for pointer and length expressions */ value_t ptr_val = gen_expr(g, ptr_expr, false); value_t len_val = gen_expr(g, len_expr, false); /* Reserve stack space for the slice */ i32 off = reserve(g, n->type); val = value_stack(OFFSET(FP, off), n->type); /* Store pointer at offset+0, length at offset+WORD_SIZE */ emit_store(g, ptr_val, FP, off + SLICE_FIELD_PTR_OFFSET); /* Force len to be stored as a dword (WORD_SIZE bytes) */ static type_t dword = { .cls = TYPE_PTR }; len_val.type = &dword; emit_store(g, len_val, FP, off + SLICE_FIELD_LEN_OFFSET); return val; } case BUILTIN_SIZE_OF: case BUILTIN_ALIGN_OF: /* These are compile-time constants and should have been * folded during type checking. */ bail("@sizeOf/@alignOf should be folded at compile time"); } break; } default: bail("unsupported expression node type %s", node_names[n->cls]); } return val; } static void gen_fn_param(gen_t *g, node_t *param, usize ix) { node_t *fn = g->fn.current->node; type_t *ret = fn->type->info.fun.ret; bool sret = type_is_passed_by_ref(ret); reg_t base = sret ? A1 : A0; reg_t a = base + (reg_t)ix; /* We're going to simply track the register in which our parameter is * held, and mark it as in use. */ param->sym->e.var.val = value_reg(a, param->type); param->sym->e.var.val.temp = false; usereg(g, a); /* If the type was passed by reference, we need to copy it to avoid * modifying the original copy. */ if (type_is_passed_by_ref(param->type)) { param->sym->e.var.val = emit_push(g, param->sym->e.var.val); freereg(g, a); } /* Nb. If code takes the address of a parameter (`¶m`), that parameter * typically must be spilled to memory since registers don't have * addresses. */ } /* Detect literal initializers that reside in a dedicated temporary and * therefore can be bound directly without creating a defensive copy. */ static bool is_unaliased(node_t *init) { switch (init->cls) { case NODE_ARRAY_LIT: case NODE_ARRAY_REPEAT_LIT: case NODE_RECORD_LIT: case NODE_STRING: case NODE_NIL: case NODE_CALL: return true; default: /* Nb. all immediates return `false`, because they do not occupy a * stack location and therefore are not considered aliasable. */ return false; } } static void gen_var(gen_t *g, node_t *n) { node_t *lval = n->val.var.ident; node_t *rval = n->val.var.value; /* For placeholders, just evaluate the rvalue for side effects */ if (lval->cls == NODE_PLACEHOLDER) { if (rval->cls != NODE_UNDEF) { gen_expr(g, rval, false); } return; } i32 align = n->sym->e.var.align; if (rval->cls == NODE_UNDEF) { i32 offset = reserve_aligned(g, n->type, align); n->sym->e.var.val = value_stack(OFFSET(FP, offset), n->type); return; } value_t val = gen_expr(g, rval, false); bool reuse = align <= n->type->align && val.loc == LOC_STACK && is_unaliased(rval); if (reuse) { n->sym->e.var.val = val; return; } i32 offset = reserve_aligned(g, n->type, align); value_t dest = value_stack(OFFSET(FP, offset), n->type); n->sym->e.var.val = dest; emit_replace(g, dest, val); } static void gen_const(gen_t *g, node_t *n) { /* Don't re-generate if it already has a location. */ if (n->sym->e.var.val.loc != LOC_NONE) return; node_t *value = n->val.constant.value; const char *name = n->sym->qualified; usize name_len = strlen(name); usize addr = data_node(&g->data, &g->mod->parser, value, name, name_len); /* Store the constant address in the symbol table */ n->sym->e.var.val = value_addr(addr, 0, n->type); n->sym->e.var.align = n->type->align; } static void gen_static(gen_t *g, node_t *n) { /* Don't re-generate if it already has a location. */ if (n->sym->e.var.val.loc != LOC_NONE) return; node_t *value = n->val.static_decl.value; const char *name = n->sym->qualified; usize name_len = strlen(n->sym->qualified); usize addr = data_node(&g->data, &g->mod->parser, value, name, name_len); n->sym->e.var.val = value_addr(addr, 0, n->type); n->sym->e.var.align = n->type->align; } /* Generate code for a block of code. */ static void gen_block(gen_t *g, node_t *n) { frame_t *frame = &g->fn.current->e.fn.frame; /* Record the stack pointer before entering the block * to restore it when exiting. */ i32 sp = frame->sp; /* Generate code for each statement in the block. */ node_t **stmts = nodespan_ptrs(&g->mod->parser, n->val.block.stmts); for (usize i = 0; i < n->val.block.stmts.len; i++) { gen_node(g, stmts[i]); } if (-frame->sp > frame->size) { /* Keep track of the maximum stack space used. */ frame->size = -frame->sp; } /* De-allocate stack space. */ frame->sp = sp; } /* Generate code for a function. */ static void gen_fn(gen_t *g, node_t *n) { /* Skip unused functions (dead code elimination) */ if (!n->sym->e.fn.used) { return; } /* Check if this is an extern function */ if (n->sym->e.fn.attribs & ATTRIB_EXTERN) { /* For extern functions, we don't generate any code since they are * implemented externally or are built-ins. */ return; } /* Check if it's a test function, and skip if not in test mode. */ if (n->sym->e.fn.attribs & ATTRIB_TEST && !(g->flags & FLAG_TEST)) { return; } type_t *ret = n->type->info.fun.ret; bool sret = type_is_passed_by_ref(ret); /* For types that are returned by reference, keep hidden return pointer * alive */ if (sret) { usereg(g, A0); } /* Set current function. */ g->fn.current = n->sym; g->fn.nretpatches = 0; symbol_t *sym = n->sym; /* Store the current instruction address as the function's address. */ sym->e.fn.addr = g->ninstrs; node_t *body = n->val.fn_decl.body; /* Functions should have non-zero address, unless it's the default */ frame_t *f = &sym->e.fn.frame; /* Offsets for RA and previous FP. */ const i32 fp_off = -WORD_SIZE - WORD_SIZE; f->sp = fp_off; f->size = 0; /* Will be patched once we know the frame size. */ /* Function prologue. Track prologue address for patching. */ usize prologue = sym->e.fn.addr; /* Generate placeholder instructions that will be patched at the end. */ /* This is the maximum prologue size, if we need to create a big * stack frame. */ emit(g, NOP); emit(g, NOP); emit(g, NOP); emit(g, NOP); emit(g, NOP); emit(g, NOP); emit(g, NOP); /* Reserve all argument registers up-front so they are not used as * temporaries while we spill each parameter. */ reg_t param_base = sret ? A1 : A0; for (usize i = 0; i < n->val.fn_decl.params.len; i++) { reg_t a = param_base + (reg_t)i; if (a > A7) { bail( "function '%s' parameter %zu exceeds available register " "arguments", g->fn.current->qualified, i + 1 ); } usereg(g, a); } /* * Save parameters on the stack. */ for (usize i = 0; i < n->val.fn_decl.params.len; i++) { gen_fn_param(g, SPAN(g, n->val.fn_decl.params)[i], i); } /* * Generate body. */ gen_block(g, body); /* Ensure fallible functions that reach the end * implicitly return success. */ if (ret->cls == TYPE_RESULT) { if (!ret->info.res.payload->size) { value_t dest; if (type_is_passed_by_ref(ret)) { usereg(g, A0); dest = value_stack(OFFSET(A0, 0), ret); } else { dest = value_reg(A0, ret); } emit_result_store_success(g, dest, value_none()); if (!type_is_passed_by_ref(ret)) { freereg(g, A0); } } } /* Align the frame size according to the RISCV ABI. */ f->size = align(f->size, STACK_ALIGNMENT); instr_t *ins = &g->instrs[prologue]; usize slot = 0; const i32 locals = f->size - WORD_SIZE * 2; ins[slot++] = ADDI(SP, SP, -WORD_SIZE * 2); ins[slot++] = SD(FP, SP, 0); ins[slot++] = SD(RA, SP, WORD_SIZE); ins[slot++] = ADDI(FP, SP, WORD_SIZE * 2); if (locals != 0) { if (is_small(-locals)) { ins[slot++] = ADDI(SP, SP, -locals); } else { i32 hi = 0, lo = 0; split_imm(locals, &hi, &lo); ins[slot++] = LUI(T0, hi); if (lo != 0) ins[slot++] = ADDI(T0, T0, lo); ins[slot++] = SUB(SP, SP, T0); } } while (slot < 7) ins[slot++] = NOP; /* Mark the epilogue position and patch all return statements * to jump to this epilogue. */ usize epilogue = g->ninstrs; for (usize i = 0; i < g->fn.nretpatches; i++) { ctpatch_t *p = &g->fn.retpatches[i]; if (!p->applied) { /* Calculate jump offset to the epilogue. */ i32 offset = jump_offset(p->pc, epilogue); /* A word-size offset basically means jumping to the next * instruction, which is a redundant. We leave it as a NOP in * that case. */ if (offset != INSTR_SIZE) { /* Update the jump instruction with the correct offset. */ g->instrs[p->pc] = JMP(offset); } p->applied = true; } } /* * Function epilogue. */ if (locals != 0) { if (is_small(locals)) { emit(g, ADDI(SP, SP, locals)); } else { emit_li(g, T0, locals); emit(g, ADD(SP, SP, T0)); } } emit(g, LD(FP, SP, 0)); emit(g, LD(RA, SP, WORD_SIZE)); emit(g, ADDI(SP, SP, WORD_SIZE * 2)); emit(g, RET); /* Release parameter and temporary registers */ for (reg_t r = A0; r <= A7; r++) freereg(g, r); for (usize i = 0; i < sizeof(temp_registers) / sizeof(reg_t); i++) freereg(g, temp_registers[i]); /* Patch function call locations. */ for (usize i = 0; i < g->nfnpatches; i++) { fnpatch_t *p = &g->fnpatches[i]; if (!p->applied && strcmp(p->fn_name, sym->qualified) == 0) { if (p->patch_type == PATCH_CALL) { i32 offset = jump_offset(p->pc, sym->e.fn.addr); if (is_jump_imm(offset)) { g->instrs[p->pc] = JAL(RA, offset); if (p->tramp_pc != (usize)-1) g->instrs[p->tramp_pc] = NOP; } else { i32 target_addr = (i32)(sym->e.fn.addr * INSTR_SIZE); i32 current_addr = (i32)(p->pc * INSTR_SIZE); i32 rel = target_addr - current_addr; i32 hi, lo; split_imm(rel, &hi, &lo); reg_t scratch = p->scratch_reg ? p->scratch_reg : T0; g->instrs[p->pc] = AUIPC(scratch, hi); g->instrs[p->tramp_pc] = JALR(RA, scratch, lo); } } else if (p->patch_type == PATCH_ADDRESS) { /* For function address patches, replace the NOPs with AUIPC + * ADDI for PC-relative addressing. Calculate target - * current_pc. */ i32 target_addr = sym->e.fn.addr * INSTR_SIZE; i32 current_addr = p->pc * INSTR_SIZE; i32 offset = target_addr - current_addr; /* Split offset into upper 20 bits and lower 12 bits */ i32 hi, lo; split_imm(offset, &hi, &lo); /* Emit AUIPC + ADDI sequence */ g->instrs[p->pc] = AUIPC(p->target_reg, hi); g->instrs[p->tramp_pc] = ADDI(p->target_reg, p->target_reg, lo); } /* Mark as applied so we don't patch it again. */ p->applied = true; } } } /* Generate code for a module. */ static void gen_module(gen_t *g, module_t *m) { node_t *n = m->ast; if (m->compiled) return; /* Set the current module for span access */ module_t *prev_mod = g->mod; g->mod = m; /* Don't compile test modules unless we are in test mode. */ if (m->attribs & ATTRIB_TEST && !(g->flags & FLAG_TEST)) { g->mod = prev_mod; return; } /* Generate all constants to ensure they're available */ node_t **stmts_const = nodespan_ptrs(&m->parser, n->val.block.stmts); for (usize i = 0; i < n->val.block.stmts.len; i++) { node_t *stmt = stmts_const[i]; if (stmt->cls == NODE_CONST) { gen_const(g, stmt); } else if (stmt->cls == NODE_STATIC) { gen_static(g, stmt); } } /* Generate code for module entry point. */ /* Must be at address _zero_ of the module. */ if (n->sym->e.mod->default_fn) { gen_fn(g, n->sym->e.mod->default_fn->node); } /* Generate all declared modules */ node_t **stmts_sub = nodespan_ptrs(&m->parser, n->val.block.stmts); for (usize i = 0; i < n->val.block.stmts.len; i++) { node_t *stmt = stmts_sub[i]; if (stmt->cls == NODE_MOD) { gen_mod(g, stmt); } if (stmt->cls == NODE_USE) { gen_use(g, stmt); } } /* Generate code for everything else. */ node_t **stmts = nodespan_ptrs(&m->parser, n->val.block.stmts); for (usize i = 0; i < n->val.block.stmts.len; i++) { node_t *stmt = stmts[i]; if (stmt->cls == NODE_CONST) continue; if (stmt->cls == NODE_FN && stmt->sym->e.fn.attribs & ATTRIB_DEFAULT) continue; gen_node(g, stmt); } m->compiled = true; g->mod = prev_mod; } /* Generate code for a module declaration. */ static void gen_mod(gen_t *g, node_t *n) { if (!n->sym) { /* Skip modules that aren't loaded like test modules. */ return; } module_t *mod = n->sym->e.mod; gen_module(g, mod); } /* Generate code for a use declaration. */ /* For function/variable imports, this generates the parent module. */ static void gen_use(gen_t *g, node_t *n) { /* For wildcard re-exports, n->sym is NULL since we're not binding * the module itself, just re-exporting its symbols. */ if (!n->sym) return; module_t *mod = n->sym->scope->mod; gen_module(g, mod); } /* Generating nothing. This is used eg. for type declaration nodes * which don't have any associated code. */ static void gen_nop(gen_t *g, node_t *n) { (void)g; (void)n; } /* Generate code from AST. */ /* Pre-size the initialized data region by summing the aligned sizes of all * initialized (non-BSS) constants and statics across every module. */ static void data_presize(gen_t *g) { usize total = 0; for (usize m = 0; m < g->mm->nmodules; m++) { module_t *mod = &g->mm->modules[m]; if (!mod->ast) continue; if (mod->attribs & ATTRIB_TEST && !(g->flags & FLAG_TEST)) continue; node_t *n = mod->ast; node_t **stmts = nodespan_ptrs(&mod->parser, n->val.block.stmts); for (usize i = 0; i < n->val.block.stmts.len; i++) { node_t *stmt = stmts[i]; node_t *value = NULL; if (stmt->cls == NODE_CONST) value = stmt->val.constant.value; else if (stmt->cls == NODE_STATIC) value = stmt->val.static_decl.value; else continue; if (value->cls == NODE_UNDEF) continue; total = align(total, WORD_SIZE); total += stmt->type->size; } } g->data.rw_init_total = align(total, WORD_SIZE); } int gen_emit(gen_t *g, module_t *root) { /* Pre-size the initialized data region so that BSS items are placed * after all initialized data in the rw section. */ data_presize(g); /* Generate root module. This has to have address zero, as it has * the entry point. */ gen_module(g, root); /* Generate `std` module if available. */ module_t *std = module_manager_lookup_by_qualified_name(g->mm, "std"); if (std) { gen_module(g, std); } /* Check that all patches have been applied. */ for (usize i = 0; i < g->nfnpatches; i++) { if (!g->fnpatches[i].applied) bail( "jump for function '%s' was not patched", g->fnpatches[i].fn_name ); } /* Check that all return patches have been applied. */ for (usize i = 0; i < g->fn.nretpatches; i++) { if (!g->fn.retpatches[i].applied) bail("return statement was not properly patched"); } /* Check that all break patches have been applied. */ for (usize i = 0; i < g->fn.nbrkpatches; i++) { if (!g->fn.brkpatches[i].applied) bail("break statement was not properly patched"); } /* Keep root module reference for data emission. */ g->mod = root; return 0; } static value_t gen_as_cast(gen_t *g, node_t *n) { node_t *expr = n->val.as_expr.expr; value_t val = gen_expr(g, expr, false); /* If casting to the same type, no conversion needed */ if (val.type == n->type) return val; /* For casts between different primitive types, we need to handle * size changes properly (e.g., u8 -> i32 requires zero extension) */ /* If the types are the same size, just change the type metadata */ if (val.type->size == n->type->size) { val.type = n->type; return val; } /* For size changes, we need to properly load and re-store the value * to ensure correct zero/sign extension */ if (val.loc == LOC_STACK) { /* Load the value using the source type (proper sized load) */ reg_t rd = emit_load(g, val); /* Push to stack using the target type (proper sized store) */ i32 offset = emit_regpush(g, rd, n->type); freereg(g, rd); return value_stack(OFFSET(FP, offset), n->type); } /* For non-stack values (registers, immediates), just change the * type */ val.type = n->type; return val; } void gen_dump_bin(gen_t *g, FILE *text, FILE *data_ro, FILE *data_rw) { /* Write instructions */ fwrite(g->instrs, sizeof(u32), g->ninstrs, text); /* Write data */ data_emit_rw(&g->data, data_rw); data_emit_ro(&g->data, data_ro); fflush(text); fflush(data_ro); fflush(data_rw); } /* Initialize a `gen` object. */ void gen_init(gen_t *g, types_t *t, module_manager_t *mm, u32 flags) { g->ninstrs = 0; g->nfnpatches = 0; g->fn.current = NULL; g->fn.nretpatches = 0; g->fn.nbrkpatches = 0; g->regs = ralloc(); g->types = t; g->loop.current = NULL; g->loop.end = 0; g->mm = mm; g->flags = flags; /* Initialize data section */ data_init(&g->data); }