feat: added Ir.Inst.Ref, global constant pool, lazy lowering in Sema

This commit is contained in:
Brett Broadhurst 2026-03-11 20:15:52 -06:00
parent ce5385ebac
commit e5e2b7c559
Failed to generate hash of commit
6 changed files with 615 additions and 534 deletions

View file

@ -105,7 +105,8 @@ const GenIr = struct {
) []Ir.Inst.Index {
return if (self.instructions_top == unstacked_top)
&[0]Ir.Inst.Index{}
else if (self.instructions == stacked_block.instructions and stacked_block.instructions_top != unstacked_top)
else if (self.instructions == stacked_block.instructions and
stacked_block.instructions_top != unstacked_top)
self.instructions.items[self.instructions_top..stacked_block.instructions_top]
else
self.instructions.items[self.instructions_top..];
@ -127,24 +128,28 @@ const GenIr = struct {
};
}
fn add(gi: *GenIr, inst: Ir.Inst) !Ir.Inst.Index {
fn add(gi: *GenIr, inst: Ir.Inst) !Ir.Inst.Ref {
return (try gi.addAsIndex(inst)).toRef();
}
fn addAsIndex(gi: *GenIr, inst: Ir.Inst) !Ir.Inst.Index {
const gpa = gi.astgen.gpa;
try gi.instructions.ensureUnusedCapacity(gpa, 1);
try gi.astgen.instructions.ensureUnusedCapacity(gpa, 1);
const inst_index: Ir.Inst.Index = @enumFromInt(gi.astgen.instructions.items.len);
const new_index: Ir.Inst.Index = @enumFromInt(gi.astgen.instructions.items.len);
gi.astgen.instructions.appendAssumeCapacity(inst);
gi.instructions.appendAssumeCapacity(inst_index);
return inst_index;
gi.instructions.appendAssumeCapacity(new_index);
return new_index;
}
fn addInt(gi: *GenIr, value: u64) !Ir.Inst.Index {
fn addInt(gi: *GenIr, value: u64) !Ir.Inst.Ref {
return add(gi, .{ .tag = .integer, .data = .{
.integer = .{ .value = value },
} });
}
fn addUnaryNode(gi: *GenIr, tag: Ir.Inst.Tag, arg: Ir.Inst.Index) !Ir.Inst.Index {
fn addUnaryNode(gi: *GenIr, tag: Ir.Inst.Tag, arg: Ir.Inst.Ref) !Ir.Inst.Ref {
return add(gi, .{ .tag = tag, .data = .{
.un = .{ .lhs = arg },
} });
@ -153,15 +158,15 @@ const GenIr = struct {
fn addBinaryNode(
gi: *GenIr,
tag: Ir.Inst.Tag,
lhs: Ir.Inst.Index,
rhs: Ir.Inst.Index,
) !Ir.Inst.Index {
lhs: Ir.Inst.Ref,
rhs: Ir.Inst.Ref,
) !Ir.Inst.Ref {
return add(gi, .{ .tag = tag, .data = .{
.bin = .{ .lhs = lhs, .rhs = rhs },
} });
}
fn addDeclRef(gi: *GenIr, decl_ref: Ir.NullTerminatedString) !Ir.Inst.Index {
fn addDeclRef(gi: *GenIr, decl_ref: Ir.NullTerminatedString) !Ir.Inst.Ref {
return add(gi, .{ .tag = .decl_ref, .data = .{
.string = .{
.start = decl_ref,
@ -185,8 +190,16 @@ const GenIr = struct {
return makePayloadNode(gi, .declaration);
}
fn makeBlockInst(gi: *GenIr) !Ir.Inst.Index {
return makePayloadNode(gi, .block);
fn makeBlockInst(gi: *GenIr, tag: Ir.Inst.Tag) !Ir.Inst.Index {
const inst_index: Ir.Inst.Index = @enumFromInt(gi.astgen.instructions.items.len);
const gpa = gi.astgen.gpa;
try gi.astgen.instructions.append(gpa, .{
.tag = tag,
.data = .{
.payload = .{ .payload_index = undefined },
},
});
return inst_index;
}
fn addKnot(self: *GenIr) !Ir.Inst.Index {
@ -331,7 +344,7 @@ fn setDeclaration(
fn setCondBrPayload(
condbr: Ir.Inst.Index,
cond: Ir.Inst.Index,
cond: Ir.Inst.Ref,
then_block: *GenIr,
else_block: *GenIr,
) !void {
@ -374,6 +387,7 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
astgen.extra.items[i] = switch (field.type) {
u32 => @field(extra, field.name),
Ir.Inst.Index => @intFromEnum(@field(extra, field.name)),
Ir.Inst.Ref => @intFromEnum(@field(extra, field.name)),
Ir.NullTerminatedString => @intFromEnum(@field(extra, field.name)),
else => @compileError("bad field type"),
};
@ -444,9 +458,9 @@ fn unaryOp(
scope: *Scope,
expr_node: *const Ast.Node,
op: Ir.Inst.Tag,
) InnerError!Ir.Inst.Index {
assert(expr_node.data.bin.lhs != null);
const lhs = try expr(gi, scope, expr_node.data.bin.lhs orelse unreachable);
) InnerError!Ir.Inst.Ref {
const data = expr_node.data.bin;
const lhs = try expr(gi, scope, data.lhs.?);
return gi.addUnaryNode(op, lhs);
}
@ -455,12 +469,11 @@ fn binaryOp(
scope: *Scope,
expr_node: *const Ast.Node,
op: Ir.Inst.Tag,
) InnerError!Ir.Inst.Index {
) InnerError!Ir.Inst.Ref {
const data = expr_node.data.bin;
assert(data.lhs != null and data.rhs != null);
const lhs = try expr(gi, scope, data.lhs orelse unreachable);
const rhs = try expr(gi, scope, data.rhs orelse unreachable);
const lhs = try expr(gi, scope, data.lhs.?);
const rhs = try expr(gi, scope, data.rhs.?);
return gi.addBinaryNode(op, lhs, rhs);
}
@ -490,23 +503,14 @@ fn logicalOp(
gen.setLabel(else_label);
}
fn trueLiteral(gi: *GenIr) InnerError!Ir.Inst.Index {
return gi.add(.{ .tag = .true_literal, .data = undefined });
}
fn falseLiteral(gi: *GenIr) InnerError!Ir.Inst.Index {
return gi.add(.{ .tag = .false_literal, .data = undefined });
}
fn numberLiteral(gen: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Index {
fn numberLiteral(gen: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
const lexeme = sliceFromNode(gen.astgen, node);
const int_value = try std.fmt.parseUnsigned(u64, lexeme, 10);
return gen.addInt(int_value);
}
fn stringLiteral(gi: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Index {
const astgen = gi.astgen;
const str = try astgen.stringFromNode(node);
fn stringLiteral(gi: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
const str = try gi.astgen.stringFromNode(node);
return gi.add(.{
.tag = .string,
.data = .{ .string = .{
@ -515,47 +519,46 @@ fn stringLiteral(gi: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Index {
});
}
fn stringExpr(gen: *GenIr, expr_node: *const Ast.Node) InnerError!Ir.Inst.Index {
assert(expr_node.data.bin.lhs != null);
const first_node = expr_node.data.bin.lhs orelse unreachable;
fn stringExpr(gen: *GenIr, expr_node: *const Ast.Node) InnerError!Ir.Inst.Ref {
const first_node = expr_node.data.bin.lhs.?;
return stringLiteral(gen, first_node);
}
fn identifier(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Index {
fn identifier(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
const astgen = gi.astgen;
const str = try astgen.stringFromNode(node);
if (scope.lookup(str)) |decl| {
return gi.addUnaryNode(.load_local, decl.inst_index);
return gi.addUnaryNode(.load, decl.inst_index.toRef());
}
return gi.addDeclRef(str);
}
fn expr(block: *GenIr, scope: *Scope, optional_expr: ?*const Ast.Node) InnerError!Ir.Inst.Index {
const expr_node = optional_expr orelse unreachable;
fn expr(gi: *GenIr, scope: *Scope, optional_expr: ?*const Ast.Node) InnerError!Ir.Inst.Ref {
const expr_node = optional_expr.?;
switch (expr_node.tag) {
.file => unreachable,
.true_literal => return trueLiteral(block),
.false_literal => return falseLiteral(block),
.number_literal => return numberLiteral(block, expr_node),
.string_literal => return stringLiteral(block, expr_node),
.string_expr => return stringExpr(block, expr_node),
.empty_string => return stringLiteral(block, expr_node),
.identifier => return identifier(block, scope, expr_node),
.add_expr => return binaryOp(block, scope, expr_node, .add),
.subtract_expr => return binaryOp(block, scope, expr_node, .sub),
.multiply_expr => return binaryOp(block, scope, expr_node, .mul),
.divide_expr => return binaryOp(block, scope, expr_node, .div),
.mod_expr => return binaryOp(block, scope, expr_node, .mod),
.negate_expr => return unaryOp(block, scope, expr_node, .neg),
.true_literal => return .bool_true,
.false_literal => return .bool_false,
.number_literal => return numberLiteral(gi, expr_node),
.string_literal => return stringLiteral(gi, expr_node),
.string_expr => return stringExpr(gi, expr_node),
.empty_string => return stringLiteral(gi, expr_node),
.identifier => return identifier(gi, scope, expr_node),
.add_expr => return binaryOp(gi, scope, expr_node, .add),
.subtract_expr => return binaryOp(gi, scope, expr_node, .sub),
.multiply_expr => return binaryOp(gi, scope, expr_node, .mul),
.divide_expr => return binaryOp(gi, scope, expr_node, .div),
.mod_expr => return binaryOp(gi, scope, expr_node, .mod),
.negate_expr => return unaryOp(gi, scope, expr_node, .neg),
.logical_and_expr => unreachable,
.logical_or_expr => unreachable,
.logical_not_expr => return unaryOp(block, scope, expr_node, .not),
.logical_equality_expr => return binaryOp(block, scope, expr_node, .cmp_eq),
.logical_inequality_expr => return binaryOp(block, scope, expr_node, .cmp_neq),
.logical_greater_expr => return binaryOp(block, scope, expr_node, .cmp_gt),
.logical_greater_or_equal_expr => return binaryOp(block, scope, expr_node, .cmp_gte),
.logical_lesser_expr => return binaryOp(block, scope, expr_node, .cmp_lt),
.logical_lesser_or_equal_expr => return binaryOp(block, scope, expr_node, .cmp_lte),
.logical_not_expr => return unaryOp(gi, scope, expr_node, .not),
.logical_equality_expr => return binaryOp(gi, scope, expr_node, .cmp_eq),
.logical_inequality_expr => return binaryOp(gi, scope, expr_node, .cmp_neq),
.logical_greater_expr => return binaryOp(gi, scope, expr_node, .cmp_gt),
.logical_greater_or_equal_expr => return binaryOp(gi, scope, expr_node, .cmp_gte),
.logical_lesser_expr => return binaryOp(gi, scope, expr_node, .cmp_lt),
.logical_lesser_or_equal_expr => return binaryOp(gi, scope, expr_node, .cmp_lte),
.call_expr => unreachable,
.choice_expr => unreachable,
.choice_start_expr => unreachable,
@ -588,22 +591,28 @@ fn expr(block: *GenIr, scope: *Scope, optional_expr: ?*const Ast.Node) InnerErro
.ref_parameter_decl => unreachable,
.argument_list => unreachable,
.parameter_list => unreachable,
.switch_stmt => unreachable,
.switch_case => unreachable,
.if_stmt => unreachable,
.multi_if_stmt => unreachable,
.if_branch => unreachable,
.else_branch => unreachable,
.switch_stmt => unreachable, // Handled in switchStmt
.switch_case => unreachable, // Handled in switchStmt
.if_stmt => unreachable, // Handled in ifStmt
.multi_if_stmt => unreachable, // Handled in multiIfStmt
.if_branch => unreachable, // Handled in ifStmt and multiIfStmt
.else_branch => unreachable, // Handled in switchStmt, multiIfStmt, and ifStmt
.content => unreachable,
.inline_logic_expr => unreachable,
.invalid => unreachable,
}
}
fn exprStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!Ir.Inst.Index {
fn exprStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
// TODO: Maybe we should introduce a unary node type to avoid optional checks?
const expr_node = stmt_node.data.bin.lhs.?;
return expr(gen, scope, expr_node);
const expr_node = node.data.bin.lhs.?;
return expr(gi, scope, expr_node);
}
fn inlineLogicExpr(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
// TODO: Maybe we should introduce a unary node type to avoid optional checks?
const main_node = node.data.bin.lhs.?;
return expr(gi, scope, main_node);
}
fn validateSwitchProngs(gen: *GenIr, stmt_node: *const Ast.Node) InnerError!void {
@ -633,96 +642,11 @@ fn validateSwitchProngs(gen: *GenIr, stmt_node: *const Ast.Node) InnerError!void
}
}
fn switchStmt(
gen: *GenIr,
parent_scope: *Scope,
stmt_node: *const Ast.Node,
) InnerError!void {
const gpa = gen.astgen.gpa;
var child_scope = try gen.astgen.createScope(parent_scope);
defer child_scope.deinit();
const label_index = try gen.makeLabel();
gen.setExit(label_index);
const eval_expr = stmt_node.data.switch_stmt.condition_expr;
const case_list = stmt_node.data.switch_stmt.cases;
// NOTE: We're going to create an array of label indexes here, since we
// may create additional labels while traversing nested expressions.
var label_list: std.ArrayList(usize) = .empty;
defer label_list.deinit(gpa);
try label_list.ensureUnusedCapacity(gpa, case_list.len);
const stack_slot = try gen.makeStackSlot();
try expr(gen, child_scope, eval_expr);
try gen.emitConstInst(.store, stack_slot);
try gen.emitSimpleInst(.pop);
for (case_list) |case_stmt| {
const case_label_index = try gen.makeLabel();
label_list.appendAssumeCapacity(case_label_index);
switch (case_stmt.tag) {
.switch_case => {
const case_eval_expr = case_stmt.data.bin.lhs orelse unreachable;
switch (case_eval_expr.tag) {
.number_literal, .true_literal, .false_literal => {},
else => return gen.fail(.invalid_switch_case, case_stmt),
}
try gen.emitConstInst(.load, stack_slot);
try expr(gen, child_scope, case_eval_expr);
try gen.emitSimpleInst(.cmp_eq);
const fixup_offset = try gen.emitJumpInst(.jmp_t);
_ = try gen.makeFixup(.{
.mode = .relative,
.label_index = case_label_index,
.code_offset = fixup_offset,
});
try gen.emitSimpleInst(.pop);
},
.else_branch => {
const fixup_offset = try gen.emitJumpInst(.jmp);
_ = try gen.makeFixup(.{
.mode = .relative,
.label_index = case_label_index,
.code_offset = fixup_offset,
});
},
else => unreachable,
}
}
for (case_list, label_list.items) |case_stmt, case_label_index| {
gen.setLabel(case_label_index);
switch (case_stmt.tag) {
.switch_case => {
try gen.emitSimpleInst(.pop);
},
.else_branch => {},
else => unreachable,
}
const block_stmt = case_stmt.data.bin.rhs;
try blockStmt(gen, child_scope, block_stmt);
const fixup_offset = try gen.emitJumpInst(.jmp);
_ = try gen.makeFixup(.{
.mode = .relative,
.label_index = gen.exit_label,
.code_offset = fixup_offset,
});
}
gen.setLabel(gen.exit_label);
}
fn ifStmt(
parent_block: *GenIr,
scope: *Scope,
stmt_node: *const Ast.Node,
) InnerError!Ir.Inst.Index {
) InnerError!Ir.Inst.Ref {
const astgen = parent_block.astgen;
const cond_expr = stmt_node.data.switch_stmt.condition_expr.?;
try validateSwitchProngs(parent_block, stmt_node);
@ -736,7 +660,7 @@ fn ifStmt(
const cond_inst = try expr(&block_scope, scope, cond_expr);
const condbr = try block_scope.addCondBr(.condbr);
const block = try parent_block.makeBlockInst();
const block = try parent_block.makeBlockInst(.block);
try block_scope.setBlockBody(block); // unstacks block
try parent_block.instructions.append(astgen.gpa, block);
@ -757,14 +681,14 @@ fn ifStmt(
}
try setCondBrPayload(condbr, cond_inst, &then_block, &else_block);
return @enumFromInt(0);
return condbr.toRef();
}
fn ifChain(
parent_block: *GenIr,
scope: *Scope,
branch_list: []const *Ast.Node,
) InnerError!Ir.Inst.Index {
) InnerError!Ir.Inst.Ref {
const gpa = parent_block.astgen.gpa;
if (branch_list.len == 0) return @enumFromInt(0);
if (branch_list[0].data.bin.lhs == null) {
@ -781,7 +705,7 @@ fn ifChain(
const body_node = branch.data.bin.rhs.?;
const cond_inst = try expr(&block_scope, scope, cond_expr);
const condbr = try block_scope.addCondBr(.condbr);
const block_inst = try parent_block.makeBlockInst();
const block_inst = try parent_block.makeBlockInst(.block);
try block_scope.setBlockBody(block_inst);
try parent_block.instructions.append(gpa, block_inst);
@ -803,7 +727,7 @@ fn multiIfStmt(
parent_block: *GenIr,
scope: *Scope,
stmt_node: *const Ast.Node,
) InnerError!Ir.Inst.Index {
) InnerError!Ir.Inst.Ref {
try validateSwitchProngs(parent_block, stmt_node);
const branch_list = stmt_node.data.switch_stmt.cases;
@ -817,19 +741,9 @@ fn multiIfStmt(
return @enumFromInt(0);
}
fn inlineLogicExpr(
gen: *GenIr,
scope: *Scope,
expr_node: *const Ast.Node,
) InnerError!Ir.Inst.Index {
const main_node = expr_node.data.bin.lhs;
assert(main_node != null);
return expr(gen, scope, main_node);
}
fn contentExpr(block: *GenIr, scope: *Scope, expr_node: *const Ast.Node) InnerError!Ir.Inst.Index {
fn contentExpr(block: *GenIr, scope: *Scope, expr_node: *const Ast.Node) InnerError!Ir.Inst.Ref {
// FIXME: This is a placeholder until we figure out what this function should be returning.
var last_inst: Ir.Inst.Index = undefined;
var last_inst: Ir.Inst.Ref = undefined;
// TODO: Make sure that this is not nullable.
const node_list = expr_node.data.list.items.?;
for (node_list) |child_node| {
@ -838,29 +752,30 @@ fn contentExpr(block: *GenIr, scope: *Scope, expr_node: *const Ast.Node) InnerEr
.inline_logic_expr => try inlineLogicExpr(block, scope, child_node),
.if_stmt => try ifStmt(block, scope, child_node),
.multi_if_stmt => try multiIfStmt(block, scope, child_node),
//.switch_stmt => try switchStmt(gen, scope, child_node),
//.switch_stmt => try switchStmt(block, scope, child_node),
else => unreachable,
};
last_inst = try block.add(.{ .tag = .content_push, .data = undefined });
last_inst = try block.addUnaryNode(.content_push, last_inst);
}
return last_inst;
}
fn contentStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!Ir.Inst.Index {
const expr_node = stmt_node.data.bin.lhs orelse unreachable;
fn contentStmt(gen: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
const expr_node = node.data.bin.lhs.?;
const expr_ref = try contentExpr(gen, scope, expr_node);
return gen.addUnaryNode(.content_flush, expr_ref);
}
fn assignStmt(gi: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void {
fn assignStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void {
const astgen = gi.astgen;
const identifier_node = stmt_node.data.bin.lhs orelse unreachable;
const expr_node = stmt_node.data.bin.rhs orelse unreachable;
const identifier_node = node.data.bin.lhs.?;
const expr_node = node.data.bin.rhs.?;
const name_ref = try astgen.stringFromNode(identifier_node);
// TODO: Support globals as well
if (scope.lookup(name_ref)) |decl| {
const expr_result = try expr(gi, scope, expr_node);
_ = try gi.addBinaryNode(.store_local, decl.inst_index, expr_result);
_ = try gi.addBinaryNode(.store, decl.inst_index.toRef(), expr_result);
return;
}
return gi.fail(.unknown_identifier, identifier_node);
@ -949,13 +864,13 @@ fn tempDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
return gi.fail(.redefined_identifier, decl_node);
}
const alloc_inst = try gi.add(.{ .tag = .alloc_local, .data = undefined });
const alloc_inst = try gi.add(.{ .tag = .alloc, .data = undefined });
const expr_result = try expr(gi, scope, expr_node);
_ = try gi.addBinaryNode(.store_local, alloc_inst, expr_result);
_ = try gi.addBinaryNode(.store, alloc_inst, expr_result);
return scope.insert(name_ref, .{
.decl_node = decl_node,
.inst_index = alloc_inst,
.inst_index = alloc_inst.toIndex().?,
});
}