From f16162b5bb0719c4e1985540e740128cfc028742 Mon Sep 17 00:00:00 2001 From: Brett Broadhurst Date: Sun, 8 Mar 2026 14:54:28 -0600 Subject: [PATCH] refactor: boilerplate for semantic ir --- src/AstGen.zig | 822 ++++++++++++++++++++----------------------------- src/Ir.zig | 264 ++++++++++++++++ 2 files changed, 604 insertions(+), 482 deletions(-) create mode 100644 src/Ir.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index a10e55e..2171aea 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1,5 +1,6 @@ const std = @import("std"); const Ast = @import("Ast.zig"); +const Ir = @import("Ir.zig"); const Story = @import("Story.zig"); const StringIndexAdapter = std.hash_map.StringIndexAdapter; const StringIndexContext = std.hash_map.StringIndexContext; @@ -7,17 +8,11 @@ const assert = std.debug.assert; const AstGen = @This(); gpa: std.mem.Allocator, -arena: std.mem.Allocator, tree: *const Ast, string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.default_max_load_percentage) = .empty, -// TODO: Output string_bytes. -// NOTE: String bytes have a 4GiB limit. string_bytes: std.ArrayListUnmanaged(u8) = .empty, -instructions: std.ArrayListUnmanaged(u8) = .empty, -label_stack: std.ArrayListUnmanaged(Label) = .empty, -fixup_stack: std.ArrayListUnmanaged(Fixup) = .empty, -constants: std.ArrayListUnmanaged(CodeUnit.Constant) = .empty, -knots: std.ArrayListUnmanaged(CodeUnit.Knot) = .empty, +instructions: std.ArrayListUnmanaged(Ir.Inst) = .empty, +extra: std.ArrayListUnmanaged(u32) = .empty, errors: std.ArrayListUnmanaged(Ast.Error) = .empty, pub const InnerError = error{ @@ -25,7 +20,6 @@ pub const InnerError = error{ SemanticError, TooManyConstants, InvalidCharacter, - NotImplemented, } || anyerror; pub const Fixup = struct { @@ -41,27 +35,30 @@ pub const Label = struct { code_offset: usize, }; -pub const IndexSlice = struct { - index: u32, - len: u32, -}; - const Scope = struct { parent: ?*Scope, astgen: *AstGen, - decls: std.AutoHashMapUnmanaged(IndexSlice, Decl), + decls: std.AutoHashMapUnmanaged(Ir.NullTerminatedString, Decl), pub fn deinit(self: *Scope) void { const gpa = self.astgen.gpa; self.decls.deinit(gpa); } - pub fn insert(self: *Scope, ref: IndexSlice, symbol: Decl) !void { - const gpa = self.astgen.arena; - return self.decls.put(gpa, ref, symbol); + pub fn makeChild(parent_scope: *Scope) Scope { + return .{ + .parent = parent_scope, + .astgen = parent_scope.astgen, + .decls = .empty, + }; } - pub fn lookup(self: *Scope, ref: IndexSlice) ?Decl { + pub fn insert(self: *Scope, ref: Ir.NullTerminatedString, decl: Decl) !void { + const gpa = self.astgen.gpa; + return self.decls.put(gpa, ref, decl); + } + + pub fn lookup(self: *Scope, ref: Ir.NullTerminatedString) ?Decl { var current_scope: ?*Scope = self; while (current_scope) |scope| : (current_scope = scope.parent) { const result = scope.decls.get(ref); @@ -71,163 +68,147 @@ const Scope = struct { } }; -const CodeGen = struct { +const GenIr = struct { astgen: *AstGen, - exit_label: usize, - chunk_name_ref: IndexSlice, - chunk_stack_size: u32, - chunk_arity: u32, - constants_top: usize, + instructions: *std.ArrayListUnmanaged(Ir.Inst.Index), instructions_top: usize, - fixup_stack_top: usize, - label_stack_top: usize, - pub fn deinit(_: *CodeGen) void {} - - pub fn finalize(self: *CodeGen) !void { - const gpa = self.astgen.gpa; - const knot: CodeUnit.Knot = .{ - .name_ref = self.chunk_name_ref, - .arity = self.chunk_arity, - .stack_size = self.chunk_stack_size, - .instructions = .{ - .index = @intCast(self.instructions_top), - .len = @intCast(self.astgen.instructions.items.len - self.instructions_top), - }, - .constants = .{ - .index = @intCast(self.constants_top), - .len = @intCast(self.astgen.constants.items.len - self.constants_top), - }, - }; - return self.astgen.knots.append(gpa, knot); + pub fn unstack(gi: *GenIr) void { + gi.instructions.items.len = gi.instructions_top; } fn fail( - self: *CodeGen, + self: *GenIr, tag: Ast.Error.Tag, node: *const Ast.Node, ) error{ SemanticError, OutOfMemory } { return self.astgen.fail(tag, node); } - pub fn makeSub(self: *CodeGen) CodeGen { + fn instructionsSlice(gi: *const GenIr) []Ir.Inst.Index { + return gi.instructions.items[gi.instructions_top..]; + } + + pub fn makeSubBlock(self: *GenIr) GenIr { return .{ .astgen = self.astgen, - .constants_top = self.astgen.constants.items.len, + .instructions = self.instructions, .instructions_top = self.astgen.instructions.items.len, - .fixup_stack_top = self.astgen.fixup_stack.items.len, - .label_stack_top = self.astgen.label_stack.items.len, - .exit_label = self.exit_label, - .chunk_name_ref = self.chunk_name_ref, - .chunk_stack_size = self.chunk_stack_size, - .chunk_arity = self.chunk_arity, }; } - pub fn makeConstant( - self: *CodeGen, - constant: CodeUnit.Constant, - ) error{OutOfMemory}!usize { + pub fn addGlobal(self: *GenIr, decl: Decl) error{OutOfMemory}!void { const gpa = self.astgen.gpa; - const constant_index = self.astgen.constants.items.len; - - try self.astgen.constants.append(gpa, constant); - return constant_index; - } - - pub fn makeGlobal(self: *CodeGen, decl: Decl) error{OutOfMemory}!void { - const gpa = self.astgen.gpa; - const global_data = self.astgen; - try global_data.globals.append(gpa, .{ + try self.astgen.globals.append(gpa, .{ .variable = .{ .name = decl.string_index }, }); } - pub fn makeStackSlot(self: *CodeGen) !u32 { - const stack_index = self.chunk_stack_size; - self.chunk_stack_size += 1; - return stack_index; + pub fn add(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); + gi.astgen.instructions.appendAssumeCapacity(inst); + gi.instructions.appendAssumeCapacity(inst_index); + return inst_index; } - pub fn emitByte(self: *CodeGen, byte: u8) error{OutOfMemory}!void { - const gpa = self.astgen.gpa; - return self.astgen.instructions.append(gpa, byte); + pub fn addInt(gi: *GenIr, value: u64) !Ir.Inst.Index { + return add(gi, .{ + .tag = .integer, + .data = .{ .integer = .{ + .value = value, + } }, + }); } - pub fn emitSimpleInst(self: *CodeGen, op: Story.Opcode) error{OutOfMemory}!void { - return self.emitByte(@intFromEnum(op)); + pub fn addUnaryNode( + gi: *GenIr, + tag: Ir.Inst.Tag, + arg: Ir.Inst.Index, + ) !Ir.Inst.Index { + return add(gi, .{ + .tag = tag, + .data = .{ .un = .{ + .lhs = arg, + } }, + }); } - pub fn emitConstInst(self: *CodeGen, op: Story.Opcode, arg: usize) !void { - if (arg >= std.math.maxInt(u8)) return error.TooManyConstants; - - try self.emitSimpleInst(op); - try self.emitByte(@intCast(arg)); + pub fn addBinaryNode( + gi: *GenIr, + tag: Ir.Inst.Tag, + lhs: Ir.Inst.Index, + rhs: Ir.Inst.Index, + ) !Ir.Inst.Index { + return add(gi, .{ + .tag = tag, + .data = .{ .bin = .{ + .lhs = lhs, + .rhs = rhs, + } }, + }); } - pub fn emitJumpInst(self: *CodeGen, op: Story.Opcode) error{OutOfMemory}!usize { - try self.emitSimpleInst(op); - try self.emitByte(0xff); - try self.emitByte(0xff); - return self.astgen.instructions.items.len - 2; + pub fn makePayloadNode(gi: *GenIr, tag: Ir.Inst.Tag) !Ir.Inst.Index { + const gpa = gi.astgen.gpa; + const inst_index: Ir.Inst.Index = @enumFromInt(gi.astgen.instructions.items.len); + try gi.astgen.instructions.append(gpa, .{ + .tag = tag, + .data = .{ + .payload = .{ .payload_index = undefined }, + }, + }); + return inst_index; } - // Create a new code label. - pub fn makeLabel(self: *CodeGen) error{OutOfMemory}!usize { - const gpa = self.astgen.gpa; - const dummy_address = 0xffffffff; - const label_stack = &self.astgen.label_stack; - const label_index = label_stack.items.len; - const label_data: Label = .{ .code_offset = dummy_address }; - - try label_stack.append(gpa, label_data); - return label_index; + pub fn makeDeclNode(gi: *GenIr) !Ir.Inst.Index { + return makePayloadNode(gi, .decl_knot); } - // Sets the code offset pointed to by a label. - pub fn setLabel(self: *CodeGen, label_index: usize) void { - const code_offset = self.astgen.instructions.items.len; - const label_stack = &self.astgen.label_stack; - assert(label_index <= label_stack.items.len); - - const label_data = &label_stack.items[label_index]; - label_data.code_offset = code_offset; + pub fn makeBlockInst(gi: *GenIr) !Ir.Inst.Index { + return makePayloadNode(gi, .block); } - pub fn makeFixup(scope: *CodeGen, fixup: Fixup) !usize { - const gpa = scope.astgen.gpa; - const fixup_stack = &scope.astgen.fixup_stack; - const fixup_index = fixup_stack.items.len; - try fixup_stack.append(gpa, fixup); - return fixup_index; - } + pub fn setDecl( + gi: *GenIr, + inst: Ir.Inst.Index, + name_ref: Ir.NullTerminatedString, + ) !void { + const astgen = gi.astgen; + const gpa = astgen.gpa; + const body = gi.instructionsSlice(); + const extra_len = @typeInfo(Ir.Inst.Knot).@"struct".fields.len + body.len; + try astgen.extra.ensureUnusedCapacity(gpa, extra_len); - pub fn setExit(scope: *CodeGen, label_index: usize) void { - scope.exit_label = label_index; - } + const inst_data = &astgen.instructions.items[@intFromEnum(inst)].data; + inst_data.payload.payload_index = astgen.addExtraAssumeCapacity( + Ir.Inst.Knot{ .name_ref = name_ref, .body_len = @intCast(body.len) }, + ); - pub fn resolveLabels(scope: *CodeGen, start_index: usize, end_index: usize) !void { - assert(start_index <= end_index); - const fixup_stack = &scope.astgen.fixup_stack; - const label_stack = &scope.astgen.label_stack; - const chunk_bytes = &scope.chunk.bytes; - const fixup_list = fixup_stack.items[start_index..end_index]; - - for (fixup_list) |fixup| { - const label = label_stack.items[fixup.label_index]; - const fixup_offset: usize = switch (fixup.mode) { - .relative => label.code_offset - fixup.code_offset - 2, - .absolute => label.code_offset, - }; - if (fixup_offset >= std.math.maxInt(u16)) { - std.debug.print("Too much code to fixup over! {d}\n", .{fixup_offset}); - return error.CompilerBug; - } - - assert(chunk_bytes.capacity >= fixup.code_offset + 2); - chunk_bytes.items[fixup.code_offset] = @intCast((fixup_offset >> 8) & 0xff); - chunk_bytes.items[fixup.code_offset + 1] = @intCast(fixup_offset & 0xff); + for (body) |inst_index| { + astgen.extra.appendAssumeCapacity(@intFromEnum(inst_index)); } + gi.unstack(); + } + + pub fn setBlockBody(gi: *GenIr, inst: Ir.Inst.Index) !void { + const astgen = gi.astgen; + const gpa = astgen.gpa; + const body = gi.instructionsSlice(); + const extra_len = @typeInfo(Ir.Inst.Knot).@"struct".fields.len + body.len; + try astgen.extra.ensureUnusedCapacity(gpa, extra_len); + + const inst_data = &astgen.instructions.items[@intFromEnum(inst)].data; + inst_data.payload.payload_index = astgen.addExtraAssumeCapacity( + Ir.Inst.Block{ .body_len = @intCast(body.len) }, + ); + for (body) |inst_index| { + astgen.extra.appendAssumeCapacity(@intFromEnum(inst_index)); + } + gi.unstack(); } }; @@ -235,14 +216,32 @@ pub fn deinit(astgen: *AstGen) void { const gpa = astgen.gpa; astgen.string_table.deinit(gpa); astgen.string_bytes.deinit(gpa); - astgen.label_stack.deinit(gpa); - astgen.fixup_stack.deinit(gpa); astgen.instructions.deinit(gpa); - astgen.constants.deinit(gpa); - astgen.knots.deinit(gpa); astgen.errors.deinit(gpa); } +fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 { + const fields = std.meta.fields(@TypeOf(extra)); + const extra_index: u32 = @intCast(astgen.extra.items.len); + astgen.extra.items.len += fields.len; + setExtra(astgen, extra_index, extra); + return extra_index; +} + +fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void { + const fields = std.meta.fields(@TypeOf(extra)); + var i = index; + inline for (fields) |field| { + astgen.extra.items[i] = switch (field.type) { + u32 => @field(extra, field.name), + Ir.Inst.Index => @intFromEnum(@field(extra, field.name)), + Ir.NullTerminatedString => @intFromEnum(@field(extra, field.name)), + else => @compileError("bad field type"), + }; + i += 1; + } +} + fn fail( self: *AstGen, tag: Ast.Error.Tag, @@ -267,7 +266,7 @@ fn sliceFromNode(astgen: *const AstGen, node: *const Ast.Node) []const u8 { return source_bytes[node.loc.start..node.loc.end]; } -fn stringFromBytes(astgen: *AstGen, bytes: []const u8) error{OutOfMemory}!IndexSlice { +fn stringFromBytes(astgen: *AstGen, bytes: []const u8) error{OutOfMemory}!Ir.NullTerminatedString { const gpa = astgen.gpa; const str_index: u32 = @intCast(astgen.string_bytes.items.len); const string_bytes = &astgen.string_bytes; @@ -281,56 +280,47 @@ fn stringFromBytes(astgen: *AstGen, bytes: []const u8) error{OutOfMemory}!IndexS }); if (gop.found_existing) { string_bytes.shrinkRetainingCapacity(str_index); - return .{ .index = gop.key_ptr.*, .len = @intCast(key.len) }; + return @enumFromInt(str_index); } else { gop.key_ptr.* = str_index; try string_bytes.append(gpa, 0); - return .{ .index = str_index, .len = @intCast(key.len) }; + return @enumFromInt(str_index); } } -fn stringFromNode(astgen: *AstGen, node: *const Ast.Node) !IndexSlice { +fn stringFromNode(astgen: *AstGen, node: *const Ast.Node) !Ir.NullTerminatedString { const name_bytes = sliceFromNode(astgen, node); assert(name_bytes.len > 0); return astgen.stringFromBytes(name_bytes); } -fn createScope(astgen: *AstGen, parent_scope: ?*Scope) !*Scope { - const child_scope = try astgen.arena.create(Scope); - child_scope.* = .{ - .parent = parent_scope, - .astgen = astgen, - .decls = .empty, - }; - return child_scope; -} - fn unaryOp( - gen: *CodeGen, + gi: *GenIr, scope: *Scope, - node: *const Ast.Node, - op: Story.Opcode, -) InnerError!void { - try expr(gen, scope, node.data.bin.lhs); - try gen.emitSimpleInst(op); + 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); + return gi.addUnaryNode(op, lhs); } fn binaryOp( - gen: *CodeGen, + gi: *GenIr, scope: *Scope, - node: *const Ast.Node, - op: Story.Opcode, -) InnerError!void { - const data = node.data.bin; + expr_node: *const Ast.Node, + op: Ir.Inst.Tag, +) InnerError!Ir.Inst.Index { + const data = expr_node.data.bin; assert(data.lhs != null and data.rhs != null); - try expr(gen, scope, data.lhs); - try expr(gen, scope, data.rhs); - try gen.emitSimpleInst(op); + const lhs = try expr(gi, scope, data.lhs orelse unreachable); + const rhs = try expr(gi, scope, data.rhs orelse unreachable); + return gi.addBinaryNode(op, lhs, rhs); } fn logicalOp( - gen: *CodeGen, + gen: *GenIr, scope: *Scope, node: *const Ast.Node, op: Story.Opcode, @@ -355,91 +345,86 @@ fn logicalOp( gen.setLabel(else_label); } -fn trueLiteral(gen: *CodeGen, _: *const Ast.Node) InnerError!void { - try gen.emitSimpleInst(.true); +fn trueLiteral(gi: *GenIr) InnerError!Ir.Inst.Index { + return gi.add(.{ + .tag = .true_literal, + .data = undefined, + }); } -fn falseLiteral(gen: *CodeGen, _: *const Ast.Node) InnerError!void { - try gen.emitSimpleInst(.false); +fn falseLiteral(gi: *GenIr) InnerError!Ir.Inst.Index { + return gi.add(.{ + .tag = .false_literal, + .data = undefined, + }); } -fn numberLiteral(gen: *CodeGen, node: *const Ast.Node) InnerError!void { +fn numberLiteral(gen: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Index { const lexeme = sliceFromNode(gen.astgen, node); - const number_value = try std.fmt.parseUnsigned(i64, lexeme, 10); - const constant_id = try gen.makeConstant(.{ .number = number_value }); - - try gen.emitConstInst(.load_const, constant_id); + const int_value = try std.fmt.parseUnsigned(u64, lexeme, 10); + return gen.addInt(int_value); } -fn stringLiteral(gen: *CodeGen, node: *const Ast.Node) InnerError!void { - const string_ref = try gen.astgen.stringFromNode(node); - const constant_id = try gen.makeConstant(.{ .string = string_ref }); - - try gen.emitConstInst(.load_const, constant_id); +fn stringLiteral(gi: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Index { + const astgen = gi.astgen; + const str = try astgen.stringFromNode(node); + return gi.add(.{ + .tag = .string, + .data = .{ .string = .{ + .start = str, + } }, + }); } -fn stringExpr(gen: *CodeGen, node: *const Ast.Node) InnerError!void { - assert(node.data.bin.lhs != null); - const expr_node = node.data.bin.lhs orelse return; - return stringLiteral(gen, expr_node); +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; + return stringLiteral(gen, first_node); } -fn identifier(gen: *CodeGen, scope: *Scope, node: *const Ast.Node) InnerError!void { +fn identifier(gen: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Index { const name_ref = try gen.astgen.stringFromNode(node); - - if (scope.lookup(name_ref)) |symbol| switch (symbol) { - .global => |data| { - return gen.emitConstInst(.load_global, data.constant_slot); - }, - .local => |data| { - return gen.emitConstInst(.load, data.stack_slot); - }, - .parameter => |data| { - return gen.emitConstInst(.load, data.stack_slot); - }, - else => unreachable, - }; + if (scope.lookup(name_ref)) |_| {} return gen.fail(.unknown_identifier, node); } -fn expr(gen: *CodeGen, scope: *Scope, optional_expr: ?*const Ast.Node) InnerError!void { - const expr_node = optional_expr orelse return; +fn expr(gen: *GenIr, scope: *Scope, optional_expr: ?*const Ast.Node) InnerError!Ir.Inst.Index { + const expr_node = optional_expr orelse unreachable; switch (expr_node.tag) { - .true_literal => try trueLiteral(gen, expr_node), - .false_literal => try falseLiteral(gen, expr_node), - .number_literal => try numberLiteral(gen, expr_node), - .string_literal => try stringLiteral(gen, expr_node), - .string_expr => try stringExpr(gen, expr_node), - .identifier => try identifier(gen, scope, expr_node), - .add_expr => try binaryOp(gen, scope, expr_node, .add), - .subtract_expr => try binaryOp(gen, scope, expr_node, .sub), - .multiply_expr => try binaryOp(gen, scope, expr_node, .mul), - .divide_expr => try binaryOp(gen, scope, expr_node, .div), - .mod_expr => try binaryOp(gen, scope, expr_node, .mod), - .negate_expr => try unaryOp(gen, scope, expr_node, .neg), - .logical_and_expr => try logicalOp(gen, scope, expr_node, .jmp_f), - .logical_or_expr => try logicalOp(gen, scope, expr_node, .jmp_t), - .logical_not_expr => try unaryOp(gen, scope, expr_node, .not), - .logical_equality_expr => try binaryOp(gen, scope, expr_node, .cmp_eq), - .logical_inequality_expr => { - try gen.emitSimpleInst(.not); - try binaryOp(gen, scope, expr_node, .cmp_eq); - }, - .logical_greater_expr => try binaryOp(gen, scope, expr_node, .cmp_gt), - .logical_greater_or_equal_expr => try binaryOp(gen, scope, expr_node, .cmp_gte), - .logical_lesser_expr => try binaryOp(gen, scope, expr_node, .cmp_lt), - .logical_lesser_or_equal_expr => try binaryOp(gen, scope, expr_node, .cmp_lte), - else => return error.NotImplemented, + .true_literal => return trueLiteral(gen), + .false_literal => return falseLiteral(gen), + .number_literal => return numberLiteral(gen, expr_node), + .string_literal => return stringLiteral(gen, expr_node), + .string_expr => return stringExpr(gen, expr_node), + .identifier => return identifier(gen, scope, expr_node), + .add_expr => return binaryOp(gen, scope, expr_node, .add), + .subtract_expr => return binaryOp(gen, scope, expr_node, .sub), + .multiply_expr => return binaryOp(gen, scope, expr_node, .mul), + .divide_expr => return binaryOp(gen, scope, expr_node, .div), + .mod_expr => return binaryOp(gen, scope, expr_node, .mod), + .negate_expr => return unaryOp(gen, scope, expr_node, .neg), + //.logical_and_expr => return logicalOp(gen, scope, expr_node, .jmp_f), + //.logical_or_expr => return logicalOp(gen, scope, expr_node, .jmp_t), + //.logical_not_expr => return unaryOp(gen, scope, expr_node, .not), + //.logical_equality_expr => return binaryOp(gen, scope, expr_node, .cmp_eq), + //.logical_inequality_expr => { + // return binaryOp(gen, scope, expr_node, .cmp_eq); + //}, + //.logical_greater_expr => return binaryOp(gen, scope, expr_node, .cmp_gt), + //.logical_greater_or_equal_expr => return binaryOp(gen, scope, expr_node, .cmp_gte), + //.logical_lesser_expr => return binaryOp(gen, scope, expr_node, .cmp_lt), + //.logical_lesser_or_equal_expr => return binaryOp(gen, scope, expr_node, .cmp_lte), + else => unreachable, } } -fn exprStmt(gen: *CodeGen, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void { - const expr_node = stmt_node.data.bin.lhs orelse return; - try expr(gen, scope, expr_node); - try gen.emitSimpleInst(.pop); +fn exprStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!Ir.Inst.Index { + // TODO: Maybe we should introduce a unary node type to avoid optional checks? + const expr_node = stmt_node.data.bin.lhs orelse unreachable; + return expr(gen, scope, expr_node); } -fn validateSwitchProngs(gen: *CodeGen, stmt_node: *const Ast.Node) InnerError!void { +fn validateSwitchProngs(gen: *GenIr, stmt_node: *const Ast.Node) InnerError!void { var stmt_has_block: bool = false; var stmt_has_else: bool = false; const case_list = stmt_node.data.switch_stmt.cases; @@ -466,7 +451,7 @@ fn validateSwitchProngs(gen: *CodeGen, stmt_node: *const Ast.Node) InnerError!vo } } -fn ifStmt(gen: *CodeGen, parent_scope: *Scope, stmt_node: *const Ast.Node) InnerError!void { +fn ifStmt(gen: *GenIr, parent_scope: *Scope, stmt_node: *const Ast.Node) InnerError!void { const case_list = stmt_node.data.switch_stmt.cases; const eval_expr = stmt_node.data.switch_stmt.condition_expr; if (eval_expr) |expr_node| { @@ -515,7 +500,7 @@ fn ifStmt(gen: *CodeGen, parent_scope: *Scope, stmt_node: *const Ast.Node) Inner } fn multiIfStmt( - gen: *CodeGen, + gen: *GenIr, parent_scope: *Scope, stmt_node: *const Ast.Node, ) InnerError!void { @@ -586,7 +571,7 @@ fn multiIfStmt( } fn switchStmt( - gen: *CodeGen, + gen: *GenIr, parent_scope: *Scope, stmt_node: *const Ast.Node, ) InnerError!void { @@ -671,46 +656,46 @@ fn switchStmt( } fn inlineLogicExpr( - gen: *CodeGen, + gen: *GenIr, scope: *Scope, expr_node: *const Ast.Node, -) InnerError!void { +) InnerError!Ir.Inst.Index { const main_node = expr_node.data.bin.lhs; assert(main_node != null); return expr(gen, scope, main_node); } -fn contentExpr( - gen: *CodeGen, - scope: *Scope, - expr_node: *const Ast.Node, -) InnerError!void { - const node_list = expr_node.data.list.items orelse return; +fn contentExpr(gi: *GenIr, scope: *Scope, expr_node: *const Ast.Node) InnerError!Ir.Inst.Index { + const astgen = gi.astgen; + const gpa = astgen.gpa; + const block_inst = try gi.makeBlockInst(); + try gi.instructions.append(gpa, block_inst); + + var sub_block = gi.makeSubBlock(); + defer sub_block.unstack(); + // TODO: Make sure that this is not nullable. + const node_list = expr_node.data.list.items orelse unreachable; for (node_list) |child_node| { - switch (child_node.tag) { - .string_literal => { - try stringLiteral(gen, child_node); - try gen.emitSimpleInst(.stream_push); - }, - .inline_logic_expr => { - try inlineLogicExpr(gen, scope, child_node); - try gen.emitSimpleInst(.stream_push); - }, - .if_stmt => try ifStmt(gen, scope, child_node), - .multi_if_stmt => try multiIfStmt(gen, scope, child_node), - .switch_stmt => try switchStmt(gen, scope, child_node), - else => return error.NotImplemented, - } + _ = switch (child_node.tag) { + .string_literal => try stringLiteral(&sub_block, child_node), + .inline_logic_expr => try inlineLogicExpr(&sub_block, scope, child_node), + //.if_stmt => try ifStmt(gen, scope, child_node), + //.multi_if_stmt => try multiIfStmt(gen, scope, child_node), + //.switch_stmt => try switchStmt(gen, scope, child_node), + else => unreachable, + }; } + try sub_block.setBlockBody(block_inst); + return block_inst; } -fn contentStmt(gen: *CodeGen, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void { - const expr_node = stmt_node.data.bin.lhs orelse return; - try contentExpr(gen, scope, expr_node); - try gen.emitSimpleInst(.stream_flush); +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; + const expr_ref = try contentExpr(gen, scope, expr_node); + return gen.addUnaryNode(.content, expr_ref); } -fn assignStmt(gen: *CodeGen, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void { +fn assignStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void { const identifier_node = stmt_node.data.bin.lhs orelse unreachable; const expr_node = stmt_node.data.bin.rhs orelse unreachable; const name_ref = try gen.astgen.stringFromNode(identifier_node); @@ -736,7 +721,7 @@ fn assignStmt(gen: *CodeGen, scope: *Scope, stmt_node: *const Ast.Node) InnerErr return gen.fail(.unknown_identifier, identifier_node); } -fn choiceStmt(gen: *CodeGen, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void { +fn choiceStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void { const Choice = struct { label_index: usize, start_expression: ?*const Ast.Node, @@ -810,7 +795,7 @@ fn choiceStmt(gen: *CodeGen, scope: *Scope, stmt_node: *const Ast.Node) InnerErr } } -fn varDecl(gen: *CodeGen, scope: *Scope, decl_node: *const Ast.Node) !void { +fn varDecl(gen: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void { const identifier_node = decl_node.data.bin.lhs orelse unreachable; const expr_node = decl_node.data.bin.rhs orelse unreachable; const name_ref = try gen.astgen.stringFromNode(identifier_node); @@ -854,61 +839,71 @@ fn varDecl(gen: *CodeGen, scope: *Scope, decl_node: *const Ast.Node) !void { try gen.emitSimpleInst(.pop); } -fn stmt(gen: *CodeGen, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void { - switch (stmt_node.tag) { - .var_decl => try varDecl(gen, scope, stmt_node), - .const_decl => try varDecl(gen, scope, stmt_node), - .temp_decl => try varDecl(gen, scope, stmt_node), - .assign_stmt => try assignStmt(gen, scope, stmt_node), - .content_stmt => try contentStmt(gen, scope, stmt_node), - .choice_stmt => try choiceStmt(gen, scope, stmt_node), - .expr_stmt => try exprStmt(gen, scope, stmt_node), - else => return error.NotImplemented, +fn blockInner(gi: *GenIr, parent_scope: *Scope, stmt_list: []*Ast.Node) !void { + var child_scope = parent_scope.makeChild(); + defer child_scope.deinit(); + + for (stmt_list) |inner_node| { + _ = switch (inner_node.tag) { + //.var_decl => try varDecl(gen, scope, inner_node), + //.const_decl => try varDecl(gen, scope, inner_node), + //.temp_decl => try varDecl(gen, scope, inner_node), + //.assign_stmt => try assignStmt(gen, scope, inner_node), + .content_stmt => try contentStmt(gi, &child_scope, inner_node), + //.choice_stmt => try choiceStmt(gen, scope, inner_node), + .expr_stmt => try exprStmt(gi, &child_scope, inner_node), + else => unreachable, + }; } } fn blockStmt( - gen: *CodeGen, - parent_scope: *Scope, - block_stmt: ?*const Ast.Node, + parent_gi: *GenIr, + scope: *Scope, + stmt_node: *const Ast.Node, ) InnerError!void { - if (block_stmt) |stmt_node| { - const block_scope = try gen.astgen.createScope(parent_scope); - const children = stmt_node.data.list.items orelse return; - for (children) |child_stmt| try stmt(gen, block_scope, child_stmt); - } else { - const fixup_offset = try gen.emitJumpInst(.jmp); - _ = try gen.makeFixup(.{ - .mode = .relative, - .label_index = gen.exit_label, - .code_offset = fixup_offset, - }); - } + const gpa = parent_gi.astgen.gpa; + const block_inst = try parent_gi.makeBlockInst(); + try parent_gi.instructions.append(gpa, block_inst); + + var gi = parent_gi.makeSubBlock(); + defer gi.unstack(); + + // TODO: Make sure that this value is concrete to omit check. + const block_stmts = stmt_node.data.list.items orelse unreachable; + try blockInner(&gi, scope, block_stmts); + try gi.setBlockBody(block_inst); } +const main_knot_name: [:0]const u8 = "$__main__$"; + fn defaultBlock( - gen: *CodeGen, - parent_scope: *Scope, + gi: *GenIr, + scope: *Scope, body_node: *const Ast.Node, ) InnerError!void { - const name_ref = try gen.astgen.stringFromBytes("$__main__$"); - const exit_label = try gen.makeLabel(); - defer gen.setExit(exit_label); + const astgen = gi.astgen; + const gpa = astgen.gpa; + const decl_inst = try gi.makeDeclNode(); + try gi.instructions.append(gpa, decl_inst); - gen.chunk_name_ref = name_ref; + var decl_scope = gi.makeSubBlock(); + defer decl_scope.unstack(); - const block_scope = try gen.astgen.createScope(parent_scope); - try blockStmt(gen, block_scope, body_node); - gen.setLabel(exit_label); - try gen.emitSimpleInst(.exit); - try gen.finalize(); + // TODO: Make sure that this value is concrete to omit check. + const block_stmts = body_node.data.list.items orelse unreachable; + try blockInner(&decl_scope, scope, block_stmts); + + std.debug.print("{any}\n", .{decl_scope}); + const name_ref = try decl_scope.astgen.stringFromBytes("$__main__$"); + try decl_scope.setDecl(decl_inst, name_ref); } -fn stitchDecl(_: *CodeGen, _: *Scope, _: *const Ast.Node) InnerError!void {} +fn stitchDecl(_: *GenIr, _: *Scope, _: *const Ast.Node) InnerError!void {} -fn functionDecl(_: *CodeGen, _: *Scope, _: *const Ast.Node) InnerError!void {} +fn functionDecl(_: *GenIr, _: *Scope, _: *const Ast.Node) InnerError!void {} -fn knotDecl(gen: *CodeGen, scope: *Scope, decl_node: *const Ast.Node) InnerError!void { +fn knotDecl(gen: *GenIr, scope: *Scope, decl_node: *const Ast.Node) InnerError!void { const prototype_node = decl_node.data.knot_decl.prototype; const nested_decls_list = decl_node.data.knot_decl.children orelse return; const identifier_node = prototype_node.data.bin.lhs orelse unreachable; @@ -916,7 +911,7 @@ fn knotDecl(gen: *CodeGen, scope: *Scope, decl_node: *const Ast.Node) InnerError const knot_symbol = scope.lookup(ident_ref) orelse unreachable; const knot_scope = knot_symbol.knot.decl_scope; - var block_gen = gen.makeSub(); + var block_gen = gen.makeSubBlock(); defer block_gen.deinit(); var start_index: usize = 0; @@ -934,141 +929,48 @@ fn knotDecl(gen: *CodeGen, scope: *Scope, decl_node: *const Ast.Node) InnerError } } -fn file(astgen: *AstGen, scope: *Scope, file_node: *const Ast.Node) InnerError!void { +fn file(root_gi: *GenIr, scope: *Scope, file_node: *const Ast.Node) InnerError!void { + const astgen = root_gi.astgen; + const gpa = astgen.gpa; + + const file_inst = try root_gi.makePayloadNode(.file); + try root_gi.instructions.append(gpa, file_inst); + + var file_scope = root_gi.makeSubBlock(); + defer file_scope.unstack(); + // TODO: Make sure this is non-nullable. const nested_decls_list = file_node.data.list.items orelse return; if (nested_decls_list.len == 0) return; - var gen: CodeGen = .{ - .astgen = astgen, - .exit_label = 0, - .chunk_name_ref = .{ .index = 0, .len = 0 }, - .chunk_stack_size = 0, - .chunk_arity = 0, - .instructions_top = astgen.instructions.items.len, - .constants_top = astgen.constants.items.len, - .fixup_stack_top = astgen.fixup_stack.items.len, - .label_stack_top = astgen.label_stack.items.len, - }; - defer gen.deinit(); - var start_index: usize = 0; + const first_child = nested_decls_list[0]; if (first_child.tag == .block_stmt) { - try defaultBlock(&gen, scope, first_child); - if (nested_decls_list.len > 1) start_index += 1 else return; + try defaultBlock(&file_scope, scope, first_child); + if (nested_decls_list.len > 1) + start_index += 1 + else + return file_scope.setBlockBody(file_inst); } for (nested_decls_list[start_index..]) |child_node| { switch (child_node.tag) { - .knot_decl => try knotDecl(&gen, scope, child_node), - .stitch_decl => try stitchDecl(&gen, scope, child_node), - .function_decl => try functionDecl(&gen, scope, child_node), + //.knot_decl => try knotDecl(gi, scope, child_node), + //.stitch_decl => try stitchDecl(gi, scope, child_node), + //.function_decl => try functionDecl(gi, scope, child_node), else => unreachable, } } + return file_scope.setBlockBody(file_inst); } -fn insertDecl(astgen: *AstGen, parent_scope: *Scope, proto_node: *const Ast.Node) InnerError!void { - const identifier_node = proto_node.data.bin.lhs orelse unreachable; - const name_ref = try astgen.stringFromNode(identifier_node); - - if (parent_scope.lookup(name_ref)) |_| { - return astgen.fail(.redefined_identifier, proto_node); - } - - const decl_scope = try astgen.createScope(parent_scope); - const arity = if (proto_node.data.bin.rhs) |param_list_node| - param_list_node.data.list.items.?.len - else - 0; - - try parent_scope.insert(name_ref, .{ - .knot = .{ - .decl_node = proto_node, - .decl_scope = decl_scope, - .arity = arity, - }, - }); - if (proto_node.data.bin.rhs) |param_list_node| { - const param_list = param_list_node.data.list.items orelse unreachable; - for (param_list, 0..) |param_node, stack_index| { - const param_name_ref = try astgen.stringFromNode(param_node); - if (decl_scope.lookup(param_name_ref)) |_| { - return astgen.fail(.redefined_identifier, param_node); - } - - try decl_scope.insert(param_name_ref, .{ - .parameter = .{ - .decl_node = param_node, - .stack_slot = stack_index, - }, - }); - } - } -} - -pub const Decl = union(enum) { - knot: struct { - decl_node: *const Ast.Node, - arity: usize, - decl_scope: *Scope, - }, - global: struct { - decl_node: *const Ast.Node, - is_constant: bool, - constant_slot: usize, - }, - local: struct { - decl_node: *const Ast.Node, - stack_slot: usize, - }, - parameter: struct { - decl_node: *const Ast.Node, - stack_slot: usize, - }, +pub const Decl = struct { + decl_node: *const Ast.Node, }; -fn collectRootDecls( - astgen: *AstGen, - parent_scope: *Scope, - root_node: *const Ast.Node, -) InnerError!void { - const root_decl_list = root_node.data.list.items orelse return; - for (root_decl_list) |root_decl_node| switch (root_decl_node.tag) { - .knot_decl => { - const knot_proto_node = root_decl_node.data.bin.lhs orelse unreachable; - try insertDecl(astgen, parent_scope, knot_proto_node); - - const knot_scope = try astgen.createScope(parent_scope); - const knot_nest_node = root_decl_node.data.bin.rhs orelse unreachable; - const nested_decl_list = knot_nest_node.data.list.items; - if (nested_decl_list) |decl_list| { - for (decl_list) |decl_node| switch (decl_node.tag) { - .stitch_decl, .function_decl => { - const proto_node = decl_node.data.bin.lhs orelse unreachable; - try insertDecl(astgen, knot_scope, proto_node); - }, - .block_stmt => {}, - else => unreachable, - }; - } - }, - .stitch_decl, .function_decl => try insertDecl(astgen, parent_scope, root_decl_node), - .block_stmt => {}, - else => unreachable, - }; -} - /// Perform code generation via tree-walk. -/// -/// Currently, two passes over the AST are performed; one to resolve forward -/// references and another to generate code. -pub fn generate(gpa: std.mem.Allocator, tree: *const Ast) !CodeUnit { - var arena_allocator: std.heap.ArenaAllocator = .init(gpa); - defer arena_allocator.deinit(); - +pub fn generate(gpa: std.mem.Allocator, tree: *const Ast) !Ir { var astgen: AstGen = .{ .gpa = gpa, - .arena = arena_allocator.allocator(), .tree = tree, }; defer astgen.deinit(); @@ -1076,73 +978,29 @@ pub fn generate(gpa: std.mem.Allocator, tree: *const Ast) !CodeUnit { // First entry is reserved for the empty string sentinel. try astgen.string_bytes.append(gpa, 0); - const file_scope = try astgen.createScope(null); + var instructions: std.ArrayListUnmanaged(Ir.Inst.Index) = .empty; + defer instructions.deinit(gpa); + var file_scope: Scope = .{ + .parent = null, + .decls = .empty, + .astgen = &astgen, + }; + var gen: GenIr = .{ + .astgen = &astgen, + .instructions = &instructions, + .instructions_top = 0, + }; + defer gen.unstack(); + + // TODO: Make sure this is never null. const root_node = tree.root orelse unreachable; - try collectRootDecls(&astgen, file_scope, root_node); - try file(&astgen, file_scope, root_node); + try file(&gen, &file_scope, root_node); return .{ .string_bytes = try astgen.string_bytes.toOwnedSlice(gpa), - .constants = try astgen.constants.toOwnedSlice(gpa), .instructions = try astgen.instructions.toOwnedSlice(gpa), - .knots = try astgen.knots.toOwnedSlice(gpa), + .extra = try astgen.extra.toOwnedSlice(gpa), .errors = try astgen.errors.toOwnedSlice(gpa), }; } - -pub const CodeUnit = struct { - string_bytes: []const u8, - // TODO: Constants COULD be interned as well. - constants: []const Constant, - instructions: []const u8, - knots: []const Knot, - errors: []const Ast.Error, - - pub const Knot = struct { - name_ref: IndexSlice, - constants: IndexSlice, - instructions: IndexSlice, - arity: usize, - stack_size: usize, - }; - - pub const Constant = union(enum) { - number: i64, - string: IndexSlice, - }; - - pub fn deinit(unit: CodeUnit, gpa: std.mem.Allocator) void { - gpa.free(unit.instructions); - gpa.free(unit.constants); - gpa.free(unit.knots); - gpa.free(unit.errors); - gpa.free(unit.string_bytes); - } - - pub fn dumpStringsWithHex(cu: *const CodeUnit) void { - const bytes = cu.string_bytes; - var start: usize = 0; - while (start < bytes.len) { - const end = std.mem.indexOfScalarPos(u8, bytes, start, 0) orelse break; - const s = bytes[start..end]; - - std.debug.print("[{d:04}] ", .{start}); - for (s) |b| std.debug.print("{x:02} ", .{b}); - std.debug.print("00: {s}\n", .{s}); - start = end + 1; - } - } - - pub fn resolveString(self: *const CodeUnit, ref: IndexSlice) []const u8 { - return self.string_bytes[ref.index..][0..ref.len]; - } - - pub fn resolveConstants(self: *const CodeUnit, ref: IndexSlice) []const Constant { - return self.constants[ref.index..][0..ref.len]; - } - - pub fn resolveInstructions(self: *const CodeUnit, ref: IndexSlice) []const u8 { - return self.instructions[ref.index..][0..ref.len]; - } -}; diff --git a/src/Ir.zig b/src/Ir.zig new file mode 100644 index 0000000..69cf5ff --- /dev/null +++ b/src/Ir.zig @@ -0,0 +1,264 @@ +const std = @import("std"); +const Ast = @import("Ast.zig"); +const Ir = @This(); + +string_bytes: []u8, +instructions: []Inst, +extra: []u32, +errors: []Ast.Error, + +pub const Inst = struct { + tag: Tag, + data: Data, + + pub const Index = enum(u32) { + file_inst = 0, + _, + }; + + pub const Tag = enum { + file, + decl_knot, + block, + add, + sub, + mul, + div, + mod, + neg, + true_literal, + false_literal, + integer, + string, + content, + }; + + pub const Data = union { + payload: struct { + payload_index: u32, + }, + un: struct { + lhs: Inst.Index, + }, + bin: struct { + lhs: Inst.Index, + rhs: Inst.Index, + }, + integer: struct { + value: u64, + }, + string: struct { + /// Offset into `string_bytes`. + start: NullTerminatedString, + + pub fn get(self: @This(), ir: Ir) []const u8 { + return nullTerminatedString(ir, self.start); + } + }, + }; + + pub const Knot = struct { + name_ref: NullTerminatedString, + body_len: u32, + }; + + pub const Block = struct { + body_len: u32, + }; +}; + +pub const NullTerminatedString = enum(u32) { + empty, + _, +}; + +pub const IndexSlice = struct { + index: u32, + len: u32, +}; + +pub fn deinit(ir: *Ir, gpa: std.mem.Allocator) void { + gpa.free(ir.string_bytes); + gpa.free(ir.instructions); + gpa.free(ir.extra); + gpa.free(ir.errors); + ir.* = undefined; +} + +const Prefix = struct { + buf: std.ArrayListUnmanaged(u8) = .empty, + + pub fn deinit(self: *Prefix, gpa: std.mem.Allocator) void { + self.buf.deinit(gpa); + } + + pub fn writeIndent(self: *const Prefix, writer: *std.Io.Writer) !void { + try writer.writeAll(self.buf.items); + } + + pub fn pushChildPrefix(self: *Prefix, gpa: std.mem.Allocator) !usize { + const old_len = self.buf.items.len; + const seg: []const u8 = " "; + try self.buf.appendSlice(gpa, seg); + return old_len; + } + + pub fn restore(self: *Prefix, new_len: usize) void { + self.buf.shrinkRetainingCapacity(new_len); + } +}; + +const Render = struct { + gpa: std.mem.Allocator, + prefix: Prefix, + writer: *std.Io.Writer, + + pub const Error = error{ + OutOfMemory, + WriteFailed, + }; + + fn renderUnaryInst(r: *Render, inst: Inst) Error!void { + const io_w = r.writer; + return io_w.print("{s}(%{d})", .{ @tagName(inst.tag), inst.data.un.lhs }); + } + + fn renderBinaryInst(r: *Render, inst: Inst) Error!void { + const io_w = r.writer; + return io_w.print("{s}(%{d}, %{d})", .{ @tagName(inst.tag), inst.data.bin.lhs, inst.data.bin.rhs }); + } + + fn renderBodyInner(r: *Render, ir: Ir, body_list: []const Inst.Index) Error!void { + const io_w = r.writer; + try io_w.writeAll("{\n"); + { + const old_len = try r.prefix.pushChildPrefix(r.gpa); + defer r.prefix.restore(old_len); + + for (body_list) |inst| try r.renderInst(ir, inst); + } + try r.prefix.writeIndent(io_w); + try io_w.writeAll("}"); + } + + fn renderBlockInst(r: *Render, ir: Ir, inst: Inst) Error!void { + const io_w = r.writer; + const extra = ir.extraData(Inst.Block, inst.data.payload.payload_index); + const body_slice = ir.bodySlice(extra.end, extra.data.body_len); + + try io_w.print("{s}(", .{@tagName(inst.tag)}); + try renderBodyInner(r, ir, body_slice); + try io_w.writeAll(")"); + } + + fn renderKnotInst(r: *Render, ir: Ir, inst: Inst) Error!void { + const io_w = r.writer; + const extra = ir.extraData(Inst.Knot, inst.data.payload.payload_index); + const body_slice = ir.bodySlice(extra.end, extra.data.body_len); + const knot_ident_str = ir.nullTerminatedString(extra.data.name_ref); + try io_w.print("{s}(name=\"{s}\",body=", .{ @tagName(inst.tag), knot_ident_str }); + try renderBodyInner(r, ir, body_slice); + try io_w.writeAll(")"); + } + + fn renderFileInst(r: *Render, ir: Ir, inst: Inst) Error!void { + const io_w = r.writer; + const extra = ir.extraData(Inst.Block, inst.data.payload.payload_index); + const body_slice = ir.bodySlice(extra.end, extra.data.body_len); + + try io_w.print("{s}(", .{@tagName(inst.tag)}); + try renderBodyInner(r, ir, body_slice); + return io_w.writeAll(")"); + } + + fn renderInst(r: *Render, ir: Ir, index: Inst.Index) Error!void { + const io_w = r.writer; + const inst_index: u32 = @intFromEnum(index); + const inst = ir.instructions[inst_index]; + + try r.prefix.writeIndent(io_w); + try io_w.print("%{d} = ", .{inst_index}); + switch (inst.tag) { + .file => try r.renderFileInst(ir, inst), + .decl_knot => try r.renderKnotInst(ir, inst), + .block => try r.renderBlockInst(ir, inst), + .add => try r.renderBinaryInst(inst), + .sub => try r.renderBinaryInst(inst), + .mul => try r.renderBinaryInst(inst), + .div => try r.renderBinaryInst(inst), + .mod => try r.renderBinaryInst(inst), + .neg => try r.renderUnaryInst(inst), + .true_literal => { + try io_w.print("{s}", .{@tagName(inst.tag)}); + }, + .false_literal => { + try io_w.print("{s}", .{@tagName(inst.tag)}); + }, + .integer => { + const value = inst.data.integer.value; + try io_w.print("{s}({d})", .{ @tagName(inst.tag), value }); + }, + .string => { + const str_bytes = inst.data.string.get(ir); + try io_w.print("{s}(\"{s}\")", .{ @tagName(inst.tag), str_bytes }); + }, + .content => try r.renderUnaryInst(inst), + } + try io_w.writeAll("\n"); + } +}; + +pub fn render(ir: Ir, gpa: std.mem.Allocator, writer: *std.Io.Writer) !void { + std.debug.assert(ir.instructions.len > 0); + var r = Render{ .gpa = gpa, .writer = writer, .prefix = .{} }; + defer r.prefix.deinit(gpa); + + try r.renderInst(ir, .file_inst); + return writer.flush(); +} + +pub fn dumpStringsWithHex(cu: *const Ir) void { + const bytes = cu.string_bytes; + var start: usize = 0; + while (start < bytes.len) { + const end = std.mem.indexOfScalarPos(u8, bytes, start, 0) orelse break; + const s = bytes[start..end]; + + std.debug.print("[{d:04}] ", .{start}); + for (s) |b| std.debug.print("{x:02} ", .{b}); + std.debug.print("00: {s}\n", .{s}); + start = end + 1; + } +} + +fn ExtraData(comptime T: type) type { + return struct { + data: T, + end: usize, + }; +} + +pub fn extraData(ir: Ir, comptime T: type, index: usize) ExtraData(T) { + const fields = @typeInfo(T).@"struct".fields; + var i: usize = index; + var result: T = undefined; + inline for (fields) |field| { + @field(result, field.name) = switch (field.type) { + u32 => ir.extra[i], + Inst.Index => @enumFromInt(ir.extra[i]), + NullTerminatedString => @enumFromInt(ir.extra[i]), + else => @compileError("bad field type"), + }; + i += 1; + } + return .{ .data = result, .end = i }; +} + +pub fn nullTerminatedString(ir: Ir, index: NullTerminatedString) [:0]const u8 { + const slice = ir.string_bytes[@intFromEnum(index)..]; + return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0]; +} + +pub fn bodySlice(ir: Ir, start: usize, len: usize) []Inst.Index { + return @ptrCast(ir.extra[start..][0..len]); +}