Commit 0f5eda973e
Changed files (5)
test
stage2
src/astgen.zig
@@ -309,7 +309,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
.Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?),
.Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?),
.OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?),
- .Switch => return switchExpr(mod, scope, rl, node.castTag(.Switch).?),
+ .Switch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Switch", .{}),
.ContainerDecl => return containerDecl(mod, scope, rl, node.castTag(.ContainerDecl).?),
.Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),
@@ -334,11 +334,19 @@ fn comptimeKeyword(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.C
return comptimeExpr(mod, scope, rl, node.expr);
}
-pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerError!*zir.Inst {
- const tree = parent_scope.tree();
- const src = tree.token_locs[node.firstToken()].start;
+pub fn comptimeExpr(
+ mod: *Module,
+ parent_scope: *Scope,
+ rl: ResultLoc,
+ node: *ast.Node,
+) InnerError!*zir.Inst {
+ // If we are already in a comptime scope, no need to make another one.
+ if (parent_scope.isComptime()) {
+ return expr(mod, parent_scope, rl, node);
+ }
- // Optimization for labeled blocks: don't need to have 2 layers of blocks, we can reuse the existing one.
+ // Optimization for labeled blocks: don't need to have 2 layers of blocks,
+ // we can reuse the existing one.
if (node.castTag(.LabeledBlock)) |block_node| {
return labeledBlockExpr(mod, parent_scope, rl, block_node, .block_comptime);
}
@@ -348,6 +356,7 @@ pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *as
.parent = parent_scope,
.decl = parent_scope.ownerDecl().?,
.arena = parent_scope.arena(),
+ .force_comptime = true,
.instructions = .{},
};
defer block_scope.instructions.deinit(mod.gpa);
@@ -356,6 +365,9 @@ pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *as
// instruction is the block's result value.
_ = try expr(mod, &block_scope.base, rl, node);
+ const tree = parent_scope.tree();
+ const src = tree.token_locs[node.firstToken()].start;
+
const block = try addZIRInstBlock(mod, parent_scope, src, .block_comptime_flat, .{
.instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
});
@@ -410,7 +422,7 @@ fn breakExpr(
try gen_zir.labeled_breaks.append(mod.gpa, br.castTag(.@"break").?);
if (have_store_to_block) {
- const inst_list = parent_scope.cast(Scope.GenZIR).?.instructions.items;
+ const inst_list = parent_scope.getGenZIR().instructions.items;
const last_inst = inst_list[inst_list.len - 2];
const store_inst = last_inst.castTag(.store_to_block_ptr).?;
assert(store_inst.positionals.lhs == gen_zir.rl_ptr.?);
@@ -559,6 +571,7 @@ fn labeledBlockExpr(
.parent = parent_scope,
.decl = parent_scope.ownerDecl().?,
.arena = gen_zir.arena,
+ .force_comptime = parent_scope.isComptime(),
.instructions = .{},
// TODO @as here is working around a stage1 miscompilation bug :(
.label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{
@@ -746,6 +759,7 @@ fn varDecl(
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
+ .force_comptime = scope.isComptime(),
.instructions = .{},
};
defer init_scope.instructions.deinit(mod.gpa);
@@ -1107,6 +1121,7 @@ fn containerDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Con
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
+ .force_comptime = scope.isComptime(),
.instructions = .{},
};
defer gen_scope.instructions.deinit(mod.gpa);
@@ -1343,6 +1358,7 @@ fn orelseCatchExpr(
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
+ .force_comptime = scope.isComptime(),
.instructions = .{},
};
setBlockResultLoc(&block_scope, rl);
@@ -1367,6 +1383,7 @@ fn orelseCatchExpr(
.parent = &block_scope.base,
.decl = block_scope.decl,
.arena = block_scope.arena,
+ .force_comptime = block_scope.force_comptime,
.instructions = .{},
};
defer then_scope.instructions.deinit(mod.gpa);
@@ -1395,6 +1412,7 @@ fn orelseCatchExpr(
.parent = &block_scope.base,
.decl = block_scope.decl,
.arena = block_scope.arena,
+ .force_comptime = block_scope.force_comptime,
.instructions = .{},
};
defer else_scope.instructions.deinit(mod.gpa);
@@ -1416,6 +1434,7 @@ fn orelseCatchExpr(
then_result,
unwrapped_payload,
block,
+ block,
);
}
@@ -1432,7 +1451,8 @@ fn finishThenElseBlock(
else_src: usize,
then_result: *zir.Inst,
else_result: ?*zir.Inst,
- block: *zir.Inst.Block,
+ main_block: *zir.Inst.Block,
+ then_break_block: *zir.Inst.Block,
) InnerError!*zir.Inst {
// We now have enough information to decide whether the result instruction should
// be communicated via result location pointer or break instructions.
@@ -1441,42 +1461,42 @@ fn finishThenElseBlock(
.break_void => {
if (!then_result.tag.isNoReturn()) {
_ = try addZirInstTag(mod, &then_scope.base, then_src, .break_void, .{
- .block = block,
+ .block = then_break_block,
});
}
if (else_result) |inst| {
if (!inst.tag.isNoReturn()) {
_ = try addZirInstTag(mod, &else_scope.base, else_src, .break_void, .{
- .block = block,
+ .block = main_block,
});
}
} else {
_ = try addZirInstTag(mod, &else_scope.base, else_src, .break_void, .{
- .block = block,
+ .block = main_block,
});
}
assert(!strat.elide_store_to_block_ptr_instructions);
try copyBodyNoEliding(then_body, then_scope.*);
try copyBodyNoEliding(else_body, else_scope.*);
- return &block.base;
+ return &main_block.base;
},
.break_operand => {
if (!then_result.tag.isNoReturn()) {
_ = try addZirInstTag(mod, &then_scope.base, then_src, .@"break", .{
- .block = block,
+ .block = then_break_block,
.operand = then_result,
});
}
if (else_result) |inst| {
if (!inst.tag.isNoReturn()) {
_ = try addZirInstTag(mod, &else_scope.base, else_src, .@"break", .{
- .block = block,
+ .block = main_block,
.operand = inst,
});
}
} else {
_ = try addZirInstTag(mod, &else_scope.base, else_src, .break_void, .{
- .block = block,
+ .block = main_block,
});
}
if (strat.elide_store_to_block_ptr_instructions) {
@@ -1487,8 +1507,8 @@ fn finishThenElseBlock(
try copyBodyNoEliding(else_body, else_scope.*);
}
switch (rl) {
- .ref => return &block.base,
- else => return rvalue(mod, parent_scope, rl, &block.base),
+ .ref => return &main_block.base,
+ else => return rvalue(mod, parent_scope, rl, &main_block.base),
}
},
}
@@ -1643,6 +1663,7 @@ fn boolBinOp(
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
+ .force_comptime = scope.isComptime(),
.instructions = .{},
};
defer block_scope.instructions.deinit(mod.gpa);
@@ -1662,6 +1683,7 @@ fn boolBinOp(
.parent = scope,
.decl = block_scope.decl,
.arena = block_scope.arena,
+ .force_comptime = block_scope.force_comptime,
.instructions = .{},
};
defer rhs_scope.instructions.deinit(mod.gpa);
@@ -1676,6 +1698,7 @@ fn boolBinOp(
.parent = scope,
.decl = block_scope.decl,
.arena = block_scope.arena,
+ .force_comptime = block_scope.force_comptime,
.instructions = .{},
};
defer const_scope.instructions.deinit(mod.gpa);
@@ -1789,6 +1812,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
+ .force_comptime = scope.isComptime(),
.instructions = .{},
};
setBlockResultLoc(&block_scope, rl);
@@ -1813,6 +1837,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
.parent = scope,
.decl = block_scope.decl,
.arena = block_scope.arena,
+ .force_comptime = block_scope.force_comptime,
.instructions = .{},
};
defer then_scope.instructions.deinit(mod.gpa);
@@ -1830,6 +1855,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
.parent = scope,
.decl = block_scope.decl,
.arena = block_scope.arena,
+ .force_comptime = block_scope.force_comptime,
.instructions = .{},
};
defer else_scope.instructions.deinit(mod.gpa);
@@ -1863,6 +1889,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
then_result,
else_result,
block,
+ block,
);
}
@@ -1912,6 +1939,7 @@ fn whileExpr(
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
+ .force_comptime = scope.isComptime(),
.instructions = .{},
};
setBlockResultLoc(&loop_scope, rl);
@@ -1921,6 +1949,7 @@ fn whileExpr(
.parent = &loop_scope.base,
.decl = loop_scope.decl,
.arena = loop_scope.arena,
+ .force_comptime = loop_scope.force_comptime,
.instructions = .{},
};
defer continue_scope.instructions.deinit(mod.gpa);
@@ -1978,6 +2007,7 @@ fn whileExpr(
.parent = &continue_scope.base,
.decl = continue_scope.decl,
.arena = continue_scope.arena,
+ .force_comptime = continue_scope.force_comptime,
.instructions = .{},
};
defer then_scope.instructions.deinit(mod.gpa);
@@ -1992,6 +2022,7 @@ fn whileExpr(
.parent = &continue_scope.base,
.decl = continue_scope.decl,
.arena = continue_scope.arena,
+ .force_comptime = continue_scope.force_comptime,
.instructions = .{},
};
defer else_scope.instructions.deinit(mod.gpa);
@@ -2027,6 +2058,7 @@ fn whileExpr(
then_result,
else_result,
while_block,
+ cond_block,
);
}
@@ -2068,6 +2100,7 @@ fn forExpr(
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
+ .force_comptime = scope.isComptime(),
.instructions = .{},
};
setBlockResultLoc(&loop_scope, rl);
@@ -2077,6 +2110,7 @@ fn forExpr(
.parent = &loop_scope.base,
.decl = loop_scope.decl,
.arena = loop_scope.arena,
+ .force_comptime = loop_scope.force_comptime,
.instructions = .{},
};
defer cond_scope.instructions.deinit(mod.gpa);
@@ -2134,6 +2168,7 @@ fn forExpr(
.parent = &cond_scope.base,
.decl = cond_scope.decl,
.arena = cond_scope.arena,
+ .force_comptime = cond_scope.force_comptime,
.instructions = .{},
};
defer then_scope.instructions.deinit(mod.gpa);
@@ -2174,6 +2209,7 @@ fn forExpr(
.parent = &cond_scope.base,
.decl = cond_scope.decl,
.arena = cond_scope.arena,
+ .force_comptime = cond_scope.force_comptime,
.instructions = .{},
};
defer else_scope.instructions.deinit(mod.gpa);
@@ -2206,281 +2242,10 @@ fn forExpr(
then_result,
else_result,
for_block,
+ cond_block,
);
}
-fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp {
- var cur = node;
- while (true) {
- switch (cur.tag) {
- .Range => return @fieldParentPtr(ast.Node.SimpleInfixOp, "base", cur),
- .GroupedExpression => cur = @fieldParentPtr(ast.Node.GroupedExpression, "base", cur).expr,
- else => return null,
- }
- }
-}
-
-fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst {
- if (true) {
- @panic("TODO reimplement this");
- }
- var block_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = scope.ownerDecl().?,
- .arena = scope.arena(),
- .instructions = .{},
- };
- defer block_scope.instructions.deinit(mod.gpa);
-
- const tree = scope.tree();
- const switch_src = tree.token_locs[switch_node.switch_token].start;
- const target_ptr = try expr(mod, &block_scope.base, .ref, switch_node.expr);
- const target = try addZIRUnOp(mod, &block_scope.base, target_ptr.src, .deref, target_ptr);
- // Add the switch instruction here so that it comes before any range checks.
- const switch_inst = (try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{
- .target_ptr = target_ptr,
- .cases = undefined, // populated below
- .items = &[_]*zir.Inst{}, // populated below
- .else_body = undefined, // populated below
- }, .{})).castTag(.switchbr).?;
-
- var items = std.ArrayList(*zir.Inst).init(mod.gpa);
- defer items.deinit();
- var cases = std.ArrayList(zir.Inst.SwitchBr.Case).init(mod.gpa);
- defer cases.deinit();
-
- // Add comptime block containing all prong items first,
- const item_block = try addZIRInstBlock(mod, scope, switch_src, .block_comptime_flat, .{
- .instructions = undefined, // populated below
- });
- // then add block containing the switch.
- const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{
- .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
- });
-
- // Most result location types can be forwarded directly; however
- // if we need to write to a pointer which has an inferred type,
- // proper type inference requires peer type resolution on the switch case.
- const case_rl: ResultLoc = switch (rl) {
- .discard, .none, .ty, .ptr, .ref => rl,
- .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block },
- };
-
- var item_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = scope.ownerDecl().?,
- .arena = scope.arena(),
- .instructions = .{},
- };
- defer item_scope.instructions.deinit(mod.gpa);
-
- var case_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = block_scope.decl,
- .arena = block_scope.arena,
- .instructions = .{},
- };
- defer case_scope.instructions.deinit(mod.gpa);
-
- var else_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = block_scope.decl,
- .arena = block_scope.arena,
- .instructions = .{},
- };
- defer else_scope.instructions.deinit(mod.gpa);
-
- // first we gather all the switch items and check else/'_' prongs
- var else_src: ?usize = null;
- var underscore_src: ?usize = null;
- var first_range: ?*zir.Inst = null;
- var special_case: ?*ast.Node.SwitchCase = null;
- for (switch_node.cases()) |uncasted_case| {
- const case = uncasted_case.castTag(.SwitchCase).?;
- const case_src = tree.token_locs[case.firstToken()].start;
- // reset without freeing to reduce allocations.
- case_scope.instructions.items.len = 0;
- assert(case.items_len != 0);
-
- // Check for else/_ prong, those are handled last.
- if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) {
- if (else_src) |src| {
- const msg = msg: {
- const msg = try mod.errMsg(
- scope,
- case_src,
- "multiple else prongs in switch expression",
- .{},
- );
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(scope, src, msg, "previous else prong is here", .{});
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(scope, msg);
- }
- else_src = case_src;
- special_case = case;
- continue;
- } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and
- mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_"))
- {
- if (underscore_src) |src| {
- const msg = msg: {
- const msg = try mod.errMsg(
- scope,
- case_src,
- "multiple '_' prongs in switch expression",
- .{},
- );
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(scope, src, msg, "previous '_' prong is here", .{});
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(scope, msg);
- }
- underscore_src = case_src;
- special_case = case;
- continue;
- }
-
- if (else_src) |some_else| {
- if (underscore_src) |some_underscore| {
- const msg = msg: {
- const msg = try mod.errMsg(
- scope,
- switch_src,
- "else and '_' prong in switch expression",
- .{},
- );
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(scope, some_else, msg, "else prong is here", .{});
- try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{});
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(scope, msg);
- }
- }
-
- // If this is a simple one item prong then it is handled by the switchbr.
- if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) {
- const item = try expr(mod, &item_scope.base, .none, case.items()[0]);
- try items.append(item);
- try switchCaseExpr(mod, &case_scope.base, case_rl, block, case);
-
- try cases.append(.{
- .item = item,
- .body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items) },
- });
- continue;
- }
-
- // TODO if the case has few items and no ranges it might be better
- // to just handle them as switch prongs.
-
- // Check if the target matches any of the items.
- // 1, 2, 3..6 will result in
- // target == 1 or target == 2 or (target >= 3 and target <= 6)
- var any_ok: ?*zir.Inst = null;
- for (case.items()) |item| {
- if (getRangeNode(item)) |range| {
- const start = try expr(mod, &item_scope.base, .none, range.lhs);
- const end = try expr(mod, &item_scope.base, .none, range.rhs);
- const range_src = tree.token_locs[range.op_token].start;
- const range_inst = try addZIRBinOp(mod, &item_scope.base, range_src, .switch_range, start, end);
- try items.append(range_inst);
- if (first_range == null) first_range = range_inst;
-
- // target >= start and target <= end
- const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, start);
- const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, end);
- const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_and, range_start_ok, range_end_ok);
-
- if (any_ok) |some| {
- any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_or, some, range_ok);
- } else {
- any_ok = range_ok;
- }
- continue;
- }
-
- const item_inst = try expr(mod, &item_scope.base, .none, item);
- try items.append(item_inst);
- const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst);
-
- if (any_ok) |some| {
- any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .bool_or, some, cpm_ok);
- } else {
- any_ok = cpm_ok;
- }
- }
-
- const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{
- .condition = any_ok.?,
- .then_body = undefined, // populated below
- .else_body = undefined, // populated below
- }, .{});
- const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{
- .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
- });
-
- // reset cond_scope for then_body
- case_scope.instructions.items.len = 0;
- try switchCaseExpr(mod, &case_scope.base, case_rl, block, case);
- condbr.positionals.then_body = .{
- .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
- };
-
- // reset cond_scope for else_body
- case_scope.instructions.items.len = 0;
- _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{
- .block = cond_block,
- }, .{});
- condbr.positionals.else_body = .{
- .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
- };
- }
-
- // Generate else block or a break last to finish the block.
- if (special_case) |case| {
- try switchCaseExpr(mod, &else_scope.base, case_rl, block, case);
- } else {
- // Not handling all possible cases is a compile error.
- _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreachable_unsafe);
- }
-
- // All items have been generated, add the instructions to the comptime block.
- item_block.positionals.body = .{
- .instructions = try block_scope.arena.dupe(*zir.Inst, item_scope.instructions.items),
- };
-
- // Actually populate switch instruction values.
- if (else_src != null) switch_inst.kw_args.special_prong = .@"else";
- if (underscore_src != null) switch_inst.kw_args.special_prong = .underscore;
- switch_inst.positionals.cases = try block_scope.arena.dupe(zir.Inst.SwitchBr.Case, cases.items);
- switch_inst.positionals.items = try block_scope.arena.dupe(*zir.Inst, items.items);
- switch_inst.kw_args.range = first_range;
- switch_inst.positionals.else_body = .{
- .instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items),
- };
- return &block.base;
-}
-
-fn switchCaseExpr(mod: *Module, scope: *Scope, rl: ResultLoc, block: *zir.Inst.Block, case: *ast.Node.SwitchCase) !void {
- const tree = scope.tree();
- const case_src = tree.token_locs[case.firstToken()].start;
- if (case.payload != null) {
- return mod.fail(scope, case_src, "TODO switch case payload capture", .{});
- }
-
- const case_body = try expr(mod, scope, rl, case.expr);
- if (!case_body.tag.isNoReturn()) {
- _ = try addZIRInst(mod, scope, case_src, zir.Inst.Break, .{
- .block = block,
- .operand = case_body,
- }, .{});
- }
-}
-
fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[cfe.ltoken].start;
@@ -2859,6 +2624,7 @@ fn asRlPtr(
.parent = scope,
.decl = scope.ownerDecl().?,
.arena = scope.arena(),
+ .force_comptime = scope.isComptime(),
.instructions = .{},
};
defer as_scope.instructions.deinit(mod.gpa);
src/Module.zig
@@ -375,6 +375,10 @@ pub const Scope = struct {
}
}
+ pub fn isComptime(self: *Scope) bool {
+ return self.getGenZIR().force_comptime;
+ }
+
pub fn ownerDecl(self: *Scope) ?*Decl {
return switch (self.tag) {
.block => self.cast(Block).?.owner_decl,
@@ -712,6 +716,7 @@ pub const Scope = struct {
parent: *Scope,
decl: *Decl,
arena: *Allocator,
+ force_comptime: bool,
/// The first N instructions in a function body ZIR are arg instructions.
instructions: std.ArrayListUnmanaged(*zir.Inst) = .{},
label: ?Label = null,
@@ -1008,6 +1013,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
.decl = decl,
.arena = &fn_type_scope_arena.allocator,
.parent = &decl.container.base,
+ .force_comptime = true,
};
defer fn_type_scope.instructions.deinit(self.gpa);
@@ -1171,6 +1177,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
.decl = decl,
.arena = &decl_arena.allocator,
.parent = &decl.container.base,
+ .force_comptime = false,
};
defer gen_scope.instructions.deinit(self.gpa);
@@ -1369,6 +1376,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
.decl = decl,
.arena = &gen_scope_arena.allocator,
.parent = &decl.container.base,
+ .force_comptime = false,
};
defer gen_scope.instructions.deinit(self.gpa);
@@ -1428,6 +1436,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
.decl = decl,
.arena = &type_scope_arena.allocator,
.parent = &decl.container.base,
+ .force_comptime = true,
};
defer type_scope.instructions.deinit(self.gpa);
@@ -1497,13 +1506,15 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
decl.analysis = .in_progress;
- // A comptime decl does not store any value so we can just deinit this arena after analysis is done.
+ // A comptime decl does not store any value so we can just deinit
+ // this arena after analysis is done.
var analysis_arena = std.heap.ArenaAllocator.init(self.gpa);
defer analysis_arena.deinit();
var gen_scope: Scope.GenZIR = .{
.decl = decl,
.arena = &analysis_arena.allocator,
.parent = &decl.container.base,
+ .force_comptime = true,
};
defer gen_scope.instructions.deinit(self.gpa);
src/zir.zig
@@ -336,12 +336,6 @@ pub const Inst = struct {
enum_literal,
/// Create an enum type.
enum_type,
- /// A switch expression.
- switchbr,
- /// A range in a switch case, `lhs...rhs`.
- /// Only checks that `lhs >= rhs` if they are ints, everything else is
- /// validated by the .switch instruction.
- switch_range,
/// Does nothing; returns a void value.
void_value,
@@ -441,7 +435,6 @@ pub const Inst = struct {
.error_union_type,
.merge_error_sets,
.slice_start,
- .switch_range,
=> BinOp,
.block,
@@ -478,7 +471,6 @@ pub const Inst = struct {
.enum_literal => EnumLiteral,
.error_set => ErrorSet,
.slice => Slice,
- .switchbr => SwitchBr,
.typeof_peer => TypeOfPeer,
.container_field_named => ContainerFieldNamed,
.container_field_typed => ContainerFieldTyped,
@@ -605,7 +597,6 @@ pub const Inst = struct {
.slice,
.slice_start,
.import,
- .switch_range,
.typeof_peer,
.resolve_inferred_alloc,
.set_eval_branch_quota,
@@ -625,7 +616,6 @@ pub const Inst = struct {
.unreachable_unsafe,
.unreachable_safe,
.loop,
- .switchbr,
.container_field_named,
.container_field_typed,
.container_field,
@@ -1091,32 +1081,6 @@ pub const Inst = struct {
},
};
- pub const SwitchBr = struct {
- pub const base_tag = Tag.switchbr;
- base: Inst,
-
- positionals: struct {
- target_ptr: *Inst,
- /// List of all individual items and ranges
- items: []*Inst,
- cases: []Case,
- else_body: Body,
- },
- kw_args: struct {
- /// Pointer to first range if such exists.
- range: ?*Inst = null,
- special_prong: enum {
- none,
- @"else",
- underscore,
- } = .none,
- },
-
- pub const Case = struct {
- item: *Inst,
- body: Body,
- };
- };
pub const TypeOfPeer = struct {
pub const base_tag = .typeof_peer;
base: Inst,
@@ -1467,26 +1431,6 @@ const Writer = struct {
}
try stream.writeByte(']');
},
- []Inst.SwitchBr.Case => {
- if (param.len == 0) {
- return stream.writeAll("{}");
- }
- try stream.writeAll("{\n");
- for (param) |*case, i| {
- if (i != 0) {
- try stream.writeAll(",\n");
- }
- try stream.writeByteNTimes(' ', self.indent);
- self.indent += 2;
- try self.writeParamToStream(stream, &case.item);
- try stream.writeAll(" => ");
- try self.writeParamToStream(stream, &case.body);
- self.indent -= 2;
- }
- try stream.writeByte('\n');
- try stream.writeByteNTimes(' ', self.indent - 2);
- try stream.writeByte('}');
- },
else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)),
}
}
src/zir_sema.zig
@@ -151,8 +151,6 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
.slice => return zirSlice(mod, scope, old_inst.castTag(.slice).?),
.slice_start => return zirSliceStart(mod, scope, old_inst.castTag(.slice_start).?),
.import => return zirImport(mod, scope, old_inst.castTag(.import).?),
- .switchbr => return zirSwitchbr(mod, scope, old_inst.castTag(.switchbr).?),
- .switch_range => return zirSwitchRange(mod, scope, old_inst.castTag(.switch_range).?),
.bool_and => return zirBoolOp(mod, scope, old_inst.castTag(.bool_and).?),
.bool_or => return zirBoolOp(mod, scope, old_inst.castTag(.bool_or).?),
.void_value => return mod.constVoid(scope, old_inst.src),
@@ -795,6 +793,12 @@ fn analyzeBlockBody(
var coerce_block = parent_block.makeSubBlock();
defer coerce_block.instructions.deinit(mod.gpa);
const coerced_operand = try mod.coerce(&coerce_block.base, resolved_ty, br.operand);
+ // If no instructions were produced, such as in the case of a coercion of a
+ // constant value to a new type, we can simply point the br operand to it.
+ if (coerce_block.instructions.items.len == 0) {
+ br.operand = coerced_operand;
+ continue;
+ }
assert(coerce_block.instructions.items[coerce_block.instructions.items.len - 1] == coerced_operand);
// Here we depend on the br instruction having been over-allocated (if necessary)
// inide analyzeBreak so that it can be converted into a br_block_flat instruction.
@@ -1531,234 +1535,6 @@ fn zirSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!
return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null);
}
-fn zirSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const start = try resolveInst(mod, scope, inst.positionals.lhs);
- const end = try resolveInst(mod, scope, inst.positionals.rhs);
-
- switch (start.ty.zigTypeTag()) {
- .Int, .ComptimeInt => {},
- else => return mod.constVoid(scope, inst.base.src),
- }
- switch (end.ty.zigTypeTag()) {
- .Int, .ComptimeInt => {},
- else => return mod.constVoid(scope, inst.base.src),
- }
- if (start.value()) |start_val| {
- if (end.value()) |end_val| {
- if (start_val.compare(.gte, end_val)) {
- return mod.fail(scope, inst.base.src, "range start value must be smaller than the end value", .{});
- }
- }
- }
- return mod.constVoid(scope, inst.base.src);
-}
-
-fn zirSwitchbr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const target_ptr = try resolveInst(mod, scope, inst.positionals.target_ptr);
- const target = try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target_ptr.src);
- try validateSwitch(mod, scope, target, inst);
-
- if (try mod.resolveDefinedValue(scope, target)) |target_val| {
- for (inst.positionals.cases) |case| {
- const resolved = try resolveInst(mod, scope, case.item);
- const casted = try mod.coerce(scope, target.ty, resolved);
- const item = try mod.resolveConstValue(scope, casted);
-
- if (target_val.eql(item)) {
- try analyzeBody(mod, scope.cast(Scope.Block).?, case.body);
- return mod.constNoReturn(scope, inst.base.src);
- }
- }
- try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body);
- return mod.constNoReturn(scope, inst.base.src);
- }
-
- if (inst.positionals.cases.len == 0) {
- // no cases just analyze else_branch
- try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body);
- return mod.constNoReturn(scope, inst.base.src);
- }
-
- const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src);
- const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len);
-
- var case_block: Scope.Block = .{
- .parent = parent_block,
- .inst_table = parent_block.inst_table,
- .func = parent_block.func,
- .owner_decl = parent_block.owner_decl,
- .src_decl = parent_block.src_decl,
- .instructions = .{},
- .arena = parent_block.arena,
- .inlining = parent_block.inlining,
- .is_comptime = parent_block.is_comptime,
- .branch_quota = parent_block.branch_quota,
- };
- defer case_block.instructions.deinit(mod.gpa);
-
- for (inst.positionals.cases) |case, i| {
- // Reset without freeing.
- case_block.instructions.items.len = 0;
-
- const resolved = try resolveInst(mod, scope, case.item);
- const casted = try mod.coerce(scope, target.ty, resolved);
- const item = try mod.resolveConstValue(scope, casted);
-
- try analyzeBody(mod, &case_block, case.body);
-
- cases[i] = .{
- .item = item,
- .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) },
- };
- }
-
- case_block.instructions.items.len = 0;
- try analyzeBody(mod, &case_block, inst.positionals.else_body);
-
- const else_body: ir.Body = .{
- .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items),
- };
-
- return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases, else_body);
-}
-
-fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void {
- // validate usage of '_' prongs
- if (inst.kw_args.special_prong == .underscore and target.ty.zigTypeTag() != .Enum) {
- return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{});
- // TODO notes "'_' prong here" inst.positionals.cases[last].src
- }
-
- // check that target type supports ranges
- if (inst.kw_args.range) |range_inst| {
- switch (target.ty.zigTypeTag()) {
- .Int, .ComptimeInt => {},
- else => {
- return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty});
- // TODO notes "range used here" range_inst.src
- },
- }
- }
-
- // validate for duplicate items/missing else prong
- switch (target.ty.zigTypeTag()) {
- .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}),
- .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}),
- .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}),
- .Int, .ComptimeInt => {
- var range_set = @import("RangeSet.zig").init(mod.gpa);
- defer range_set.deinit();
-
- for (inst.positionals.items) |item| {
- const maybe_src = if (item.castTag(.switch_range)) |range| blk: {
- const start_resolved = try resolveInst(mod, scope, range.positionals.lhs);
- const start_casted = try mod.coerce(scope, target.ty, start_resolved);
- const end_resolved = try resolveInst(mod, scope, range.positionals.rhs);
- const end_casted = try mod.coerce(scope, target.ty, end_resolved);
-
- break :blk try range_set.add(
- try mod.resolveConstValue(scope, start_casted),
- try mod.resolveConstValue(scope, end_casted),
- item.src,
- );
- } else blk: {
- const resolved = try resolveInst(mod, scope, item);
- const casted = try mod.coerce(scope, target.ty, resolved);
- const value = try mod.resolveConstValue(scope, casted);
- break :blk try range_set.add(value, value, item.src);
- };
-
- if (maybe_src) |previous_src| {
- return mod.fail(scope, item.src, "duplicate switch value", .{});
- // TODO notes "previous value is here" previous_src
- }
- }
-
- if (target.ty.zigTypeTag() == .Int) {
- var arena = std.heap.ArenaAllocator.init(mod.gpa);
- defer arena.deinit();
-
- const start = try target.ty.minInt(&arena, mod.getTarget());
- const end = try target.ty.maxInt(&arena, mod.getTarget());
- if (try range_set.spans(start, end)) {
- if (inst.kw_args.special_prong == .@"else") {
- return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
- }
- return;
- }
- }
-
- if (inst.kw_args.special_prong != .@"else") {
- return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
- }
- },
- .Bool => {
- var true_count: u8 = 0;
- var false_count: u8 = 0;
- for (inst.positionals.items) |item| {
- const resolved = try resolveInst(mod, scope, item);
- const casted = try mod.coerce(scope, Type.initTag(.bool), resolved);
- if ((try mod.resolveConstValue(scope, casted)).toBool()) {
- true_count += 1;
- } else {
- false_count += 1;
- }
-
- if (true_count + false_count > 2) {
- return mod.fail(scope, item.src, "duplicate switch value", .{});
- }
- }
- if ((true_count + false_count < 2) and inst.kw_args.special_prong != .@"else") {
- return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
- }
- if ((true_count + false_count == 2) and inst.kw_args.special_prong == .@"else") {
- return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
- }
- },
- .EnumLiteral, .Void, .Fn, .Pointer, .Type => {
- if (inst.kw_args.special_prong != .@"else") {
- return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty});
- }
-
- var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa);
- defer seen_values.deinit();
-
- for (inst.positionals.items) |item| {
- const resolved = try resolveInst(mod, scope, item);
- const casted = try mod.coerce(scope, target.ty, resolved);
- const val = try mod.resolveConstValue(scope, casted);
-
- if (try seen_values.fetchPut(val, item.src)) |prev| {
- return mod.fail(scope, item.src, "duplicate switch value", .{});
- // TODO notes "previous value here" prev.value
- }
- }
- },
-
- .ErrorUnion,
- .NoReturn,
- .Array,
- .Struct,
- .Undefined,
- .Null,
- .Optional,
- .BoundFn,
- .Opaque,
- .Vector,
- .Frame,
- .AnyFrame,
- .ComptimeFloat,
- .Float,
- => {
- return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty});
- },
- }
-}
-
fn zirImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
test/stage2/test.zig
@@ -962,43 +962,6 @@ pub fn addCases(ctx: *TestContext) !void {
,
"hello\nhello\nhello\nhello\nhello\n",
);
-
- // comptime switch
-
- // Basic for loop
- case.addCompareOutput(
- \\pub export fn _start() noreturn {
- \\ assert(foo() == 1);
- \\ exit();
- \\}
- \\
- \\fn foo() u32 {
- \\ const a: comptime_int = 1;
- \\ var b: u32 = 0;
- \\ switch (a) {
- \\ 1 => b = 1,
- \\ 2 => b = 2,
- \\ else => unreachable,
- \\ }
- \\ return b;
- \\}
- \\
- \\pub fn assert(ok: bool) void {
- \\ if (!ok) unreachable; // assertion failure
- \\}
- \\
- \\fn exit() noreturn {
- \\ asm volatile ("syscall"
- \\ :
- \\ : [number] "{rax}" (231),
- \\ [arg1] "{rdi}" (0)
- \\ : "rcx", "r11", "memory"
- \\ );
- \\ unreachable;
- \\}
- ,
- "",
- );
}
{