From 236acc7d60d507247fab21a5eb2cf3aefa8162cb Mon Sep 17 00:00:00 2001 From: Brett Broadhurst Date: Wed, 1 Apr 2026 21:13:36 -0600 Subject: [PATCH] fix: call frame handling, logical short circuiting --- src/AstGen.zig | 115 +++++++++++++++++++++++------- src/Ir.zig | 23 ++++-- src/Sema.zig | 80 +++++++++++++++------ src/Story.zig | 165 +++++++++++++++++++++---------------------- src/Story/Dumper.zig | 17 ++++- src/Story/Object.zig | 5 +- src/compile.zig | 4 ++ src/print_ir.zig | 51 ++++++++----- 8 files changed, 301 insertions(+), 159 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 5895b65..f40ddf3 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -423,19 +423,20 @@ const GenIr = struct { } fn addBreak( - gen: *GenIr, + gi: *GenIr, tag: Ir.Inst.Tag, node: *const Ast.Node, block_inst: Ir.Inst.Index, + operand: Ir.Inst.Ref, ) !Ir.Inst.Ref { - const gpa = gen.astgen.gpa; const extra_len = @typeInfo(Ir.Inst.Break).@"struct".fields.len; - try gen.astgen.extra.ensureUnusedCapacity(gpa, extra_len); + try gi.astgen.extra.ensureUnusedCapacity(gi.astgen.gpa, extra_len); - const extra_index = gen.astgen.addExtraAssumeCapacity( - Ir.Inst.Break{ .block_inst = block_inst }, - ); - return gen.addPayloadNodeWithIndex(tag, node, extra_index); + const extra_index = gi.astgen.addExtraAssumeCapacity(Ir.Inst.Break{ + .operand = operand, + .block_inst = block_inst, + }); + return gi.addPayloadNodeWithIndex(tag, node, extra_index); } fn makePayloadNode(self: *GenIr, tag: Ir.Inst.Tag) !Ir.Inst.Index { @@ -450,6 +451,23 @@ const GenIr = struct { return inst_index; } + /// Assumes nothing stacked on `gz`. Unstacks `gz`. + fn setBoolBrBody(gi: *GenIr, bool_br: Ir.Inst.Index, bool_br_lhs: Ir.Inst.Ref) !void { + const astgen = gi.astgen; + const body = gi.instructionsSlice(); + try astgen.extra.ensureUnusedCapacity( + astgen.gpa, + @typeInfo(Ir.Inst.BoolBr).@"struct".fields.len + body.len, + ); + const data = &astgen.instructions.items[@intFromEnum(bool_br)].data; + data.payload.extra_index = astgen.addExtraAssumeCapacity(Ir.Inst.BoolBr{ + .lhs = bool_br_lhs, + .body_len = @intCast(body.len), + }); + astgen.appendBlockBody(body); + gi.unstack(); + } + fn setBlockBody(self: *GenIr, inst: Ir.Inst.Index) !void { const gpa = self.astgen.gpa; const body = self.instructionsSlice(); @@ -644,6 +662,31 @@ fn binaryOp( return gi.addBinaryNode(op, lhs, rhs); } +fn boolBinaryOp( + gi: *GenIr, + scope: *Scope, + node: *const Ast.Node, + ir_tag: Ir.Inst.Tag, +) InnerError!Ir.Inst.Ref { + const data = node.data.bin; + const lhs_node = data.lhs.?; + const rhs_node = data.rhs.?; + + const lhs = try expr(gi, scope, lhs_node); + const bool_br = (try gi.addPayloadNodeWithIndex(ir_tag, node, undefined)).toIndex().?; + + var rhs_block = gi.makeSubBlock(); + defer rhs_block.unstack(); + const rhs = try expr(&rhs_block, scope, rhs_node); + if (!rhs_block.endsWithNoReturn()) { + _ = try rhs_block.addBreak(.break_inline, rhs_node, bool_br, rhs); + } + try rhs_block.setBoolBrBody(bool_br, lhs); + + const block_ref = bool_br.toRef(); + return block_ref; +} + fn parseNumberLiteral(bytes: []const u8) union(enum) { int: i64, float: f64, @@ -731,8 +774,8 @@ fn expr(gi: *GenIr, scope: *Scope, optional_node: ?*const Ast.Node) InnerError!I .mod_expr => return binaryOp(gi, scope, node, .mod), .negate_expr => return unaryOp(gi, scope, node, .neg), .logical_not_expr => return unaryOp(gi, scope, node, .not), - .logical_and_expr => return binaryOp(gi, scope, node, .bool_and), - .logical_or_expr => return binaryOp(gi, scope, node, .bool_or), + .logical_and_expr => return boolBinaryOp(gi, scope, node, .bool_br_and), + .logical_or_expr => return boolBinaryOp(gi, scope, node, .bool_br_or), .logical_equality_expr => return binaryOp(gi, scope, node, .cmp_eq), .logical_inequality_expr => return binaryOp(gi, scope, node, .cmp_neq), .logical_greater_expr => return binaryOp(gi, scope, node, .cmp_gt), @@ -819,13 +862,13 @@ fn inlineIfStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir. } } if (!then_block.endsWithNoReturn()) { - _ = try then_block.addBreak(.@"break", node, block); + _ = try then_block.addBreak(.@"break", node, block, .void); } var else_block = gi.makeSubBlock(); defer else_block.unstack(); - _ = try else_block.addBreak(.@"break", node, block); + _ = try else_block.addBreak(.@"break", node, block, .void); try setCondBrPayload(condbr, cond_inst, &then_block, &else_block); return condbr.toRef(); } @@ -885,7 +928,7 @@ fn ifStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.R try blockStmt(&then_block, scope, then_body); if (!then_block.endsWithNoReturn()) { - _ = try then_block.addBreak(.@"break", then_body, block); + _ = try then_block.addBreak(.@"break", then_body, block, .void); } var else_block = gi.makeSubBlock(); @@ -898,11 +941,11 @@ fn ifStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.R try blockStmt(&else_block, scope, else_body); if (!else_block.endsWithNoReturn()) { - _ = try else_block.addBreak(.@"break", else_body, block); + _ = try else_block.addBreak(.@"break", else_body, block, .void); } } } else { - _ = try else_block.addBreak(.@"break", then_body, block); + _ = try else_block.addBreak(.@"break", then_body, block, .void); } try setCondBrPayload(condbr, cond_inst, &then_block, &else_block); @@ -938,7 +981,7 @@ fn ifChain(gi: *GenIr, scope: *Scope, branch_list: []const *Ast.Node) InnerError try blockStmt(&then_block, scope, body); if (!then_block.endsWithNoReturn()) { - _ = try then_block.addBreak(.@"break", body, block_inst); + _ = try then_block.addBreak(.@"break", body, block_inst, .void); } var else_block = gi.makeSubBlock(); @@ -1006,7 +1049,7 @@ fn switchStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.In _ = try blockStmt(&case_block, scope, case_data.rhs.?); if (!case_block.endsWithNoReturn()) { - _ = try case_block.addBreak(.@"break", case_stmt, switch_br); + _ = try case_block.addBreak(.@"break", case_stmt, switch_br, .void); } const body = case_block.instructionsSlice(); @@ -1032,10 +1075,10 @@ fn switchStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.In const else_data = else_branch.data.bin; _ = try blockStmt(&else_block, scope, else_data.rhs.?); if (!else_block.endsWithNoReturn()) { - _ = try else_block.addBreak(.@"break", else_branch, switch_br); + _ = try else_block.addBreak(.@"break", else_branch, switch_br, .void); } } else { - _ = try else_block.addBreak(.@"break", node, switch_br); + _ = try else_block.addBreak(.@"break", node, switch_br, .void); } const else_body = else_block.instructionsSlice(); @@ -1288,14 +1331,18 @@ fn callExpr( const callee_node = data.lhs.?; const callee = try calleeExpr(gi, scope, callee_node); - const scratch_top = astgen.scratch.items.len; - defer astgen.scratch.shrinkRetainingCapacity(scratch_top); - // FIXME: List nodes should not have optional slices. // This hack is an abomination. const arguments: ?[]*Ast.Node = if (data.rhs) |args_node| args_node.data.list.items else null; const args_count = if (arguments) |args| args.len else 0; + const call_index: Ir.Inst.Index = @enumFromInt(astgen.instructions.items.len); + const call_inst = call_index.toRef(); + try gi.astgen.instructions.append(gpa, undefined); + try gi.instructions.append(gpa, call_index); + const scratch_top = astgen.scratch.items.len; + defer astgen.scratch.shrinkRetainingCapacity(scratch_top); + try astgen.scratch.resize(gpa, scratch_top + args_count); var scratch_index = scratch_top; @@ -1304,7 +1351,10 @@ fn callExpr( var arg_block = gi.makeSubBlock(); defer arg_block.unstack(); - _ = try expr(&arg_block, scope, arg); + const arg_ref = try expr(&arg_block, scope, arg); + if (!arg_block.endsWithNoReturn()) { + _ = try arg_block.addBreak(.break_inline, arg, call_index, arg_ref); + } const body = arg_block.instructionsSlice(); try astgen.scratch.ensureUnusedCapacity(gpa, body.len); @@ -1324,7 +1374,13 @@ fn callExpr( if (args_count != 0) { try astgen.extra.appendSlice(gpa, astgen.scratch.items[scratch_top..]); } - return gi.addPayloadNodeWithIndex(tag, callee_node, extra_index); + astgen.instructions.items[@intFromEnum(call_index)] = .{ + .tag = tag, + .data = .{ .payload = .{ + .src_offset = @intCast(node.loc.start), + .extra_index = extra_index, + } }, + }; }, .field => |callee_field| { const tag = if (call == .divert) .field_divert else .field_call; @@ -1336,9 +1392,16 @@ fn callExpr( if (args_count != 0) { try astgen.extra.appendSlice(gpa, astgen.scratch.items[scratch_top..]); } - return gi.addPayloadNodeWithIndex(tag, callee_node, extra_index); + astgen.instructions.items[@intFromEnum(call_index)] = .{ + .tag = tag, + .data = .{ .payload = .{ + .src_offset = @intCast(node.loc.start), + .extra_index = extra_index, + } }, + }; }, } + return call_inst; } fn divertExpr(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void { @@ -1407,7 +1470,7 @@ fn returnStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void { const ret_arg = if (node.data.bin.lhs) |lhs| blk: { const arg_inst = try expr(gi, scope, lhs); break :blk arg_inst; - } else .none; + } else .void; _ = try gi.addUnaryNode(.ret, ret_arg); } @@ -1444,7 +1507,7 @@ fn varDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void { const name_str = try astgen.strFromNode(identifier_node); const var_inst = try decl_block.makePayloadNode(.decl_var); const rvalue_inst = try expr(&decl_block, scope, expr_node); - _ = try decl_block.addBinaryNode(.break_inline, var_inst.toRef(), rvalue_inst); + _ = try decl_block.addBreak(.break_inline, decl_node, var_inst, rvalue_inst); try setDeclVarPayload(var_inst, &decl_block, identifier_node); try setDeclaration(decl_inst, .{ diff --git a/src/Ir.zig b/src/Ir.zig index d025c03..c19363d 100644 --- a/src/Ir.zig +++ b/src/Ir.zig @@ -126,6 +126,7 @@ pub const Inst = struct { pub const Ref = enum(u32) { bool_true, bool_false, + void, none = std.math.maxInt(u32), _, @@ -177,10 +178,6 @@ pub const Inst = struct { /// Uses the `un` union field. not, /// Uses the `bin` union field. - bool_and, - /// Uses the `bin` union field. - bool_or, - /// Uses the `bin` union field. cmp_eq, /// Uses the `bin` union field. cmp_neq, @@ -198,6 +195,14 @@ pub const Inst = struct { float, /// Uses the `str` union field. str, + /// Short-circuiting boolean `and`. `lhs` is a boolean `Ref` and the other operand + /// is a block, which is evaluated if `lhs` is `true`. + /// Uses the `payload` union field. Payload is `BoolBr`. + bool_br_and, + /// Short-circuiting boolean `or`. `lhs` is a boolean `Ref` and the other operand + /// is a block, which is evaluated if `lhs` is `false`. + /// Uses the `payload` union field. Payload is `BoolBr`. + bool_br_or, block, condbr, @"break", @@ -290,7 +295,13 @@ pub const Inst = struct { body_len: u32, }; + pub const BoolBr = struct { + lhs: Ref, + body_len: u32, + }; + pub const Break = struct { + operand: Ref, block_inst: Index, }; @@ -361,8 +372,8 @@ pub const Inst = struct { .mod, .neg, .not, - .bool_and, - .bool_or, + .bool_br_and, + .bool_br_or, .cmp_eq, .cmp_neq, .cmp_gt, diff --git a/src/Sema.zig b/src/Sema.zig index cd55597..7f74975 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14,6 +14,7 @@ module: *compile.Module, ir: Ir, inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, ValueInfo) = .empty, errors: *std.ArrayListUnmanaged(Module.Error), +comptime_break_inst: Ir.Inst.Index = undefined, const InnerError = error{ OutOfMemory, @@ -160,6 +161,10 @@ pub const Builder = struct { constants_map: std.AutoHashMapUnmanaged(InternPool.Index, u8) = .empty, labels: std.ArrayListUnmanaged(Label) = .empty, fixups: std.ArrayListUnmanaged(Fixup) = .empty, + knot_tag: enum { + knot, + function, + }, const Label = struct { code_offset: usize, @@ -495,35 +500,50 @@ fn irBinaryOp( return .stack; } +fn analyzeInlineBody( + sema: *Sema, + builder: *Builder, + body: []const Ir.Inst.Index, +) !ValueInfo { + if (sema.analyzeBodyInner(builder, body, true)) |_| {} else |err| switch (err) { + error.ComptimeBreak => {}, + else => |e| return e, + } + const break_inst = sema.ir.instructions[@intFromEnum(sema.comptime_break_inst)]; + const extra = sema.ir.extraData(Ir.Inst.Break, break_inst.data.payload.extra_index).data; + return sema.resolveInst(extra.operand); +} + fn irLogicalOp( sema: *Sema, builder: *Builder, inst: Ir.Inst.Index, - logical_or: bool, + is_logical_or: bool, ) InnerError!ValueInfo { const ip = &sema.module.intern_pool; - const data = sema.ir.instructions[@intFromEnum(inst)].data.bin; - const lhs = sema.resolveInst(data.lhs); - const rhs = sema.resolveInst(data.rhs); + const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; + const extra = sema.ir.extraData(Ir.Inst.BoolBr, data.extra_index); + const body = sema.ir.bodySlice(extra.end, extra.data.body_len); + const lhs = sema.resolveInst(extra.data.lhs); if (sema.resolveValue(lhs)) |lhs_info| { const lhs_value = lhs_info.unwrap(ip); - if (sema.resolveValue(rhs)) |_| { - return if (logical_or) - if (lhs_value.isTruthy()) lhs else rhs - else if (!lhs_value.isTruthy()) lhs else rhs; + if (is_logical_or and lhs_value.bool) { + const value = ip.getOrPutBool(true); + return .{ .value = value }; + } else if (!is_logical_or and !lhs_value.bool) { + const value = ip.getOrPutBool(false); + return .{ .value = value }; } - - if (logical_or and lhs_value.isTruthy()) return lhs; - if (!logical_or and !lhs_value.isTruthy()) return lhs; - try builder.ensureLoad(rhs); - return .none; + return try sema.analyzeInlineBody(builder, body); } const else_label = try builder.addLabel(); try builder.ensureLoad(lhs); - try builder.addFixup(if (logical_or) .jmp_t else .jmp_f, else_label); + try builder.addFixup(if (is_logical_or) .jmp_t else .jmp_f, else_label); try builder.addByteOp(.pop); + + const rhs = try sema.analyzeInlineBody(builder, body); try builder.ensureLoad(rhs); builder.setLabel(else_label); return .none; @@ -768,15 +788,25 @@ fn irChoiceBr(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!vo fn irRet(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void { const data = sema.ir.instructions[@intFromEnum(inst)].data.un; - if (data.lhs.toIndexAllowNone()) |index| { - const value = sema.inst_map.get(index).?; - if (value != .none) try builder.ensureLoad(value); + const lhs = sema.resolveInst(data.lhs); + if (lhs != .none) { + try builder.ensureLoad(lhs); + } else { + try builder.addByteOp(.stream_glue); } try builder.addByteOp(.ret); } fn irImplicitRet(_: *Sema, builder: *Builder, _: Ir.Inst.Index) InnerError!void { - try builder.addByteOp(.exit); + switch (builder.knot_tag) { + .knot => { + try builder.addByteOp(.exit); + }, + .function => { + try builder.addByteOp(.stream_glue); + try builder.addByteOp(.ret); + }, + } } fn irCall( @@ -823,7 +853,7 @@ fn irCall( const arg_end = sema.ir.extra[extra.end + i]; defer arg_start = arg_end; const arg_body = body[arg_start..arg_end]; - const arg_value = try analyzeBodyInner(sema, builder, @ptrCast(arg_body), false); + const arg_value = try sema.analyzeInlineBody(builder, @ptrCast(arg_body)); if (arg_value != .none) try builder.ensureLoad(arg_value); } try builder.addConstOp(.call, @intCast(args_len)); @@ -986,8 +1016,8 @@ fn analyzeBodyInner( .mod => try irBinaryOp(sema, builder, inst, .mod), .neg => try irUnaryOp(sema, builder, inst, .neg), .not => try irUnaryOp(sema, builder, inst, .not), - .bool_and => try irLogicalOp(sema, builder, inst, false), - .bool_or => try irLogicalOp(sema, builder, inst, true), + .bool_br_and => try irLogicalOp(sema, builder, inst, false), + .bool_br_or => try irLogicalOp(sema, builder, inst, true), .cmp_eq => try irBinaryOp(sema, builder, inst, .cmp_eq), .cmp_neq => try irBinaryOp(sema, builder, inst, .cmp_neq), .cmp_lt => try irBinaryOp(sema, builder, inst, .cmp_lt), @@ -1008,7 +1038,10 @@ fn analyzeBodyInner( try irBreak(sema, inst); continue; }, - .break_inline => try irBreakInline(sema, inst), + .break_inline => { + sema.comptime_break_inst = inst; + return error.ComptimeBreak; + }, .block => { try irBlock(sema, builder, inst); continue; @@ -1233,7 +1266,7 @@ fn resolveGlobalDecl( const body = sema.ir.bodySlice(extra.end, extra.data.body_len); entry.resolution = .in_progress; - const val = try sema.analyzeBodyInner(builder, body, true); + const val = try sema.analyzeInlineBody(builder, body); entry.resolution = .{ .resolved = val }; return val; }, @@ -1260,6 +1293,7 @@ pub fn scanTopLevelDecls( .sema = sema, .code = undefined, .namespace = namespace, + .knot_tag = .knot, }; defer builder.deinit(gpa); diff --git a/src/Story.zig b/src/Story.zig index fc10de3..9b6bfc4 100644 --- a/src/Story.zig +++ b/src/Story.zig @@ -100,10 +100,13 @@ pub const Opcode = enum(u8) { }; pub const CallFrame = struct { + /// Pointer to the knot that initiated the call. callee: *Object.Knot, - caller_top: usize, + /// Instruction pointer. ip: usize, - sp: usize, + bp: usize, + /// Output stream base. + output_base: usize, }; pub const Value = union(enum) { @@ -377,11 +380,11 @@ fn getConstant(story: *Story, frame: *CallFrame, offset: u8) !Value { } fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?Value { - return vm.stack[frame.sp + offset]; + return vm.stack[frame.bp + offset + 1]; } fn setLocal(vm: *Story, frame: *CallFrame, offset: u8, value: Value) void { - vm.stack[frame.sp + offset] = value; + vm.stack[frame.bp + offset + 1] = value; } // TODO: This should probably check the constants table first. @@ -426,68 +429,58 @@ pub fn getKnot(vm: *Story, name: []const u8) ?*Object.Knot { return knot; } -fn call(vm: *Story, knot: *Object.Knot) !void { - if (vm.call_stack_top >= vm.call_stack.len) - return error.CallStackOverflow; - - const locals_count = knot.code.locals_count; - const args_count = knot.code.args_count; - const sp = vm.stack_top - args_count; - const caller_top = if (vm.call_stack_top == 0) - sp - else - sp - 1; - - const frame_top = sp + args_count + locals_count; - if (frame_top > vm.stack.len) return error.StackOverflow; - for (vm.stack[sp + args_count .. frame_top]) |*slot| slot.* = .nil; - - vm.stack_top = frame_top; - vm.call_stack[vm.call_stack_top] = .{ - .callee = knot, - .ip = 0, - .sp = sp, - .caller_top = caller_top, - }; - vm.call_stack_top += 1; +fn callValue(vm: *Story, value: Value, args_count: u8) !void { + switch (value) { + .object => |object| switch (object.tag) { + .knot => return call(vm, @ptrCast(object), args_count), + else => return error.InvalidCallTarget, + }, + else => return error.InvalidCallTarget, + } } -// Diverts are essentially tail calls. -fn divert(vm: *Story, knot: *Object.Knot) !void { - const args_count = knot.code.args_count; - const locals_count = knot.code.locals_count; - +fn call(vm: *Story, knot: *Object.Knot, args_count: u8) !void { + assert(knot.code.args_count == args_count); + if (vm.call_stack_top >= vm.call_stack.len) + return error.CallStackOverflow; if (!vm.can_advance) vm.can_advance = true; - if (vm.call_stack_top == 0) - return vm.call(knot); - - const args_start = vm.stack_top - args_count; - const current_frame = &vm.call_stack[vm.call_stack_top - 1]; - const sp = current_frame.sp; - const caller_top = current_frame.caller_top; - - if (args_count > 0) { - std.mem.copyForwards( - Value, - vm.stack[sp .. sp + args_count], - vm.stack[args_start .. args_start + args_count], - ); - } - - const frame_top = sp + args_count + locals_count; - if (frame_top > vm.stack.len) return error.StackOverflow; - - for (vm.stack[sp + args_count .. frame_top]) |*slot| slot.* = .nil; - vm.stack_top = frame_top; - - current_frame.* = .{ + vm.call_stack[vm.call_stack_top] = .{ .callee = knot, .ip = 0, - .sp = sp, - .caller_top = caller_top, + .bp = vm.stack_top - args_count - 1, + .output_base = vm.output_buffer.items.len, }; + vm.call_stack_top += 1; + vm.stack_top += knot.code.locals_count; +} + +fn divertValue(vm: *Story, value: Value, args_count: u8) !void { + switch (value) { + .object => |object| switch (object.tag) { + .knot => return divert(vm, @ptrCast(object), args_count), + else => return error.InvalidCallTarget, + }, + else => return error.InvalidCallTarget, + } +} + +// Diverts are essentially tail calls. +fn divert(vm: *Story, knot: *Object.Knot, args_count: u8) !void { + assert(knot.code.args_count == args_count); + if (vm.call_stack_top == 0) + return vm.call(knot, args_count); + + const frame = &vm.call_stack[vm.call_stack_top - 1]; + frame.* = .{ + .callee = knot, + .ip = 0, + .bp = vm.stack_top - args_count - 1, + .output_base = vm.output_buffer.items.len, + }; + + vm.stack_top += knot.code.locals_count; } fn readAddress(code: []const Story.Opcode, offset: usize) u16 { @@ -518,16 +511,32 @@ fn step(vm: *Story) !StepSignal { .exit => return .exit, .done => return .done, .ret => { - const return_value = vm.stack[vm.stack_top - 1]; - + if (vm.call_stack_top == 0) return error.UnexpectedReturn; vm.call_stack_top -= 1; - const completed_frame = vm.call_stack[vm.call_stack_top]; - vm.stack_top = completed_frame.caller_top; + const resolved_stream: ?Value = if (vm.output_buffer.items.len > frame.output_base) blk: { + const frame_output = vm.output_buffer.items[frame.output_base..]; + defer vm.output_buffer.shrinkRetainingCapacity(frame.output_base); + + const str_bytes = try resolveOutputStream(vm, arena, frame_output); + const str_object = try Object.String.create(vm, .{ .bytes = str_bytes }); + break :blk .{ .object = &str_object.base }; + } else blk: { + break :blk null; + }; + const return_value = if (resolved_stream) |stream| blk: { + if (frame.bp + frame.callee.code.stack_size + 1 < vm.stack_top) { + try vm.output_buffer.append(gpa, .{ .value = stream }); + break :blk popStack(vm).?; + } + break :blk stream; + } else if (frame.bp + frame.callee.code.stack_size + 1 < vm.stack_top) blk: { + break :blk popStack(vm).?; + } else .nil; + + vm.stack_top = frame.bp; vm.stack[vm.stack_top] = return_value; vm.stack_top += 1; - - if (vm.call_stack_top == 0) return error.UnexpectedReturn; frame = &vm.call_stack[vm.call_stack_top - 1]; }, .pop => { @@ -629,17 +638,11 @@ fn step(vm: *Story) !StepSignal { } }, .call => { - const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]); + const args_count: u8 = @intFromEnum(code[frame.ip + 1]); frame.ip += 2; - if (peekStack(vm, arg_offset)) |value| { - switch (value) { - .object => |object| switch (object.tag) { - .knot => try call(vm, @ptrCast(object)), - else => unreachable, - }, - else => unreachable, - } + if (peekStack(vm, args_count)) |value| { + try callValue(vm, value, args_count); } else { return error.InvalidArgument; } @@ -647,17 +650,11 @@ fn step(vm: *Story) !StepSignal { frame = &vm.call_stack[vm.call_stack_top - 1]; }, .divert => { - const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]); + const args_count: u8 = @intFromEnum(code[frame.ip + 1]); frame.ip += 2; - if (peekStack(vm, arg_offset)) |value| { - switch (value) { - .object => |object| switch (object.tag) { - .knot => try divert(vm, @ptrCast(object)), - else => unreachable, - }, - else => unreachable, - } + if (peekStack(vm, args_count)) |value| { + try divertValue(vm, value, args_count); } else { return error.InvalidArgument; } @@ -831,6 +828,7 @@ pub fn advance(story: *Story) !?[]const u8 { } } story.can_advance = false; + if (output_buffer.items.len == 0) return null; return try resolveOutputStream(story, arena, output_buffer.items[0..]); } @@ -906,7 +904,8 @@ pub fn fromSourceBytes( try comp.setupStoryRuntime(gpa, &story); if (story.getKnot(Story.default_knot_name)) |knot| { - try story.divert(knot); + try story.pushStack(.{ .object = &knot.base }); + try story.divert(knot, 0); } return story; } diff --git a/src/Story/Dumper.zig b/src/Story/Dumper.zig index 564d034..5c95347 100644 --- a/src/Story/Dumper.zig +++ b/src/Story/Dumper.zig @@ -350,15 +350,26 @@ pub fn dump(story: *Story, writer: *std.Io.Writer) !void { pub fn trace(vm: *Story, writer: *std.Io.Writer, frame: *Story.CallFrame) !void { var dumper: Dumper = .{ .story = vm }; - try writer.print("\tStack => stack_pointer={d}, objects=[", .{frame.sp}); + try writer.print("\tStack => stack_base={d}, values=[", .{frame.bp}); - const window = vm.stack[frame.sp..vm.stack_top]; - for (window, 0..) |*slot, i| { + const stack_window = vm.stack[frame.bp..vm.stack_top]; + for (stack_window, 0..) |*slot, i| { if (i > 0) try writer.writeAll(", "); try dumper.dumpValue(writer, slot); } try writer.writeAll("]\n"); + + try writer.print("\tOutput => offset_base={d}, values=[", .{frame.output_base}); + + const output_window = vm.output_buffer.items[frame.output_base..]; + for (output_window, 0..) |slot, i| { + if (i > 0) try writer.writeAll(", "); + try writer.print("{any}", .{slot}); + } + + try writer.writeAll("]\n"); + _ = try dumper.dumpInst(writer, frame.callee, frame.ip, true); return writer.flush(); } diff --git a/src/Story/Object.zig b/src/Story/Object.zig index 72d1099..898f6d8 100644 --- a/src/Story/Object.zig +++ b/src/Story/Object.zig @@ -114,7 +114,10 @@ pub const String = struct { }, .object => |object| switch (object.tag) { .string => return @ptrCast(object), - else => return error.TypeError, + else => { + std.debug.print("Bad object type: {s}\n", .{@tagName(object.tag)}); + return error.TypeError; + }, }, } } diff --git a/src/compile.zig b/src/compile.zig index 83cbfa1..9f623e8 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -292,11 +292,13 @@ pub const Module = struct { .sema = &sema, .code = code_chunk, .namespace = work_unit.namespace, + .knot_tag = undefined, }; defer builder.deinit(gpa); switch (work_unit.tag) { .knot => { + builder.knot_tag = .knot; try sema.analyzeKnot(&builder, work_unit.inst_index); try builder.finalize(); @@ -307,6 +309,7 @@ pub const Module = struct { }); }, .stitch => { + builder.knot_tag = .knot; try sema.analyzeStitch(&builder, work_unit.inst_index); try builder.finalize(); @@ -317,6 +320,7 @@ pub const Module = struct { }); }, .function => { + builder.knot_tag = .function; try sema.analyzeFunction(&builder, work_unit.inst_index); try builder.finalize(); diff --git a/src/print_ir.zig b/src/print_ir.zig index 4bb830b..4aeef1a 100644 --- a/src/print_ir.zig +++ b/src/print_ir.zig @@ -80,6 +80,15 @@ pub const Writer = struct { try self.writeInstRef(w, data.rhs); } + fn writeBoolBinaryInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void { + const data = self.code.instructions[@intFromEnum(inst)].data.payload; + const extra = self.code.extraData(Ir.Inst.BoolBr, data.extra_index); + const body = self.code.bodySlice(extra.end, extra.data.body_len); + try self.writeInstRef(w, extra.data.lhs); + try w.writeAll(", "); + try self.writeBodyInner(w, body); + } + fn writeFieldPtrInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void { const data = self.code.instructions[@intFromEnum(inst)].data.payload; const extra = self.code.extraData(Ir.Inst.Field, data.extra_index).data; @@ -114,29 +123,36 @@ pub const Writer = struct { try self.writeStringRef(w, extra.data.field_name_start); }, } - try w.writeAll(", "); - try w.writeAll("[\n"); - var arg_start: u32 = args_len; - var i: u32 = 0; - while (i < args_len) : (i += 1) { - self.pushIndent(); - const arg_end = self.code.extra[extra.end + i]; - defer arg_start = arg_end; - const arg_body = body[arg_start..arg_end]; - try self.writeBodyInner(w, @ptrCast(arg_body)); - try w.writeAll(",\n"); - self.popIndent(); + try w.writeAll(", ["); + if (args_len > 0) { + try w.writeAll("\n"); + + var arg_start: u32 = args_len; + var i: u32 = 0; + while (i < args_len) : (i += 1) { + self.pushIndent(); + try self.writeIndent(w); + const arg_end = self.code.extra[extra.end + i]; + defer arg_start = arg_end; + const arg_body = body[arg_start..arg_end]; + try self.writeBodyInner(w, @ptrCast(arg_body)); + try w.writeAll(",\n"); + self.popIndent(); + } + + try self.writeIndent(w); } - - try self.writeIndent(w); try w.writeAll("]"); } fn writeBreakInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void { const data = self.code.instructions[@intFromEnum(inst)].data.payload; const extra = self.code.extraData(Ir.Inst.Break, data.extra_index); + try self.writeInstIndex(w, extra.data.block_inst); + try w.writeAll(", "); + try self.writeInstRef(w, extra.data.operand); } fn writeCondbrInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void { @@ -318,7 +334,7 @@ pub const Writer = struct { .decl_ref => try self.writeStrTokInst(w, inst), .condbr => try self.writeCondbrInst(w, inst), .@"break" => try self.writeBreakInst(w, inst), - .break_inline => try self.writeBinaryInst(w, inst), + .break_inline => try self.writeBreakInst(w, inst), .switch_br => try self.writeSwitchBrInst(w, inst), .alloc => {}, .load => try self.writeUnaryInst(w, inst), @@ -331,8 +347,8 @@ pub const Writer = struct { .mod => try self.writeBinaryInst(w, inst), .neg => try self.writeUnaryInst(w, inst), .not => try self.writeUnaryInst(w, inst), - .bool_and => try self.writeBinaryInst(w, inst), - .bool_or => try self.writeBinaryInst(w, inst), + .bool_br_and => try self.writeBoolBinaryInst(w, inst), + .bool_br_or => try self.writeBoolBinaryInst(w, inst), .cmp_eq => try self.writeBinaryInst(w, inst), .cmp_neq => try self.writeBinaryInst(w, inst), .cmp_gt => try self.writeBinaryInst(w, inst), @@ -359,5 +375,6 @@ pub const Writer = struct { } try w.writeAll(")"); try w.writeAll("\n"); + try w.flush(); } };