fix: call frame handling, logical short circuiting

This commit is contained in:
Brett Broadhurst 2026-04-01 21:13:36 -06:00
parent 5c133e5fa2
commit 236acc7d60
Failed to generate hash of commit
8 changed files with 301 additions and 159 deletions

View file

@ -423,19 +423,20 @@ const GenIr = struct {
} }
fn addBreak( fn addBreak(
gen: *GenIr, gi: *GenIr,
tag: Ir.Inst.Tag, tag: Ir.Inst.Tag,
node: *const Ast.Node, node: *const Ast.Node,
block_inst: Ir.Inst.Index, block_inst: Ir.Inst.Index,
operand: Ir.Inst.Ref,
) !Ir.Inst.Ref { ) !Ir.Inst.Ref {
const gpa = gen.astgen.gpa;
const extra_len = @typeInfo(Ir.Inst.Break).@"struct".fields.len; 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( const extra_index = gi.astgen.addExtraAssumeCapacity(Ir.Inst.Break{
Ir.Inst.Break{ .block_inst = block_inst }, .operand = operand,
); .block_inst = block_inst,
return gen.addPayloadNodeWithIndex(tag, node, extra_index); });
return gi.addPayloadNodeWithIndex(tag, node, extra_index);
} }
fn makePayloadNode(self: *GenIr, tag: Ir.Inst.Tag) !Ir.Inst.Index { fn makePayloadNode(self: *GenIr, tag: Ir.Inst.Tag) !Ir.Inst.Index {
@ -450,6 +451,23 @@ const GenIr = struct {
return inst_index; 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 { fn setBlockBody(self: *GenIr, inst: Ir.Inst.Index) !void {
const gpa = self.astgen.gpa; const gpa = self.astgen.gpa;
const body = self.instructionsSlice(); const body = self.instructionsSlice();
@ -644,6 +662,31 @@ fn binaryOp(
return gi.addBinaryNode(op, lhs, rhs); 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) { fn parseNumberLiteral(bytes: []const u8) union(enum) {
int: i64, int: i64,
float: f64, 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), .mod_expr => return binaryOp(gi, scope, node, .mod),
.negate_expr => return unaryOp(gi, scope, node, .neg), .negate_expr => return unaryOp(gi, scope, node, .neg),
.logical_not_expr => return unaryOp(gi, scope, node, .not), .logical_not_expr => return unaryOp(gi, scope, node, .not),
.logical_and_expr => return binaryOp(gi, scope, node, .bool_and), .logical_and_expr => return boolBinaryOp(gi, scope, node, .bool_br_and),
.logical_or_expr => return binaryOp(gi, scope, node, .bool_or), .logical_or_expr => return boolBinaryOp(gi, scope, node, .bool_br_or),
.logical_equality_expr => return binaryOp(gi, scope, node, .cmp_eq), .logical_equality_expr => return binaryOp(gi, scope, node, .cmp_eq),
.logical_inequality_expr => return binaryOp(gi, scope, node, .cmp_neq), .logical_inequality_expr => return binaryOp(gi, scope, node, .cmp_neq),
.logical_greater_expr => return binaryOp(gi, scope, node, .cmp_gt), .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()) { if (!then_block.endsWithNoReturn()) {
_ = try then_block.addBreak(.@"break", node, block); _ = try then_block.addBreak(.@"break", node, block, .void);
} }
var else_block = gi.makeSubBlock(); var else_block = gi.makeSubBlock();
defer else_block.unstack(); 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); try setCondBrPayload(condbr, cond_inst, &then_block, &else_block);
return condbr.toRef(); 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); try blockStmt(&then_block, scope, then_body);
if (!then_block.endsWithNoReturn()) { 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(); 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); try blockStmt(&else_block, scope, else_body);
if (!else_block.endsWithNoReturn()) { if (!else_block.endsWithNoReturn()) {
_ = try else_block.addBreak(.@"break", else_body, block); _ = try else_block.addBreak(.@"break", else_body, block, .void);
} }
} }
} else { } 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); 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); try blockStmt(&then_block, scope, body);
if (!then_block.endsWithNoReturn()) { 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(); 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.?); _ = try blockStmt(&case_block, scope, case_data.rhs.?);
if (!case_block.endsWithNoReturn()) { 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(); 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; const else_data = else_branch.data.bin;
_ = try blockStmt(&else_block, scope, else_data.rhs.?); _ = try blockStmt(&else_block, scope, else_data.rhs.?);
if (!else_block.endsWithNoReturn()) { if (!else_block.endsWithNoReturn()) {
_ = try else_block.addBreak(.@"break", else_branch, switch_br); _ = try else_block.addBreak(.@"break", else_branch, switch_br, .void);
} }
} else { } else {
_ = try else_block.addBreak(.@"break", node, switch_br); _ = try else_block.addBreak(.@"break", node, switch_br, .void);
} }
const else_body = else_block.instructionsSlice(); const else_body = else_block.instructionsSlice();
@ -1288,14 +1331,18 @@ fn callExpr(
const callee_node = data.lhs.?; const callee_node = data.lhs.?;
const callee = try calleeExpr(gi, scope, callee_node); 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. // FIXME: List nodes should not have optional slices.
// This hack is an abomination. // This hack is an abomination.
const arguments: ?[]*Ast.Node = if (data.rhs) |args_node| args_node.data.list.items else null; 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 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); try astgen.scratch.resize(gpa, scratch_top + args_count);
var scratch_index = scratch_top; var scratch_index = scratch_top;
@ -1304,7 +1351,10 @@ fn callExpr(
var arg_block = gi.makeSubBlock(); var arg_block = gi.makeSubBlock();
defer arg_block.unstack(); 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(); const body = arg_block.instructionsSlice();
try astgen.scratch.ensureUnusedCapacity(gpa, body.len); try astgen.scratch.ensureUnusedCapacity(gpa, body.len);
@ -1324,7 +1374,13 @@ fn callExpr(
if (args_count != 0) { if (args_count != 0) {
try astgen.extra.appendSlice(gpa, astgen.scratch.items[scratch_top..]); 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| { .field => |callee_field| {
const tag = if (call == .divert) .field_divert else .field_call; const tag = if (call == .divert) .field_divert else .field_call;
@ -1336,9 +1392,16 @@ fn callExpr(
if (args_count != 0) { if (args_count != 0) {
try astgen.extra.appendSlice(gpa, astgen.scratch.items[scratch_top..]); 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 { 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 ret_arg = if (node.data.bin.lhs) |lhs| blk: {
const arg_inst = try expr(gi, scope, lhs); const arg_inst = try expr(gi, scope, lhs);
break :blk arg_inst; break :blk arg_inst;
} else .none; } else .void;
_ = try gi.addUnaryNode(.ret, ret_arg); _ = 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 name_str = try astgen.strFromNode(identifier_node);
const var_inst = try decl_block.makePayloadNode(.decl_var); const var_inst = try decl_block.makePayloadNode(.decl_var);
const rvalue_inst = try expr(&decl_block, scope, expr_node); 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 setDeclVarPayload(var_inst, &decl_block, identifier_node);
try setDeclaration(decl_inst, .{ try setDeclaration(decl_inst, .{

View file

@ -126,6 +126,7 @@ pub const Inst = struct {
pub const Ref = enum(u32) { pub const Ref = enum(u32) {
bool_true, bool_true,
bool_false, bool_false,
void,
none = std.math.maxInt(u32), none = std.math.maxInt(u32),
_, _,
@ -177,10 +178,6 @@ pub const Inst = struct {
/// Uses the `un` union field. /// Uses the `un` union field.
not, not,
/// Uses the `bin` union field. /// Uses the `bin` union field.
bool_and,
/// Uses the `bin` union field.
bool_or,
/// Uses the `bin` union field.
cmp_eq, cmp_eq,
/// Uses the `bin` union field. /// Uses the `bin` union field.
cmp_neq, cmp_neq,
@ -198,6 +195,14 @@ pub const Inst = struct {
float, float,
/// Uses the `str` union field. /// Uses the `str` union field.
str, 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, block,
condbr, condbr,
@"break", @"break",
@ -290,7 +295,13 @@ pub const Inst = struct {
body_len: u32, body_len: u32,
}; };
pub const BoolBr = struct {
lhs: Ref,
body_len: u32,
};
pub const Break = struct { pub const Break = struct {
operand: Ref,
block_inst: Index, block_inst: Index,
}; };
@ -361,8 +372,8 @@ pub const Inst = struct {
.mod, .mod,
.neg, .neg,
.not, .not,
.bool_and, .bool_br_and,
.bool_or, .bool_br_or,
.cmp_eq, .cmp_eq,
.cmp_neq, .cmp_neq,
.cmp_gt, .cmp_gt,

View file

@ -14,6 +14,7 @@ module: *compile.Module,
ir: Ir, ir: Ir,
inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, ValueInfo) = .empty, inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, ValueInfo) = .empty,
errors: *std.ArrayListUnmanaged(Module.Error), errors: *std.ArrayListUnmanaged(Module.Error),
comptime_break_inst: Ir.Inst.Index = undefined,
const InnerError = error{ const InnerError = error{
OutOfMemory, OutOfMemory,
@ -160,6 +161,10 @@ pub const Builder = struct {
constants_map: std.AutoHashMapUnmanaged(InternPool.Index, u8) = .empty, constants_map: std.AutoHashMapUnmanaged(InternPool.Index, u8) = .empty,
labels: std.ArrayListUnmanaged(Label) = .empty, labels: std.ArrayListUnmanaged(Label) = .empty,
fixups: std.ArrayListUnmanaged(Fixup) = .empty, fixups: std.ArrayListUnmanaged(Fixup) = .empty,
knot_tag: enum {
knot,
function,
},
const Label = struct { const Label = struct {
code_offset: usize, code_offset: usize,
@ -495,35 +500,50 @@ fn irBinaryOp(
return .stack; 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( fn irLogicalOp(
sema: *Sema, sema: *Sema,
builder: *Builder, builder: *Builder,
inst: Ir.Inst.Index, inst: Ir.Inst.Index,
logical_or: bool, is_logical_or: bool,
) InnerError!ValueInfo { ) InnerError!ValueInfo {
const ip = &sema.module.intern_pool; const ip = &sema.module.intern_pool;
const data = sema.ir.instructions[@intFromEnum(inst)].data.bin; const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
const lhs = sema.resolveInst(data.lhs); const extra = sema.ir.extraData(Ir.Inst.BoolBr, data.extra_index);
const rhs = sema.resolveInst(data.rhs); const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
const lhs = sema.resolveInst(extra.data.lhs);
if (sema.resolveValue(lhs)) |lhs_info| { if (sema.resolveValue(lhs)) |lhs_info| {
const lhs_value = lhs_info.unwrap(ip); const lhs_value = lhs_info.unwrap(ip);
if (sema.resolveValue(rhs)) |_| { if (is_logical_or and lhs_value.bool) {
return if (logical_or) const value = ip.getOrPutBool(true);
if (lhs_value.isTruthy()) lhs else rhs return .{ .value = value };
else if (!lhs_value.isTruthy()) lhs else rhs; } else if (!is_logical_or and !lhs_value.bool) {
const value = ip.getOrPutBool(false);
return .{ .value = value };
} }
return try sema.analyzeInlineBody(builder, body);
if (logical_or and lhs_value.isTruthy()) return lhs;
if (!logical_or and !lhs_value.isTruthy()) return lhs;
try builder.ensureLoad(rhs);
return .none;
} }
const else_label = try builder.addLabel(); const else_label = try builder.addLabel();
try builder.ensureLoad(lhs); 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); try builder.addByteOp(.pop);
const rhs = try sema.analyzeInlineBody(builder, body);
try builder.ensureLoad(rhs); try builder.ensureLoad(rhs);
builder.setLabel(else_label); builder.setLabel(else_label);
return .none; 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 { fn irRet(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void {
const data = sema.ir.instructions[@intFromEnum(inst)].data.un; const data = sema.ir.instructions[@intFromEnum(inst)].data.un;
if (data.lhs.toIndexAllowNone()) |index| { const lhs = sema.resolveInst(data.lhs);
const value = sema.inst_map.get(index).?; if (lhs != .none) {
if (value != .none) try builder.ensureLoad(value); try builder.ensureLoad(lhs);
} else {
try builder.addByteOp(.stream_glue);
} }
try builder.addByteOp(.ret); try builder.addByteOp(.ret);
} }
fn irImplicitRet(_: *Sema, builder: *Builder, _: Ir.Inst.Index) InnerError!void { fn irImplicitRet(_: *Sema, builder: *Builder, _: Ir.Inst.Index) InnerError!void {
switch (builder.knot_tag) {
.knot => {
try builder.addByteOp(.exit); try builder.addByteOp(.exit);
},
.function => {
try builder.addByteOp(.stream_glue);
try builder.addByteOp(.ret);
},
}
} }
fn irCall( fn irCall(
@ -823,7 +853,7 @@ fn irCall(
const arg_end = sema.ir.extra[extra.end + i]; const arg_end = sema.ir.extra[extra.end + i];
defer arg_start = arg_end; defer arg_start = arg_end;
const arg_body = body[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); if (arg_value != .none) try builder.ensureLoad(arg_value);
} }
try builder.addConstOp(.call, @intCast(args_len)); try builder.addConstOp(.call, @intCast(args_len));
@ -986,8 +1016,8 @@ fn analyzeBodyInner(
.mod => try irBinaryOp(sema, builder, inst, .mod), .mod => try irBinaryOp(sema, builder, inst, .mod),
.neg => try irUnaryOp(sema, builder, inst, .neg), .neg => try irUnaryOp(sema, builder, inst, .neg),
.not => try irUnaryOp(sema, builder, inst, .not), .not => try irUnaryOp(sema, builder, inst, .not),
.bool_and => try irLogicalOp(sema, builder, inst, false), .bool_br_and => try irLogicalOp(sema, builder, inst, false),
.bool_or => try irLogicalOp(sema, builder, inst, true), .bool_br_or => try irLogicalOp(sema, builder, inst, true),
.cmp_eq => try irBinaryOp(sema, builder, inst, .cmp_eq), .cmp_eq => try irBinaryOp(sema, builder, inst, .cmp_eq),
.cmp_neq => try irBinaryOp(sema, builder, inst, .cmp_neq), .cmp_neq => try irBinaryOp(sema, builder, inst, .cmp_neq),
.cmp_lt => try irBinaryOp(sema, builder, inst, .cmp_lt), .cmp_lt => try irBinaryOp(sema, builder, inst, .cmp_lt),
@ -1008,7 +1038,10 @@ fn analyzeBodyInner(
try irBreak(sema, inst); try irBreak(sema, inst);
continue; continue;
}, },
.break_inline => try irBreakInline(sema, inst), .break_inline => {
sema.comptime_break_inst = inst;
return error.ComptimeBreak;
},
.block => { .block => {
try irBlock(sema, builder, inst); try irBlock(sema, builder, inst);
continue; continue;
@ -1233,7 +1266,7 @@ fn resolveGlobalDecl(
const body = sema.ir.bodySlice(extra.end, extra.data.body_len); const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
entry.resolution = .in_progress; entry.resolution = .in_progress;
const val = try sema.analyzeBodyInner(builder, body, true); const val = try sema.analyzeInlineBody(builder, body);
entry.resolution = .{ .resolved = val }; entry.resolution = .{ .resolved = val };
return val; return val;
}, },
@ -1260,6 +1293,7 @@ pub fn scanTopLevelDecls(
.sema = sema, .sema = sema,
.code = undefined, .code = undefined,
.namespace = namespace, .namespace = namespace,
.knot_tag = .knot,
}; };
defer builder.deinit(gpa); defer builder.deinit(gpa);

View file

@ -100,10 +100,13 @@ pub const Opcode = enum(u8) {
}; };
pub const CallFrame = struct { pub const CallFrame = struct {
/// Pointer to the knot that initiated the call.
callee: *Object.Knot, callee: *Object.Knot,
caller_top: usize, /// Instruction pointer.
ip: usize, ip: usize,
sp: usize, bp: usize,
/// Output stream base.
output_base: usize,
}; };
pub const Value = union(enum) { 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 { 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 { 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. // 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; return knot;
} }
fn call(vm: *Story, knot: *Object.Knot) !void { fn callValue(vm: *Story, value: Value, args_count: u8) !void {
if (vm.call_stack_top >= vm.call_stack.len) switch (value) {
return error.CallStackOverflow; .object => |object| switch (object.tag) {
.knot => return call(vm, @ptrCast(object), args_count),
const locals_count = knot.code.locals_count; else => return error.InvalidCallTarget,
const args_count = knot.code.args_count; },
const sp = vm.stack_top - args_count; else => return error.InvalidCallTarget,
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;
} }
// Diverts are essentially tail calls. fn call(vm: *Story, knot: *Object.Knot, args_count: u8) !void {
fn divert(vm: *Story, knot: *Object.Knot) !void { assert(knot.code.args_count == args_count);
const args_count = knot.code.args_count; if (vm.call_stack_top >= vm.call_stack.len)
const locals_count = knot.code.locals_count; return error.CallStackOverflow;
if (!vm.can_advance) if (!vm.can_advance)
vm.can_advance = true; vm.can_advance = true;
if (vm.call_stack_top == 0) vm.call_stack[vm.call_stack_top] = .{
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.* = .{
.callee = knot, .callee = knot,
.ip = 0, .ip = 0,
.sp = sp, .bp = vm.stack_top - args_count - 1,
.caller_top = caller_top, .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 { fn readAddress(code: []const Story.Opcode, offset: usize) u16 {
@ -518,16 +511,32 @@ fn step(vm: *Story) !StepSignal {
.exit => return .exit, .exit => return .exit,
.done => return .done, .done => return .done,
.ret => { .ret => {
const return_value = vm.stack[vm.stack_top - 1]; if (vm.call_stack_top == 0) return error.UnexpectedReturn;
vm.call_stack_top -= 1; 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[vm.stack_top] = return_value;
vm.stack_top += 1; vm.stack_top += 1;
if (vm.call_stack_top == 0) return error.UnexpectedReturn;
frame = &vm.call_stack[vm.call_stack_top - 1]; frame = &vm.call_stack[vm.call_stack_top - 1];
}, },
.pop => { .pop => {
@ -629,17 +638,11 @@ fn step(vm: *Story) !StepSignal {
} }
}, },
.call => { .call => {
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]); const args_count: u8 = @intFromEnum(code[frame.ip + 1]);
frame.ip += 2; frame.ip += 2;
if (peekStack(vm, arg_offset)) |value| { if (peekStack(vm, args_count)) |value| {
switch (value) { try callValue(vm, value, args_count);
.object => |object| switch (object.tag) {
.knot => try call(vm, @ptrCast(object)),
else => unreachable,
},
else => unreachable,
}
} else { } else {
return error.InvalidArgument; return error.InvalidArgument;
} }
@ -647,17 +650,11 @@ fn step(vm: *Story) !StepSignal {
frame = &vm.call_stack[vm.call_stack_top - 1]; frame = &vm.call_stack[vm.call_stack_top - 1];
}, },
.divert => { .divert => {
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]); const args_count: u8 = @intFromEnum(code[frame.ip + 1]);
frame.ip += 2; frame.ip += 2;
if (peekStack(vm, arg_offset)) |value| { if (peekStack(vm, args_count)) |value| {
switch (value) { try divertValue(vm, value, args_count);
.object => |object| switch (object.tag) {
.knot => try divert(vm, @ptrCast(object)),
else => unreachable,
},
else => unreachable,
}
} else { } else {
return error.InvalidArgument; return error.InvalidArgument;
} }
@ -831,6 +828,7 @@ pub fn advance(story: *Story) !?[]const u8 {
} }
} }
story.can_advance = false; story.can_advance = false;
if (output_buffer.items.len == 0) return null;
return try resolveOutputStream(story, arena, output_buffer.items[0..]); return try resolveOutputStream(story, arena, output_buffer.items[0..]);
} }
@ -906,7 +904,8 @@ pub fn fromSourceBytes(
try comp.setupStoryRuntime(gpa, &story); try comp.setupStoryRuntime(gpa, &story);
if (story.getKnot(Story.default_knot_name)) |knot| { 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; return story;
} }

View file

@ -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 { pub fn trace(vm: *Story, writer: *std.Io.Writer, frame: *Story.CallFrame) !void {
var dumper: Dumper = .{ .story = vm }; 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]; const stack_window = vm.stack[frame.bp..vm.stack_top];
for (window, 0..) |*slot, i| { for (stack_window, 0..) |*slot, i| {
if (i > 0) try writer.writeAll(", "); if (i > 0) try writer.writeAll(", ");
try dumper.dumpValue(writer, slot); try dumper.dumpValue(writer, slot);
} }
try writer.writeAll("]\n"); 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); _ = try dumper.dumpInst(writer, frame.callee, frame.ip, true);
return writer.flush(); return writer.flush();
} }

View file

@ -114,7 +114,10 @@ pub const String = struct {
}, },
.object => |object| switch (object.tag) { .object => |object| switch (object.tag) {
.string => return @ptrCast(object), .string => return @ptrCast(object),
else => return error.TypeError, else => {
std.debug.print("Bad object type: {s}\n", .{@tagName(object.tag)});
return error.TypeError;
},
}, },
} }
} }

View file

@ -292,11 +292,13 @@ pub const Module = struct {
.sema = &sema, .sema = &sema,
.code = code_chunk, .code = code_chunk,
.namespace = work_unit.namespace, .namespace = work_unit.namespace,
.knot_tag = undefined,
}; };
defer builder.deinit(gpa); defer builder.deinit(gpa);
switch (work_unit.tag) { switch (work_unit.tag) {
.knot => { .knot => {
builder.knot_tag = .knot;
try sema.analyzeKnot(&builder, work_unit.inst_index); try sema.analyzeKnot(&builder, work_unit.inst_index);
try builder.finalize(); try builder.finalize();
@ -307,6 +309,7 @@ pub const Module = struct {
}); });
}, },
.stitch => { .stitch => {
builder.knot_tag = .knot;
try sema.analyzeStitch(&builder, work_unit.inst_index); try sema.analyzeStitch(&builder, work_unit.inst_index);
try builder.finalize(); try builder.finalize();
@ -317,6 +320,7 @@ pub const Module = struct {
}); });
}, },
.function => { .function => {
builder.knot_tag = .function;
try sema.analyzeFunction(&builder, work_unit.inst_index); try sema.analyzeFunction(&builder, work_unit.inst_index);
try builder.finalize(); try builder.finalize();

View file

@ -80,6 +80,15 @@ pub const Writer = struct {
try self.writeInstRef(w, data.rhs); 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 { fn writeFieldPtrInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void {
const data = self.code.instructions[@intFromEnum(inst)].data.payload; const data = self.code.instructions[@intFromEnum(inst)].data.payload;
const extra = self.code.extraData(Ir.Inst.Field, data.extra_index).data; const extra = self.code.extraData(Ir.Inst.Field, data.extra_index).data;
@ -114,13 +123,16 @@ pub const Writer = struct {
try self.writeStringRef(w, extra.data.field_name_start); try self.writeStringRef(w, extra.data.field_name_start);
}, },
} }
try w.writeAll(", ");
try w.writeAll("[\n"); try w.writeAll(", [");
if (args_len > 0) {
try w.writeAll("\n");
var arg_start: u32 = args_len; var arg_start: u32 = args_len;
var i: u32 = 0; var i: u32 = 0;
while (i < args_len) : (i += 1) { while (i < args_len) : (i += 1) {
self.pushIndent(); self.pushIndent();
try self.writeIndent(w);
const arg_end = self.code.extra[extra.end + i]; const arg_end = self.code.extra[extra.end + i];
defer arg_start = arg_end; defer arg_start = arg_end;
const arg_body = body[arg_start..arg_end]; const arg_body = body[arg_start..arg_end];
@ -130,13 +142,17 @@ pub const Writer = struct {
} }
try self.writeIndent(w); try self.writeIndent(w);
}
try w.writeAll("]"); try w.writeAll("]");
} }
fn writeBreakInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void { fn writeBreakInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void {
const data = self.code.instructions[@intFromEnum(inst)].data.payload; const data = self.code.instructions[@intFromEnum(inst)].data.payload;
const extra = self.code.extraData(Ir.Inst.Break, data.extra_index); const extra = self.code.extraData(Ir.Inst.Break, data.extra_index);
try self.writeInstIndex(w, extra.data.block_inst); 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 { 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), .decl_ref => try self.writeStrTokInst(w, inst),
.condbr => try self.writeCondbrInst(w, inst), .condbr => try self.writeCondbrInst(w, inst),
.@"break" => try self.writeBreakInst(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), .switch_br => try self.writeSwitchBrInst(w, inst),
.alloc => {}, .alloc => {},
.load => try self.writeUnaryInst(w, inst), .load => try self.writeUnaryInst(w, inst),
@ -331,8 +347,8 @@ pub const Writer = struct {
.mod => try self.writeBinaryInst(w, inst), .mod => try self.writeBinaryInst(w, inst),
.neg => try self.writeUnaryInst(w, inst), .neg => try self.writeUnaryInst(w, inst),
.not => try self.writeUnaryInst(w, inst), .not => try self.writeUnaryInst(w, inst),
.bool_and => try self.writeBinaryInst(w, inst), .bool_br_and => try self.writeBoolBinaryInst(w, inst),
.bool_or => try self.writeBinaryInst(w, inst), .bool_br_or => try self.writeBoolBinaryInst(w, inst),
.cmp_eq => try self.writeBinaryInst(w, inst), .cmp_eq => try self.writeBinaryInst(w, inst),
.cmp_neq => try self.writeBinaryInst(w, inst), .cmp_neq => try self.writeBinaryInst(w, inst),
.cmp_gt => 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(")");
try w.writeAll("\n"); try w.writeAll("\n");
try w.flush();
} }
}; };