diff --git a/src/AstGen.zig b/src/AstGen.zig index 26cf78d..576094c 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -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().?, }); } diff --git a/src/Ir.zig b/src/Ir.zig index 5b9a89f..0698098 100644 --- a/src/Ir.zig +++ b/src/Ir.zig @@ -1,5 +1,6 @@ const std = @import("std"); const Ast = @import("Ast.zig"); +const assert = std.debug.assert; const Ir = @This(); string_bytes: []u8, @@ -13,8 +14,35 @@ pub const Inst = struct { data: Data, pub const Index = enum(u32) { - file_inst = 0, + file_inst, + ref_start_index = 32, _, + + pub fn toRef(i: Index) Inst.Ref { + return @enumFromInt(@intFromEnum(Index.ref_start_index) + @intFromEnum(i)); + } + }; + + pub const Ref = enum(u32) { + bool_true, + bool_false, + none = std.math.maxInt(u32), + _, + + pub fn toIndex(inst: Ref) ?Index { + assert(inst != .none); + const ref_int = @intFromEnum(inst); + if (ref_int >= @intFromEnum(Index.ref_start_index)) { + return @enumFromInt(ref_int - @intFromEnum(Index.ref_start_index)); + } else { + return null; + } + } + + pub fn toIndexAllowNone(inst: Ref) ?Index { + if (inst == .none) return null; + return toIndex(inst); + } }; pub const Tag = enum { @@ -23,12 +51,9 @@ pub const Inst = struct { decl_knot, decl_var, decl_ref, - block, - condbr, - @"break", - alloc_local, - load_local, - store_local, + alloc, + load, + store, add, sub, mul, @@ -42,10 +67,11 @@ pub const Inst = struct { cmp_gte, cmp_lt, cmp_lte, - true_literal, - false_literal, integer, string, + block, + condbr, + @"break", content_push, content_flush, }; @@ -55,11 +81,11 @@ pub const Inst = struct { payload_index: u32, }, un: struct { - lhs: Index, + lhs: Ref, }, bin: struct { - lhs: Index, - rhs: Index, + lhs: Ref, + rhs: Ref, }, integer: struct { value: u64, @@ -96,7 +122,7 @@ pub const Inst = struct { }; pub const CondBr = struct { - condition: Index, + condition: Ref, then_body_len: u32, else_body_len: u32, }; @@ -165,6 +191,22 @@ const Render = struct { } }; + fn renderInstIndex(r: *Render, index: Inst.Index) !void { + const io_w = r.writer; + return io_w.print("%{d}", .{@intFromEnum(index)}); + } + + fn renderInstRef(r: *Render, ref: Inst.Ref) !void { + const io_w = r.writer; + if (ref == .none) { + return io_w.writeAll(".none"); + } else if (ref.toIndex()) |i| { + return r.renderInstIndex(i); + } else { + return io_w.print("@{s}", .{@tagName(ref)}); + } + } + fn renderSimple(r: *Render, inst: Inst) Error!void { const io_w = r.writer; return io_w.print("{s}(?)", .{@tagName(inst.tag)}); @@ -172,12 +214,22 @@ const Render = struct { fn renderUnary(r: *Render, inst: Inst) Error!void { const io_w = r.writer; - return io_w.print("{s}(%{d})", .{ @tagName(inst.tag), inst.data.un.lhs }); + const data = inst.data.un; + try io_w.print("{s}(", .{@tagName(inst.tag)}); + + const lhs = data.lhs; + try renderInstRef(r, lhs); + return io_w.writeAll(")"); } fn renderBinary(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 }); + const data = inst.data.bin; + try io_w.print("{s}(", .{@tagName(inst.tag)}); + try renderInstRef(r, data.lhs); + try io_w.writeAll(", "); + try renderInstRef(r, data.rhs); + return io_w.writeAll(")"); } fn renderBodyInner(r: *Render, ir: Ir, body_list: []const Inst.Index) Error!void { @@ -216,7 +268,9 @@ const Render = struct { const then_body = ir.bodySlice(extra.end, extra.data.then_body_len); const else_body = ir.bodySlice(extra.end + then_body.len, extra.data.else_body_len); - try io_w.print("{s}(%{d}, ", .{ @tagName(inst.tag), extra.data.condition }); + try io_w.print("{s}(", .{@tagName(inst.tag)}); + try renderInstRef(r, extra.data.condition); + try io_w.writeAll(", "); try renderBodyInner(r, ir, then_body); try io_w.writeAll(", "); try renderBodyInner(r, ir, else_body); @@ -286,9 +340,9 @@ const Render = struct { }, .condbr => try r.renderCondbr(ir, inst), .@"break" => try r.renderBreak(ir, inst), - .alloc_local => try r.renderSimple(inst), - .load_local => try r.renderUnary(inst), - .store_local => try r.renderBinary(inst), + .alloc => try r.renderSimple(inst), + .load => try r.renderUnary(inst), + .store => try r.renderBinary(inst), .block => try r.renderBlock(ir, inst), .add => try r.renderBinary(inst), .sub => try r.renderBinary(inst), @@ -303,12 +357,6 @@ const Render = struct { .cmp_gte => try r.renderBinary(inst), .cmp_lt => try r.renderBinary(inst), .cmp_lte => try r.renderBinary(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 }); @@ -317,8 +365,8 @@ const Render = struct { const str_bytes = inst.data.string.get(ir); try io_w.print("{s}(\"{s}\")", .{ @tagName(inst.tag), str_bytes }); }, - .content_push => try r.renderSimple(inst), - .content_flush => try r.renderUnary(inst), + .content_push => try r.renderUnary(inst), + .content_flush => try r.renderSimple(inst), } try io_w.writeAll("\n"); } @@ -329,7 +377,7 @@ pub fn render(ir: Ir, gpa: std.mem.Allocator, writer: *std.Io.Writer) !void { var r = Render{ .gpa = gpa, .writer = writer, .prefix = .{} }; defer r.prefix.deinit(gpa); - try r.renderInst(ir, .file_inst); + try r.renderInst(ir, @enumFromInt(0)); return writer.flush(); } @@ -367,6 +415,7 @@ pub fn extraData(ir: Ir, comptime T: type, index: usize) ExtraData(T) { @field(result, field.name) = switch (field.type) { u32 => ir.extra[i], Inst.Index => @enumFromInt(ir.extra[i]), + Inst.Ref => @enumFromInt(ir.extra[i]), NullTerminatedString => @enumFromInt(ir.extra[i]), else => @compileError("bad field type"), }; diff --git a/src/Sema.zig b/src/Sema.zig index 3c6b9e2..6b51dec 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7,8 +7,9 @@ const Sema = @This(); gpa: std.mem.Allocator, ir: *const Ir, -bytecode: std.ArrayListUnmanaged(u8) = .empty, constants: std.ArrayListUnmanaged(CompiledStory.Constant) = .empty, +constant_map: std.AutoHashMapUnmanaged(CompiledStory.Constant, u32) = .empty, +globals: std.ArrayListUnmanaged(u32) = .empty, knots: std.ArrayListUnmanaged(CompiledStory.Knot) = .empty, const InnerError = error{ @@ -17,10 +18,21 @@ const InnerError = error{ InvalidJump, }; +const Ref = union(enum) { + bool_true, + bool_false, + none, + index: u32, + constant: u32, + global: u32, + local: u32, +}; + fn deinit(sema: *Sema) void { const gpa = sema.gpa; - sema.bytecode.deinit(gpa); sema.constants.deinit(gpa); + sema.constant_map.deinit(gpa); + sema.globals.deinit(gpa); sema.knots.deinit(gpa); sema.* = undefined; } @@ -29,51 +41,290 @@ fn fail(_: *Sema, message: []const u8) InnerError { @panic(message); } -fn resolveIndex(sema: *Sema, index: Ir.Inst.Index) Ir.Inst { - return sema.ir.instructions[@intFromEnum(index)]; -} - -fn emitByte(sema: *Sema, byte: u8) !void { +fn getConstant(sema: *Sema, data: CompiledStory.Constant) !Ref { const gpa = sema.gpa; - return sema.bytecode.append(gpa, byte); + + if (sema.constant_map.get(data)) |index| { + return .{ .constant = index }; + } else { + const index = sema.constants.items.len; + try sema.constants.append(gpa, data); + try sema.constant_map.put(gpa, data, @intCast(index)); + return .{ .constant = @intCast(index) }; + } } -fn emitByteOp(sema: *Sema, op: Story.Opcode) !void { - return emitByte(sema, @intFromEnum(op)); -} - -fn emitConstOp(sema: *Sema, op: Story.Opcode, arg: u8) !void { +fn addGlobal(sema: *Sema, name: Ir.NullTerminatedString) !Ref { const gpa = sema.gpa; - if (arg >= std.math.maxInt(u8)) return error.TooManyConstants; - try sema.bytecode.ensureUnusedCapacity(gpa, 2); - sema.bytecode.appendAssumeCapacity(@intFromEnum(op)); - sema.bytecode.appendAssumeCapacity(arg); + const interned = try sema.getConstant(.{ .string = name }); + try sema.globals.append(gpa, interned.constant); + return .{ .global = interned.constant }; } -fn emitJumpOp(sema: *Sema, op: Story.Opcode) error{OutOfMemory}!usize { - const gpa = sema.gpa; - try sema.bytecode.ensureUnusedCapacity(gpa, 3); - sema.bytecode.appendAssumeCapacity(@intFromEnum(op)); - sema.bytecode.appendAssumeCapacity(0xff); - sema.bytecode.appendAssumeCapacity(0xff); - return sema.bytecode.items.len - 2; +fn getGlobal(sema: *Sema, name: Ir.NullTerminatedString) !Ref { + const interned = try sema.getConstant(.{ .string = name }); + for (sema.ir.globals) |global| { + if (name == global.name) { + return .{ .global = interned.constant }; + } + } + return sema.fail("unknown global variable"); } -fn makeConstant(sema: *Sema, data: CompiledStory.Constant) !usize { +fn irInteger(sema: *Sema, inst: Ir.Inst.Index) InnerError!Ref { + const data = sema.ir.instructions[@intFromEnum(inst)].data.integer; + return sema.getConstant(.{ .integer = data.value }); +} + +fn irString(sema: *Sema, inst: Ir.Inst.Index) InnerError!Ref { + const data = sema.ir.instructions[@intFromEnum(inst)].data.string; + return sema.getConstant(.{ .string = data.start }); +} + +fn irUnary( + sema: *Sema, + chunk: *Chunk, + inst: Ir.Inst.Index, + op: Story.Opcode, +) InnerError!Ref { + const data = sema.ir.instructions[@intFromEnum(inst)].data.un; + const lhs = chunk.resolveInst(data.lhs); + _ = try chunk.doLoad(lhs); + return chunk.addByteOp(op); +} + +fn irBinary( + sema: *Sema, + chunk: *Chunk, + inst: Ir.Inst.Index, + op: Story.Opcode, +) InnerError!Ref { + const data = sema.ir.instructions[@intFromEnum(inst)].data.bin; + const lhs = chunk.resolveInst(data.lhs); + const rhs = chunk.resolveInst(data.rhs); + + _ = try chunk.doLoad(lhs); + _ = try chunk.doLoad(rhs); + return chunk.addByteOp(op); +} + +fn irAlloc(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) InnerError!Ref { + const local_index = chunk.knot.stack_size; + // TODO: Add constraints on how many temporaries we can have. + // max(u8) or max(u16) are most likey appropriate. + chunk.knot.stack_size += 1; + return .{ .local = local_index }; +} + +fn irStore(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void { + const data = sema.ir.instructions[@intFromEnum(inst)].data.bin; + const lhs = chunk.resolveInst(data.lhs); + const rhs = chunk.resolveInst(data.rhs); + _ = try chunk.doLoad(rhs); + switch (lhs) { + .bool_true, .bool_false => unreachable, // TODO: "Cannot assign to boolean" + .none => unreachable, + .constant => |_| unreachable, // TODO: "Cannot assign to constant" + .global => |id| _ = try chunk.addConstOp(.store_global, @intCast(id)), + .local => |id| _ = try chunk.addConstOp(.store, @intCast(id)), + .index => unreachable, + } + _ = try chunk.addByteOp(.pop); +} + +// TODO: Check what the target is! +fn irLoad(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref { + const data = sema.ir.instructions[@intFromEnum(inst)].data.un; + const lhs = chunk.resolveInst(data.lhs); + return chunk.doLoad(lhs); +} + +fn irCondBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref { + const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; + const extra = sema.ir.extraData(Ir.Inst.CondBr, data.payload_index); + const then_body = sema.ir.bodySlice(extra.end, extra.data.then_body_len); + const else_body = sema.ir.bodySlice(extra.end + then_body.len, extra.data.else_body_len); + const else_label = try chunk.addLabel(); + const end_label = try chunk.addLabel(); + const condition = chunk.resolveInst(extra.data.condition); + + _ = try chunk.doLoad(condition); + try chunk.addFixup(.jmp_f, else_label); + _ = try chunk.addByteOp(.pop); + try blockBodyInner(sema, chunk, then_body); + + try chunk.addFixup(.jmp, end_label); + chunk.setLabel(else_label); + _ = try chunk.addByteOp(.pop); + + try blockBodyInner(sema, chunk, else_body); + chunk.setLabel(end_label); + return .none; +} + +fn irBreak(sema: *Sema, inst: Ir.Inst.Index) InnerError!void { + _ = sema; + _ = inst; +} + +fn irBlock(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void { + const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; + const extra = sema.ir.extraData(Ir.Inst.Block, data.payload_index); + const body = sema.ir.bodySlice(extra.end, extra.data.body_len); + return blockBodyInner(sema, chunk, body); +} + +fn irContentPush(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref { + const data = sema.ir.instructions[@intFromEnum(inst)].data.un; + const lhs = chunk.resolveInst(data.lhs); + _ = try chunk.doLoad(lhs); + return chunk.addByteOp(.stream_push); +} + +fn irContentFlush(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) InnerError!Ref { + return chunk.addByteOp(.stream_flush); +} + +fn irDeclRef(sema: *Sema, _: *Chunk, inst: Ir.Inst.Index) InnerError!Ref { + const data = sema.ir.instructions[@intFromEnum(inst)].data.string; + return sema.getGlobal(data.start); +} + +fn irDeclVar( + sema: *Sema, + chunk: *Chunk, + name: Ir.NullTerminatedString, + inst: Ir.Inst.Index, +) InnerError!void { + const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; + const extra = sema.ir.extraData(Ir.Inst.Block, data.payload_index); + const body = sema.ir.bodySlice(extra.end, extra.data.body_len); + try blockBodyInner(sema, chunk, body); + // FIXME: hack + { + const last_inst = body[body.len - 1].toRef(); + const val = chunk.resolveInst(last_inst); + _ = try chunk.doLoad(val); + } + const global = try sema.addGlobal(name); + _ = try chunk.addConstOp(.store_global, @intCast(global.global)); + _ = try chunk.addByteOp(.pop); +} + +fn irDeclKnot( + sema: *Sema, + name_ref: Ir.NullTerminatedString, + inst: Ir.Inst.Index, +) InnerError!void { const gpa = sema.gpa; - const const_index = sema.constants.items.len; - try sema.constants.append(gpa, data); - return const_index; + const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; + const extra = sema.ir.extraData(Ir.Inst.Knot, data.payload_index); + + var knot: CompiledStory.Knot = .{ + .name = name_ref, + .arity = 0, + .stack_size = 0, + }; + var chunk: Chunk = .{ + .sema = sema, + .knot = &knot, + }; + defer chunk.fixups.deinit(gpa); + defer chunk.labels.deinit(gpa); + defer chunk.inst_map.deinit(gpa); + + const body = sema.ir.bodySlice(extra.end, extra.data.body_len); + try blockBodyInner(sema, &chunk, body); + _ = try chunk.addByteOp(.exit); + try chunk.resolveLabels(); + try sema.knots.append(gpa, knot); +} + +fn irDeclaration(sema: *Sema, parent_chunk: ?*Chunk, inst: Ir.Inst.Index) !void { + const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; + const extra = sema.ir.extraData(Ir.Inst.Declaration, data.payload_index).data; + const value_data = sema.ir.instructions[@intFromEnum(extra.value)]; + switch (value_data.tag) { + .decl_var => try irDeclVar(sema, parent_chunk.?, extra.name, extra.value), + .decl_knot => try irDeclKnot(sema, extra.name, extra.value), + else => unreachable, + } +} + +fn blockBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) InnerError!void { + const gpa = sema.gpa; + + for (body) |inst| { + const data = sema.ir.instructions[@intFromEnum(inst)]; + const ref: Ref = switch (data.tag) { + .file => unreachable, + .declaration => { + try irDeclaration(sema, chunk, inst); + continue; + }, + .decl_var => unreachable, // handled in declaration() + .decl_knot => unreachable, // handled in declaration() + .alloc => try irAlloc(sema, chunk, inst), + .store => { + try irStore(sema, chunk, inst); + continue; + }, + .load => try irLoad(sema, chunk, inst), + .add => try irBinary(sema, chunk, inst, .add), + .sub => try irBinary(sema, chunk, inst, .sub), + .mul => try irBinary(sema, chunk, inst, .mul), + .div => try irBinary(sema, chunk, inst, .div), + .mod => try irBinary(sema, chunk, inst, .mod), + .neg => try irUnary(sema, chunk, inst, .neg), + .not => try irUnary(sema, chunk, inst, .not), + .cmp_eq => try irBinary(sema, chunk, inst, .cmp_eq), + .cmp_neq => blk: { + _ = try irBinary(sema, chunk, inst, .cmp_eq); + const tmp = try chunk.addByteOp(.not); + break :blk tmp; + }, + .cmp_lt => try irBinary(sema, chunk, inst, .cmp_lt), + .cmp_lte => try irBinary(sema, chunk, inst, .cmp_lte), + .cmp_gt => try irBinary(sema, chunk, inst, .cmp_gt), + .cmp_gte => try irBinary(sema, chunk, inst, .cmp_gte), + .decl_ref => try irDeclRef(sema, chunk, inst), + .integer => try irInteger(sema, inst), + .string => try irString(sema, inst), + .condbr => try irCondBr(sema, chunk, inst), + .@"break" => { + try irBreak(sema, inst); + continue; + }, + .block => { + try irBlock(sema, chunk, inst); + continue; + }, + .content_push => try irContentPush(sema, chunk, inst), + .content_flush => try irContentFlush(sema, chunk, inst), + }; + try chunk.inst_map.put(gpa, inst, ref); + } +} + +fn file(sema: *Sema, inst: Ir.Inst.Index) !void { + const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; + const extra = sema.ir.extraData(Ir.Inst.Block, data.payload_index); + const body = sema.ir.bodySlice(extra.end, extra.data.body_len); + + // FIXME: We are going to get burned by this if we don't formalize it. + // Adding common constants to the constant pool. + _ = try sema.getConstant(.{ .integer = 0 }); + _ = try sema.getConstant(.{ .integer = 1 }); + + for (body) |body_index| try irDeclaration(sema, null, body_index); } const Chunk = struct { sema: *Sema, - name: Ir.NullTerminatedString, - arity: u32, - stack_size: u32, - stack_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, u32), - labels: std.ArrayListUnmanaged(Label), - fixups: std.ArrayListUnmanaged(Fixup), + knot: *CompiledStory.Knot, + labels: std.ArrayListUnmanaged(Label) = .empty, + fixups: std.ArrayListUnmanaged(Fixup) = .empty, + inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, Ref) = .empty, const dummy_address = 0xffffffff; @@ -86,15 +337,55 @@ const Chunk = struct { relative, absolute, }, - label_index: usize, - code_offset: usize, + label_index: u32, + code_offset: u32, }; + fn addByteOp(chunk: *Chunk, op: Story.Opcode) error{OutOfMemory}!Ref { + const gpa = chunk.sema.gpa; + const bytecode = &chunk.knot.bytecode; + const byte_index = bytecode.items.len; + try bytecode.append(gpa, @intFromEnum(op)); + return .{ .index = @intCast(byte_index) }; + } + + fn addConstOp(chunk: *Chunk, op: Story.Opcode, arg: u8) error{OutOfMemory}!Ref { + const gpa = chunk.sema.gpa; + const bytecode = &chunk.knot.bytecode; + const byte_index = bytecode.items.len; + try bytecode.ensureUnusedCapacity(gpa, 2); + bytecode.appendAssumeCapacity(@intFromEnum(op)); + bytecode.appendAssumeCapacity(arg); + return .{ .index = @intCast(byte_index) }; + } + + fn addJumpOp(chunk: *Chunk, op: Story.Opcode) error{OutOfMemory}!Ref { + const gpa = chunk.sema.gpa; + const bytecode = &chunk.knot.bytecode; + try bytecode.ensureUnusedCapacity(gpa, 3); + bytecode.appendAssumeCapacity(@intFromEnum(op)); + bytecode.appendAssumeCapacity(0xff); + bytecode.appendAssumeCapacity(0xff); + return .{ .index = @intCast(bytecode.items.len - 2) }; + } + + fn resolveInst(chunk: *Chunk, ref: Ir.Inst.Ref) Ref { + if (ref.toIndex()) |index| { + return chunk.inst_map.get(index).?; + } + switch (ref) { + .bool_true => return .bool_true, + .bool_false => return .bool_false, + else => return .{ .constant = @intFromEnum(ref) }, + } + } + fn addFixup(chunk: *Chunk, op: Story.Opcode, label: usize) !void { + const code_ref = try chunk.addJumpOp(op); return chunk.fixups.append(chunk.sema.gpa, .{ .mode = .relative, - .label_index = label, - .code_offset = try chunk.sema.emitJumpOp(op), + .label_index = @intCast(label), + .code_offset = code_ref.index, }); } @@ -107,7 +398,7 @@ const Chunk = struct { } fn setLabel(chunk: *Chunk, label_index: usize) void { - const code_offset = chunk.sema.bytecode.items.len; + const code_offset = chunk.knot.bytecode.items.len; assert(label_index <= chunk.labels.items.len); const label_data = &chunk.labels.items[label_index]; @@ -117,7 +408,7 @@ const Chunk = struct { fn resolveLabels(chunk: *Chunk) !void { const start_index = 0; const end_index = chunk.fixups.items.len; - const bytecode = &chunk.sema.bytecode; + const bytecode = &chunk.knot.bytecode; for (chunk.fixups.items[start_index..end_index]) |fixup| { const label = chunk.labels.items[fixup.label_index]; @@ -135,244 +426,44 @@ const Chunk = struct { bytecode.items[fixup.code_offset + 1] = @intCast(target_offset & 0xff); } } + + fn doLoad(chunk: *Chunk, ref: Ref) InnerError!Ref { + const gpa = chunk.sema.gpa; + switch (ref) { + .bool_true => return chunk.addByteOp(.true), + .bool_false => return chunk.addByteOp(.false), + .none => return ref, + .constant => |id| { + // TODO: This isn't great. New constant indexes are + // created each time. + const ref_const = chunk.knot.constants.items.len; + try chunk.knot.constants.append(gpa, id); + return chunk.addConstOp(.load_const, @intCast(ref_const)); + }, + .global => |id| { + // TODO: This isn't great. New constant indexes are + // created each time. + const ref_const = chunk.knot.constants.items.len; + try chunk.knot.constants.append(gpa, id); + return chunk.addConstOp(.load_global, @intCast(ref_const)); + }, + .local => |id| return chunk.addConstOp(.load, @intCast(id)), + .index => return ref, + } + } }; -fn integerInst(sema: *Sema, inst: Ir.Inst) InnerError!void { - const int_const = try sema.makeConstant(.{ - .integer = inst.data.integer.value, - }); - return emitConstOp(sema, .load_const, @intCast(int_const)); -} - -fn stringInst(sema: *Sema, inst: Ir.Inst) InnerError!void { - const str_const = try sema.makeConstant(.{ - .string = inst.data.string.start, - }); - return emitConstOp(sema, .load_const, @intCast(str_const)); -} - -fn unaryInst(sema: *Sema, _: Ir.Inst, op: Story.Opcode) InnerError!void { - return emitByteOp(sema, op); -} - -fn binaryInst(sema: *Sema, _: Ir.Inst, op: Story.Opcode) InnerError!void { - return emitByteOp(sema, op); -} - -fn irContentPush(sema: *Sema, _: Ir.Inst) InnerError!void { - return emitByteOp(sema, .stream_push); -} - -fn irContentFlush(sema: *Sema, _: Ir.Inst) InnerError!void { - return emitByteOp(sema, .stream_flush); -} - -fn allocLocal(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void { - const gpa = sema.gpa; - // TODO: Add constraints on how many temporaries we can have. - // max(u8) or max(u16) are most likey appropriate. - try chunk.stack_map.put(gpa, inst, chunk.stack_size); - chunk.stack_size += 1; -} - -fn storeLocal(sema: *Sema, chunk: *Chunk, inst: Ir.Inst) InnerError!void { - const temp_inst = inst.data.bin.lhs; - const stack_offset = chunk.stack_map.get(temp_inst).?; - try emitConstOp(sema, .store, @intCast(stack_offset)); - try emitByteOp(sema, .pop); -} - -fn loadLocal(sema: *Sema, chunk: *Chunk, inst: Ir.Inst) InnerError!void { - const temp_inst = inst.data.un.lhs; - const stack_offset = chunk.stack_map.get(temp_inst).?; - try emitConstOp(sema, .load, @intCast(stack_offset)); -} - -fn irCondBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst) InnerError!void { - const payload_node = inst.data.payload; - const extra = sema.ir.extraData(Ir.Inst.CondBr, payload_node.payload_index); - const then_body = sema.ir.bodySlice(extra.end, extra.data.then_body_len); - const else_body = sema.ir.bodySlice(extra.end + then_body.len, extra.data.else_body_len); - const else_label = try chunk.addLabel(); - const end_label = try chunk.addLabel(); - - try chunk.addFixup(.jmp_f, else_label); - try emitByteOp(sema, .pop); - for (then_body) |body_index| try compileInst(sema, chunk, body_index); - - try chunk.addFixup(.jmp, end_label); - chunk.setLabel(else_label); - try emitByteOp(sema, .pop); - - for (else_body) |body_index| try compileInst(sema, chunk, body_index); - chunk.setLabel(end_label); -} - -fn irBreak(sema: *Sema, inst: Ir.Inst) InnerError!void { - _ = sema; - _ = inst; -} - -fn irBlock(sema: *Sema, chunk: *Chunk, block_inst: Ir.Inst) InnerError!void { - const payload_node = block_inst.data.payload; - const extra = sema.ir.extraData(Ir.Inst.Block, payload_node.payload_index); - const body = sema.ir.bodySlice(extra.end, extra.data.body_len); - for (body) |body_index| try compileInst(sema, chunk, body_index); -} - -fn declRef(sema: *Sema, inst: Ir.Inst) InnerError!void { - const ir = sema.ir; - const str = inst.data.string.start; - for (ir.globals) |global| { - if (str == global.name) { - const constant_index = try sema.makeConstant(.{ .string = str }); - return emitConstOp(sema, .load_global, @intCast(constant_index)); - } - } - return sema.fail("unknown global variable"); -} - -fn declVar( - sema: *Sema, - chunk: *Chunk, - name: Ir.NullTerminatedString, - inst: Ir.Inst, -) InnerError!void { - const ir = sema.ir; - const payload_index = inst.data.payload.payload_index; - const extra = ir.extraData(Ir.Inst.Block, payload_index); - const body = ir.bodySlice(extra.end, extra.data.body_len); - for (body) |body_index| try compileInst(sema, chunk, body_index); - for (ir.globals) |global| { - if (name == global.name) { - const constant_index = try sema.makeConstant(.{ .string = name }); - return emitConstOp(sema, .store_global, @intCast(constant_index)); - } - } - - return sema.fail("unknown global variable"); -} - -fn declKnot(sema: *Sema, name_ref: Ir.NullTerminatedString, inst: Ir.Inst) InnerError!void { - const gpa = sema.gpa; - const ir = sema.ir; - const extra = ir.extraData(Ir.Inst.Knot, inst.data.payload.payload_index); - const bytecode_start = sema.bytecode.items.len; - const const_start = sema.constants.items.len; - - var chunk: Chunk = .{ - .sema = sema, - .name = name_ref, - .arity = 0, - .stack_size = 0, - .stack_map = .empty, - .fixups = .empty, - .labels = .empty, - }; - defer chunk.stack_map.deinit(gpa); - defer chunk.fixups.deinit(gpa); - defer chunk.labels.deinit(gpa); - - const body_slice = ir.bodySlice(extra.end, extra.data.body_len); - for (body_slice) |body_inst| try compileInst(sema, &chunk, body_inst); - - try emitByteOp(sema, .exit); - try chunk.resolveLabels(); - - try sema.knots.append(gpa, .{ - .name_ref = name_ref, - .arity = chunk.arity, - .stack_size = chunk.stack_size, - .bytecode = .{ - .start = @intCast(bytecode_start), - .len = @intCast(sema.bytecode.items.len - bytecode_start), - }, - .constants = .{ - .start = @intCast(const_start), - .len = @intCast(sema.constants.items.len - const_start), - }, - }); -} - -fn declaration(sema: *Sema, parent_chunk: ?*Chunk, inst: Ir.Inst) !void { - const ir = sema.ir; - const extra = ir.extraData(Ir.Inst.Declaration, inst.data.payload.payload_index); - const value_inst = sema.resolveIndex(extra.data.value); - const str = extra.data.name; - - switch (value_inst.tag) { - .decl_var => try declVar(sema, parent_chunk.?, str, value_inst), - .decl_knot => try declKnot(sema, str, value_inst), - else => unreachable, - } -} - -fn compileInst(sema: *Sema, chunk: *Chunk, index: Ir.Inst.Index) InnerError!void { - const inst = sema.resolveIndex(index); - switch (inst.tag) { - .file => unreachable, - .declaration => try declaration(sema, chunk, inst), - .decl_var => unreachable, // handled in declaration() - .decl_knot => unreachable, // handled in declaration() - .decl_ref => try declRef(sema, inst), - .condbr => try irCondBr(sema, chunk, inst), - .@"break" => try irBreak(sema, inst), - .block => try irBlock(sema, chunk, inst), - .alloc_local => try allocLocal(sema, chunk, index), - .store_local => try storeLocal(sema, chunk, inst), - .load_local => try loadLocal(sema, chunk, inst), - .true_literal => unreachable, // TODO - .false_literal => unreachable, // TODO - .add => try binaryInst(sema, inst, .add), - .sub => try binaryInst(sema, inst, .sub), - .mul => try binaryInst(sema, inst, .mul), - .div => try binaryInst(sema, inst, .div), - .mod => try binaryInst(sema, inst, .mod), - .neg => try unaryInst(sema, inst, .neg), - .not => try unaryInst(sema, inst, .not), - .cmp_eq => try unaryInst(sema, inst, .cmp_eq), - .cmp_neq => { - try unaryInst(sema, inst, .cmp_eq); - try emitByteOp(sema, .not); - }, - .cmp_lt => try unaryInst(sema, inst, .cmp_lt), - .cmp_lte => try unaryInst(sema, inst, .cmp_lte), - .cmp_gt => try unaryInst(sema, inst, .cmp_gt), - .cmp_gte => try unaryInst(sema, inst, .cmp_gte), - .content_push => try irContentPush(sema, inst), - .content_flush => try irContentFlush(sema, inst), - .string => try stringInst(sema, inst), - .integer => try integerInst(sema, inst), - } -} - -fn file(sema: *Sema, inst: Ir.Inst) !void { - const extra = sema.ir.extraData(Ir.Inst.Block, inst.data.payload.payload_index); - const body = sema.ir.bodySlice(extra.end, extra.data.body_len); - for (body) |body_index| { - const body_inst = sema.resolveIndex(body_index); - assert(body_inst.tag == .declaration); - try declaration(sema, null, body_inst); - } -} - pub const CompiledStory = struct { knots: []Knot, constants: []Constant, - bytecode: []u8, + globals: []u32, pub const Knot = struct { - name_ref: Ir.NullTerminatedString, + name: Ir.NullTerminatedString, arity: u32, stack_size: u32, - constants: struct { - start: u32, - len: u32, - }, - bytecode: struct { - start: u32, - len: u32, - }, + constants: std.ArrayListUnmanaged(u32) = .empty, + bytecode: std.ArrayListUnmanaged(u8) = .empty, }; pub const Constant = union(enum) { @@ -381,58 +472,57 @@ pub const CompiledStory = struct { }; pub fn deinit(self: *CompiledStory, gpa: std.mem.Allocator) void { + for (self.knots) |*knot| { + knot.constants.deinit(gpa); + knot.bytecode.deinit(gpa); + } + gpa.free(self.knots); - gpa.free(self.bytecode); + gpa.free(self.globals); gpa.free(self.constants); self.* = undefined; } - fn bytecodeSlice(self: CompiledStory, knot: *const Knot) []const u8 { - return self.bytecode[knot.bytecode.start..][0..knot.bytecode.len]; - } - - fn constantsSlice(self: CompiledStory, knot: *const Knot) []const Constant { - return self.constants[knot.constants.start..][0..knot.constants.len]; - } - pub fn buildRuntime( self: *CompiledStory, gpa: std.mem.Allocator, ir: Ir, story: *Story, ) !void { - for (self.knots) |compiled_knot| { - var constant_pool: std.ArrayListUnmanaged(*Object) = .empty; - try constant_pool.ensureUnusedCapacity(gpa, compiled_knot.constants.len); - defer constant_pool.deinit(gpa); + const constants_pool = &story.constants_pool; + try constants_pool.ensureUnusedCapacity(gpa, self.constants.len); + try story.paths.ensureUnusedCapacity(gpa, self.knots.len); + try story.globals.ensureUnusedCapacity(gpa, @intCast(self.globals.len)); - const constants_slice = self.constantsSlice(&compiled_knot); - for (constants_slice) |constant| { - switch (constant) { - .integer => |value| { - const object: *Object.Number = try .create(story, .{ - .integer = @intCast(value), - }); - constant_pool.appendAssumeCapacity(&object.base); - }, - .string => |ref| { - const bytes = ir.nullTerminatedString(ref); - const object: *Object.String = try .create(story, bytes); - constant_pool.appendAssumeCapacity(&object.base); - }, - } + for (self.constants) |constant| { + switch (constant) { + .integer => |value| { + const object: *Object.Number = try .create(story, .{ + .integer = @intCast(value), + }); + constants_pool.appendAssumeCapacity(&object.base); + }, + .string => |ref| { + const bytes = ir.nullTerminatedString(ref); + const object: *Object.String = try .create(story, bytes); + constants_pool.appendAssumeCapacity(&object.base); + }, } - - const bytecode_slice = self.bytecodeSlice(&compiled_knot); - const chunk_name = ir.nullTerminatedString(compiled_knot.name_ref); + } + for (self.globals) |global_index| { + const str = self.constants[global_index]; + const name_bytes = ir.nullTerminatedString(str.string); + story.globals.putAssumeCapacity(name_bytes, null); + } + for (self.knots) |*knot| { const runtime_chunk: *Object.ContentPath = try .create(story, .{ - .name = try .create(story, chunk_name), - .arity = @intCast(compiled_knot.arity), - .locals_count = @intCast(compiled_knot.stack_size - compiled_knot.arity), - .const_pool = try constant_pool.toOwnedSlice(gpa), - .bytes = try gpa.dupe(u8, bytecode_slice), + .name = try .create(story, ir.nullTerminatedString(knot.name)), + .arity = @intCast(knot.arity), + .locals_count = @intCast(knot.stack_size - knot.arity), + .const_pool = try knot.constants.toOwnedSlice(gpa), + .bytes = try knot.bytecode.toOwnedSlice(gpa), }); - try story.paths.append(gpa, &runtime_chunk.base); + story.paths.appendAssumeCapacity(&runtime_chunk.base); } } }; @@ -444,10 +534,10 @@ pub fn compile(gpa: std.mem.Allocator, ir: *const Ir) !CompiledStory { }; defer sema.deinit(); - try file(&sema, ir.instructions[0]); + try file(&sema, @enumFromInt(0)); return .{ - .bytecode = try sema.bytecode.toOwnedSlice(gpa), .constants = try sema.constants.toOwnedSlice(gpa), + .globals = try sema.globals.toOwnedSlice(gpa), .knots = try sema.knots.toOwnedSlice(gpa), }; } diff --git a/src/Story.zig b/src/Story.zig index 739de64..b91d8c1 100644 --- a/src/Story.zig +++ b/src/Story.zig @@ -15,6 +15,7 @@ is_exited: bool = false, can_advance: bool = false, choice_index: usize = 0, current_choices: std.ArrayListUnmanaged(Choice) = .empty, +constants_pool: std.ArrayListUnmanaged(*Object) = .empty, globals: std.StringHashMapUnmanaged(?*Object) = .empty, paths: std.ArrayListUnmanaged(*Object) = .empty, stack: std.ArrayListUnmanaged(?*Object) = .empty, @@ -101,6 +102,7 @@ pub fn deinit(story: *Story) void { } story.current_choices.deinit(gpa); + story.constants_pool.deinit(gpa); story.globals.deinit(gpa); story.paths.deinit(gpa); story.stack.deinit(gpa); @@ -109,6 +111,24 @@ pub fn deinit(story: *Story) void { pub fn dump(story: *Story, writer: *std.Io.Writer) !void { const story_dumper: Dumper = .{ .story = story, .writer = writer }; + + try writer.writeAll("=== Constants ===\n"); + for (story.constants_pool.items) |global_constant| { + try story_dumper.dumpObject(global_constant); + try writer.writeAll("\n"); + } + + try writer.writeAll("\n"); + try writer.writeAll("=== Globals ===\n"); + + var global_iter = story.globals.iterator(); + while (global_iter.next()) |entry| { + try writer.print("{s} => ...\n", .{entry.key_ptr.*}); + } + + try writer.writeAll("\n"); + try writer.writeAll("=== Knots ===\n"); + for (story.paths.items) |path_object| { try story_dumper.dump(@ptrCast(path_object)); } @@ -152,8 +172,7 @@ fn currentFrame(vm: *Story) *CallFrame { fn peekStack(vm: *Story, offset: usize) ?*Object { const stack_top = vm.stack.items.len; - assert(stack_top > offset); - assert(stack_top != 0); + if (stack_top <= offset) return null; return vm.stack.items[stack_top - offset - 1]; } @@ -171,10 +190,11 @@ fn popStack(vm: *Story) ?*Object { return vm.stack.pop() orelse unreachable; } -fn getConstant(_: *Story, frame: *CallFrame, offset: u8) !*Object { - const constant_pool = frame.callee.const_pool; - if (offset >= constant_pool.len) return error.InvalidArgument; - return constant_pool[offset]; +fn getConstant(story: *Story, frame: *CallFrame, offset: u8) !*Object { + if (offset >= frame.callee.const_pool.len) return error.InvalidArgument; + + const constant_index = frame.callee.const_pool[offset]; + return story.constants_pool.items[constant_index]; } fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?*Object { @@ -380,14 +400,17 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) { frame.ip += 2; }, .stream_push => { - const arg_object = vm.popStack(); - if (arg_object) |object| { + // FIXME: This should be more strict. + // Its not because theres a bug in when these instructions are + // emitted. + if (vm.peekStack(0)) |object| { const string_object = try Object.String.fromObject(vm, object); const string_bytes = string_object.bytes[0..string_object.length]; try stream_writer.writer.writeAll(string_bytes); - } else { - return error.InvalidArgument; - } + _ = vm.popStack(); + } //else { + //return error.InvalidArgument; + //} frame.ip += 1; }, diff --git a/src/Story/Dumper.zig b/src/Story/Dumper.zig index 3f119d2..ffb7771 100644 --- a/src/Story/Dumper.zig +++ b/src/Story/Dumper.zig @@ -15,8 +15,10 @@ fn dumpSimpleInst(d: Dumper, offset: usize, op: Opcode) !usize { fn dumpByteInst(d: Dumper, context: *const Object.ContentPath, offset: usize, op: Opcode) !usize { const arg = context.bytes[offset + 1]; if (op == .load_const) { + const constant_index = context.const_pool[arg]; + const global_constant = d.story.constants_pool.items[constant_index]; try d.writer.print("{s} {d} (", .{ @tagName(op), arg }); - try d.dumpObject(context.const_pool[arg]); + try d.dumpObject(global_constant); try d.writer.print(")\n", .{}); } else { try d.writer.print("{s} {x}\n", .{ @tagName(op), arg }); @@ -31,7 +33,9 @@ fn dumpGlobalInst( op: Opcode, ) !usize { const arg = context.bytes[offset + 1]; - const global_name: *Object.String = @ptrCast(context.const_pool[arg]); + const constant_index = context.const_pool[arg]; + const global_constant = d.story.constants_pool.items[constant_index]; + const global_name: *Object.String = @ptrCast(global_constant); const name_bytes = global_name.bytes[0..global_name.length]; try d.writer.print("{s} {x} '{s}'\n", .{ @tagName(op), arg, name_bytes }); diff --git a/src/Story/object.zig b/src/Story/object.zig index 46e7d85..dc28d56 100644 --- a/src/Story/object.zig +++ b/src/Story/object.zig @@ -264,14 +264,14 @@ pub const Object = struct { // TODO: Rename this to stack size. locals_count: usize, // TODO: Rename this to constant_pool. - const_pool: []*Object, + const_pool: []u32, bytes: []const u8, pub const CreateOptions = struct { name: *Object.String, arity: usize, locals_count: usize, - const_pool: []*Object, + const_pool: []u32, bytes: []const u8, };