aboutsummaryrefslogtreecommitdiff
path: root/src/Sema.zig
diff options
context:
space:
mode:
authormlugg <mlugg@mlugg.co.uk>2024-04-28 21:44:57 +0100
committermlugg <mlugg@mlugg.co.uk>2024-09-01 18:30:31 +0100
commit5e12ca9fe3c77ce1d2a3ea1c22c4bcb6d9b2bb0c (patch)
treea4badc5eab3da4901e1c0c3f3239b07628fc339f /src/Sema.zig
parent5fb4a7df38deb705f77088d7788f0acc09da613d (diff)
downloadzig-5e12ca9fe3c77ce1d2a3ea1c22c4bcb6d9b2bb0c.tar.gz
zig-5e12ca9fe3c77ce1d2a3ea1c22c4bcb6d9b2bb0c.zip
compiler: implement labeled switch/continue
Diffstat (limited to 'src/Sema.zig')
-rw-r--r--src/Sema.zig620
1 files changed, 479 insertions, 141 deletions
diff --git a/src/Sema.zig b/src/Sema.zig
index a9287e21d1..57c33324f5 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -503,11 +503,21 @@ pub const Block = struct {
/// to enable more precise compile errors.
/// Same indexes, capacity, length as `results`.
src_locs: std.ArrayListUnmanaged(?LazySrcLoc),
-
- pub fn deinit(merges: *@This(), allocator: mem.Allocator) void {
+ /// Most blocks do not utilize this field. When it is used, its use is
+ /// contextual. The possible uses are as follows:
+ /// * for a `switch_block[_ref]`, this refers to dummy `br` instructions
+ /// which correspond to `switch_continue` ZIR. The switch logic will
+ /// rewrite these to appropriate AIR switch dispatches.
+ extra_insts: std.ArrayListUnmanaged(Air.Inst.Index) = .{},
+ /// Same indexes, capacity, length as `extra_insts`.
+ extra_src_locs: std.ArrayListUnmanaged(LazySrcLoc) = .{},
+
+ pub fn deinit(merges: *@This(), allocator: Allocator) void {
merges.results.deinit(allocator);
merges.br_list.deinit(allocator);
merges.src_locs.deinit(allocator);
+ merges.extra_insts.deinit(allocator);
+ merges.extra_src_locs.deinit(allocator);
}
};
@@ -946,14 +956,21 @@ fn analyzeInlineBody(
error.ComptimeBreak => {},
else => |e| return e,
}
- const break_inst = sema.comptime_break_inst;
- const break_data = sema.code.instructions.items(.data)[@intFromEnum(break_inst)].@"break";
- const extra = sema.code.extraData(Zir.Inst.Break, break_data.payload_index).data;
+ const break_inst = sema.code.instructions.get(@intFromEnum(sema.comptime_break_inst));
+ switch (break_inst.tag) {
+ .switch_continue => {
+ // This is handled by separate logic.
+ return error.ComptimeBreak;
+ },
+ .break_inline, .@"break" => {},
+ else => unreachable,
+ }
+ const extra = sema.code.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data;
if (extra.block_inst != break_target) {
// This control flow goes further up the stack.
return error.ComptimeBreak;
}
- return try sema.resolveInst(break_data.operand);
+ return try sema.resolveInst(break_inst.data.@"break".operand);
}
/// Like `analyzeInlineBody`, but if the body does not break with a value, returns
@@ -1571,6 +1588,13 @@ fn analyzeBodyInner(
i = 0;
continue;
},
+ .switch_continue => if (block.is_comptime) {
+ sema.comptime_break_inst = inst;
+ return error.ComptimeBreak;
+ } else {
+ try sema.zirSwitchContinue(block, inst);
+ break;
+ },
.loop => blk: {
if (!block.is_comptime) break :blk try sema.zirLoop(block, inst);
// Same as `block_inline`. TODO https://github.com/ziglang/zig/issues/8220
@@ -6531,6 +6555,56 @@ fn zirBreak(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError
}
}
+fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].@"break";
+ const extra = sema.code.extraData(Zir.Inst.Break, inst_data.payload_index).data;
+ assert(extra.operand_src_node != Zir.Inst.Break.no_src_node);
+ const operand_src = start_block.nodeOffset(extra.operand_src_node);
+ const uncoerced_operand = try sema.resolveInst(inst_data.operand);
+ const switch_inst = extra.block_inst;
+
+ switch (sema.code.instructions.items(.tag)[@intFromEnum(switch_inst)]) {
+ .switch_block, .switch_block_ref => {},
+ else => unreachable, // assertion failure
+ }
+
+ const switch_payload_index = sema.code.instructions.items(.data)[@intFromEnum(switch_inst)].pl_node.payload_index;
+ const switch_operand_ref = sema.code.extraData(Zir.Inst.SwitchBlock, switch_payload_index).data.operand;
+ const switch_operand_ty = sema.typeOf(try sema.resolveInst(switch_operand_ref));
+
+ const operand = try sema.coerce(start_block, switch_operand_ty, uncoerced_operand, operand_src);
+
+ try sema.validateRuntimeValue(start_block, operand_src, operand);
+
+ // We want to generate a `switch_dispatch` instruction with the switch condition,
+ // possibly preceded by a store to the stack alloc containing the raw operand.
+ // However, to avoid too much special-case state in Sema, this is handled by the
+ // `switch` lowering logic. As such, we will find the `Block` corresponding to the
+ // parent `switch_block[_ref]` instruction, create a dummy `br`, and add a merge
+ // to signal to the switch logic to rewrite this into an appropriate dispatch.
+
+ var block = start_block;
+ while (true) {
+ if (block.label) |label| {
+ if (label.zir_block == switch_inst) {
+ const br_ref = try start_block.addBr(label.merges.block_inst, operand);
+ try label.merges.extra_insts.append(sema.gpa, br_ref.toIndex().?);
+ try label.merges.extra_src_locs.append(sema.gpa, operand_src);
+ block.runtime_index.increment();
+ if (block.runtime_cond == null and block.runtime_loop == null) {
+ block.runtime_cond = start_block.runtime_cond orelse start_block.runtime_loop;
+ block.runtime_loop = start_block.runtime_loop;
+ }
+ return;
+ }
+ }
+ block = block.parent.?;
+ }
+}
+
fn zirDbgStmt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
if (block.is_comptime or block.ownerModule().strip) return;
@@ -10940,12 +11014,7 @@ const SwitchProngAnalysis = struct {
sema: *Sema,
/// The block containing the `switch_block` itself.
parent_block: *Block,
- /// The raw switch operand value (*not* the condition). Always defined.
- operand: Air.Inst.Ref,
- /// May be `undefined` if no prong has a by-ref capture.
- operand_ptr: Air.Inst.Ref,
- /// The switch condition value. For unions, `operand` is the union and `cond` is its tag.
- cond: Air.Inst.Ref,
+ operand: Operand,
/// If this switch is on an error set, this is the type to assign to the
/// `else` prong. If `null`, the prong should be unreachable.
else_error_ty: ?Type,
@@ -10955,6 +11024,34 @@ const SwitchProngAnalysis = struct {
/// undefined if no prong has a tag capture.
tag_capture_inst: Zir.Inst.Index,
+ const Operand = union(enum) {
+ /// This switch will be dispatched only once, with the given operand.
+ simple: struct {
+ /// The raw switch operand value. Always defined.
+ by_val: Air.Inst.Ref,
+ /// The switch operand *pointer*. Defined only if there is a prong
+ /// with a by-ref capture.
+ by_ref: Air.Inst.Ref,
+ /// The switch condition value. For unions, `operand` is the union
+ /// and `cond` is its enum tag value.
+ cond: Air.Inst.Ref,
+ },
+ /// This switch may be dispatched multiple times with `continue` syntax.
+ /// As such, the operand is stored in an alloc if needed.
+ loop: struct {
+ /// The `alloc` containing the `switch` operand for the active dispatch.
+ /// Each prong must load from this `alloc` to get captures.
+ /// If there are no captures, this may be undefined.
+ operand_alloc: Air.Inst.Ref,
+ /// Whether `operand_alloc` contains a by-val operand or a by-ref
+ /// operand.
+ operand_is_ref: bool,
+ /// The switch condition value for the *initial* dispatch. For
+ /// unions, this is the enum tag value.
+ init_cond: Air.Inst.Ref,
+ },
+ };
+
/// Resolve a switch prong which is determined at comptime to have no peers.
/// Uses `resolveBlockBody`. Sets up captures as needed.
fn resolveProngComptime(
@@ -11086,7 +11183,15 @@ const SwitchProngAnalysis = struct {
const sema = spa.sema;
const pt = sema.pt;
const zcu = pt.zcu;
- const operand_ty = sema.typeOf(spa.operand);
+ const operand_ty = switch (spa.operand) {
+ .simple => |s| sema.typeOf(s.by_val),
+ .loop => |l| ty: {
+ const alloc_ty = sema.typeOf(l.operand_alloc);
+ const alloc_child = alloc_ty.childType(zcu);
+ if (l.operand_is_ref) break :ty alloc_child.childType(zcu);
+ break :ty alloc_child;
+ },
+ };
if (operand_ty.zigTypeTag(zcu) != .@"union") {
const tag_capture_src: LazySrcLoc = .{
.base_node_inst = capture_src.base_node_inst,
@@ -11117,10 +11222,24 @@ const SwitchProngAnalysis = struct {
const zir_datas = sema.code.instructions.items(.data);
const switch_node_offset = zir_datas[@intFromEnum(spa.switch_block_inst)].pl_node.src_node;
- const operand_ty = sema.typeOf(spa.operand);
- const operand_ptr_ty = if (capture_byref) sema.typeOf(spa.operand_ptr) else undefined;
const operand_src = block.src(.{ .node_offset_switch_operand = switch_node_offset });
+ const operand_val, const operand_ptr = switch (spa.operand) {
+ .simple => |s| .{ s.by_val, s.by_ref },
+ .loop => |l| op: {
+ const loaded = try sema.analyzeLoad(block, operand_src, l.operand_alloc, operand_src);
+ if (l.operand_is_ref) {
+ const by_val = try sema.analyzeLoad(block, operand_src, loaded, operand_src);
+ break :op .{ by_val, loaded };
+ } else {
+ break :op .{ loaded, undefined };
+ }
+ },
+ };
+
+ const operand_ty = sema.typeOf(operand_val);
+ const operand_ptr_ty = if (capture_byref) sema.typeOf(operand_ptr) else undefined;
+
if (inline_case_capture != .none) {
const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inline_case_capture, undefined) catch unreachable;
if (operand_ty.zigTypeTag(zcu) == .@"union") {
@@ -11136,16 +11255,16 @@ const SwitchProngAnalysis = struct {
.address_space = operand_ptr_ty.ptrAddressSpace(zcu),
},
});
- if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |union_ptr| {
+ if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |union_ptr| {
return Air.internedToRef((try union_ptr.ptrField(field_index, pt)).toIntern());
}
- return block.addStructFieldPtr(spa.operand_ptr, field_index, ptr_field_ty);
+ return block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);
} else {
- if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |union_val| {
+ if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |union_val| {
const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
return Air.internedToRef(tag_and_val.val);
}
- return block.addStructFieldVal(spa.operand, field_index, field_ty);
+ return block.addStructFieldVal(operand_val, field_index, field_ty);
}
} else if (capture_byref) {
return sema.uavRef(item_val.toIntern());
@@ -11156,17 +11275,17 @@ const SwitchProngAnalysis = struct {
if (is_special_prong) {
if (capture_byref) {
- return spa.operand_ptr;
+ return operand_ptr;
}
switch (operand_ty.zigTypeTag(zcu)) {
.error_set => if (spa.else_error_ty) |ty| {
- return sema.bitCast(block, ty, spa.operand, operand_src, null);
+ return sema.bitCast(block, ty, operand_val, operand_src, null);
} else {
try sema.analyzeUnreachable(block, operand_src, false);
return .unreachable_value;
},
- else => return spa.operand,
+ else => return operand_val,
}
}
@@ -11265,19 +11384,19 @@ const SwitchProngAnalysis = struct {
};
};
- if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |op_ptr_val| {
+ if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |op_ptr_val| {
if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
return Air.internedToRef((try pt.getCoerced(field_ptr_val, capture_ptr_ty)).toIntern());
}
try sema.requireRuntimeBlock(block, operand_src, null);
- return block.addStructFieldPtr(spa.operand_ptr, first_field_index, capture_ptr_ty);
+ return block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);
}
- if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |operand_val| {
- if (operand_val.isUndef(zcu)) return pt.undefRef(capture_ty);
- const union_val = ip.indexToKey(operand_val.toIntern()).un;
+ if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |operand_val_val| {
+ if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);
+ const union_val = ip.indexToKey(operand_val_val.toIntern()).un;
if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
const uncoerced = Air.internedToRef(union_val.val);
return sema.coerce(block, capture_ty, uncoerced, operand_src);
@@ -11286,7 +11405,7 @@ const SwitchProngAnalysis = struct {
try sema.requireRuntimeBlock(block, operand_src, null);
if (same_types) {
- return block.addStructFieldVal(spa.operand, first_field_index, capture_ty);
+ return block.addStructFieldVal(operand_val, first_field_index, capture_ty);
}
// We may have to emit a switch block which coerces the operand to the capture type.
@@ -11300,7 +11419,7 @@ const SwitchProngAnalysis = struct {
}
// All fields are in-memory coercible to the resolved type!
// Just take the first field and bitcast the result.
- const uncoerced = try block.addStructFieldVal(spa.operand, first_field_index, first_field_ty);
+ const uncoerced = try block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
return block.addBitCast(capture_ty, uncoerced);
};
@@ -11364,7 +11483,7 @@ const SwitchProngAnalysis = struct {
const field_idx = field_indices[idx];
const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_idx]);
- const uncoerced = try coerce_block.addStructFieldVal(spa.operand, field_idx, field_ty);
+ const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);
const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
_ = try coerce_block.addBr(capture_block_inst, coerced);
@@ -11388,7 +11507,7 @@ const SwitchProngAnalysis = struct {
const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
const first_imc_field_idx = field_indices[first_imc_item_idx];
const first_imc_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
- const uncoerced = try coerce_block.addStructFieldVal(spa.operand, first_imc_field_idx, first_imc_field_ty);
+ const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
_ = try coerce_block.addBr(capture_block_inst, coerced);
@@ -11404,21 +11523,47 @@ const SwitchProngAnalysis = struct {
const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
try sema.air_instructions.append(sema.gpa, .{
.tag = .switch_br,
- .data = .{ .pl_op = .{
- .operand = spa.cond,
- .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
- .cases_len = @intCast(prong_count),
- .else_body_len = @intCast(else_body_len),
- }),
- } },
+ .data = .{
+ .pl_op = .{
+ .operand = undefined, // set by switch below
+ .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
+ .cases_len = @intCast(prong_count),
+ .else_body_len = @intCast(else_body_len),
+ }),
+ },
+ },
});
sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
// Set up block body
- sema.air_instructions.items(.data)[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
- .body_len = 1,
- });
- sema.air_extra.appendAssumeCapacity(switch_br_inst);
+ switch (spa.operand) {
+ .simple => |s| {
+ const air_datas = sema.air_instructions.items(.data);
+ air_datas[switch_br_inst].pl_op.operand = s.cond;
+ air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
+ .body_len = 1,
+ });
+ sema.air_extra.appendAssumeCapacity(switch_br_inst);
+ },
+ .loop => {
+ // The block must first extract the tag from the loaded union.
+ const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
+ try sema.air_instructions.append(sema.gpa, .{
+ .tag = .get_union_tag,
+ .data = .{ .ty_op = .{
+ .ty = Air.internedToRef(union_obj.enum_tag_ty),
+ .operand = operand_val,
+ } },
+ });
+ const air_datas = sema.air_instructions.items(.data);
+ air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
+ air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
+ .body_len = 2,
+ });
+ sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
+ sema.air_extra.appendAssumeCapacity(switch_br_inst);
+ },
+ }
return capture_block_inst.toRef();
},
@@ -11435,7 +11580,7 @@ const SwitchProngAnalysis = struct {
if (case_vals.len == 1) {
const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable;
const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
- return sema.bitCast(block, item_ty, spa.operand, operand_src, null);
+ return sema.bitCast(block, item_ty, operand_val, operand_src, null);
}
var names: InferredErrorSet.NameMap = .{};
@@ -11445,15 +11590,15 @@ const SwitchProngAnalysis = struct {
names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
}
const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
- return sema.bitCast(block, error_ty, spa.operand, operand_src, null);
+ return sema.bitCast(block, error_ty, operand_val, operand_src, null);
},
else => {
// In this case the capture value is just the passed-through value
// of the switch condition.
if (capture_byref) {
- return spa.operand_ptr;
+ return operand_ptr;
} else {
- return spa.operand;
+ return operand_val;
}
},
}
@@ -11686,9 +11831,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
var spa: SwitchProngAnalysis = .{
.sema = sema,
.parent_block = block,
- .operand = undefined, // must be set to the unwrapped error code before use
- .operand_ptr = .none,
- .cond = raw_operand_val,
+ .operand = .{
+ .simple = .{
+ .by_val = undefined, // must be set to the unwrapped error code before use
+ .by_ref = undefined,
+ .cond = raw_operand_val,
+ },
+ },
.else_error_ty = else_error_ty,
.switch_block_inst = inst,
.tag_capture_inst = undefined,
@@ -11709,13 +11858,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
.name = operand_val.getErrorName(zcu).unwrap().?,
},
}));
- spa.operand = if (extra.data.bits.payload_is_ref)
+ spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref)
try sema.analyzeErrUnionCodePtr(block, switch_operand_src, raw_operand_val)
else
try sema.analyzeErrUnionCode(block, switch_operand_src, raw_operand_val);
if (extra.data.bits.any_uses_err_capture) {
- sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand);
+ sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val);
}
defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));
@@ -11723,7 +11872,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
sema,
spa,
&child_block,
- try sema.switchCond(block, switch_operand_src, spa.operand),
+ try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val),
err_val,
operand_err_set_ty,
switch_src_node_offset,
@@ -11777,20 +11926,20 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
const true_instructions = try sub_block.instructions.toOwnedSlice(gpa);
defer gpa.free(true_instructions);
- spa.operand = if (extra.data.bits.payload_is_ref)
+ spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref)
try sema.analyzeErrUnionCodePtr(&sub_block, switch_operand_src, raw_operand_val)
else
try sema.analyzeErrUnionCode(&sub_block, switch_operand_src, raw_operand_val);
if (extra.data.bits.any_uses_err_capture) {
- sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand);
+ sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val);
}
defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));
_ = try sema.analyzeSwitchRuntimeBlock(
spa,
&sub_block,
switch_src,
- try sema.switchCond(block, switch_operand_src, spa.operand),
+ try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val),
operand_err_set_ty,
switch_operand_src,
case_vals,
@@ -11859,17 +12008,63 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
const special_prong_src = block.src(.{ .node_offset_switch_special_prong = src_node_offset });
const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
- const raw_operand_val: Air.Inst.Ref, const raw_operand_ptr: Air.Inst.Ref = blk: {
+ const operand: SwitchProngAnalysis.Operand, const raw_operand_ty: Type = op: {
const maybe_ptr = try sema.resolveInst(extra.data.operand);
- if (operand_is_ref) {
- const val = try sema.analyzeLoad(block, src, maybe_ptr, operand_src);
- break :blk .{ val, maybe_ptr };
- } else {
- break :blk .{ maybe_ptr, undefined };
+ const val, const ref = if (operand_is_ref)
+ .{ try sema.analyzeLoad(block, src, maybe_ptr, operand_src), maybe_ptr }
+ else
+ .{ maybe_ptr, undefined };
+
+ const init_cond = try sema.switchCond(block, operand_src, val);
+
+ const operand_ty = sema.typeOf(val);
+
+ if (extra.data.bits.has_continue and !block.is_comptime) {
+ // Even if the operand is comptime-known, this `switch` is runtime.
+ if (try operand_ty.comptimeOnlySema(pt)) {
+ return sema.failWithOwnedErrorMsg(block, msg: {
+ const msg = try sema.errMsg(operand_src, "operand of switch loop has comptime-only type '{}'", .{operand_ty.fmt(pt)});
+ errdefer msg.destroy(gpa);
+ try sema.errNote(operand_src, msg, "switch loops are evalauted at runtime outside of comptime scopes", .{});
+ break :msg msg;
+ });
+ }
+ try sema.validateRuntimeValue(block, operand_src, maybe_ptr);
+ const operand_alloc = if (extra.data.bits.any_non_inline_capture) a: {
+ const operand_ptr_ty = try pt.singleMutPtrType(sema.typeOf(maybe_ptr));
+ const operand_alloc = try block.addTy(.alloc, operand_ptr_ty);
+ _ = try block.addBinOp(.store, operand_alloc, maybe_ptr);
+ break :a operand_alloc;
+ } else undefined;
+ break :op .{
+ .{ .loop = .{
+ .operand_alloc = operand_alloc,
+ .operand_is_ref = operand_is_ref,
+ .init_cond = init_cond,
+ } },
+ operand_ty,
+ };
}
+
+ // We always use `simple` in the comptime case, because as far as the dispatching logic
+ // is concerned, it really is dispatching a single prong. `resolveSwitchComptime` will
+ // be resposible for recursively resolving different prongs as needed.
+ break :op .{
+ .{ .simple = .{
+ .by_val = val,
+ .by_ref = ref,
+ .cond = init_cond,
+ } },
+ operand_ty,
+ };
};
- const operand = try sema.switchCond(block, operand_src, raw_operand_val);
+ const union_originally = raw_operand_ty.zigTypeTag(zcu) == .@"union";
+ const err_set = raw_operand_ty.zigTypeTag(zcu) == .error_set;
+ const cond_ty = switch (raw_operand_ty.zigTypeTag(zcu)) {
+ .@"union" => raw_operand_ty.unionTagType(zcu).?, // validated by `switchCond` above
+ else => raw_operand_ty,
+ };
// AstGen guarantees that the instruction immediately preceding
// switch_block(_ref) is a dbg_stmt
@@ -11919,9 +12114,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
},
};
- const maybe_union_ty = sema.typeOf(raw_operand_val);
- const union_originally = maybe_union_ty.zigTypeTag(zcu) == .@"union";
-
// Duplicate checking variables later also used for `inline else`.
var seen_enum_fields: []?LazySrcLoc = &.{};
var seen_errors = SwitchErrorSet.init(gpa);
@@ -11937,13 +12129,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
var empty_enum = false;
- const operand_ty = sema.typeOf(operand);
- const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
-
var else_error_ty: ?Type = null;
// Validate usage of '_' prongs.
- if (special_prong == .under and (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally)) {
+ if (special_prong == .under and !raw_operand_ty.isNonexhaustiveEnum(zcu)) {
const msg = msg: {
const msg = try sema.errMsg(
src,
@@ -11969,11 +12158,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
}
// Validate for duplicate items, missing else prong, and invalid range.
- switch (operand_ty.zigTypeTag(zcu)) {
+ switch (cond_ty.zigTypeTag(zcu)) {
.@"union" => unreachable, // handled in `switchCond`
.@"enum" => {
- seen_enum_fields = try gpa.alloc(?LazySrcLoc, operand_ty.enumFieldCount(zcu));
- empty_enum = seen_enum_fields.len == 0 and !operand_ty.isNonexhaustiveEnum(zcu);
+ seen_enum_fields = try gpa.alloc(?LazySrcLoc, cond_ty.enumFieldCount(zcu));
+ empty_enum = seen_enum_fields.len == 0 and !cond_ty.isNonexhaustiveEnum(zcu);
@memset(seen_enum_fields, null);
// `range_set` is used for non-exhaustive enum values that do not correspond to any tags.
@@ -11991,7 +12180,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
seen_enum_fields,
&range_set,
item_ref,
- operand_ty,
+ cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
@@ -12019,7 +12208,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
seen_enum_fields,
&range_set,
item_ref,
- operand_ty,
+ cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
@@ -12028,7 +12217,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
));
}
- try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);
+ try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
}
}
const all_tags_handled = for (seen_enum_fields) |seen_src| {
@@ -12036,7 +12225,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
} else true;
if (special_prong == .@"else") {
- if (all_tags_handled and !operand_ty.isNonexhaustiveEnum(zcu)) return sema.fail(
+ if (all_tags_handled and !cond_ty.isNonexhaustiveEnum(zcu)) return sema.fail(
block,
special_prong_src,
"unreachable else prong; all cases already handled",
@@ -12053,9 +12242,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
for (seen_enum_fields, 0..) |seen_src, i| {
if (seen_src != null) continue;
- const field_name = operand_ty.enumFieldName(i, zcu);
+ const field_name = cond_ty.enumFieldName(i, zcu);
try sema.addFieldErrNote(
- operand_ty,
+ cond_ty,
i,
msg,
"unhandled enumeration value: '{}'",
@@ -12063,15 +12252,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
);
}
try sema.errNote(
- operand_ty.srcLoc(zcu),
+ cond_ty.srcLoc(zcu),
msg,
"enum '{}' declared here",
- .{operand_ty.fmt(pt)},
+ .{cond_ty.fmt(pt)},
);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
- } else if (special_prong == .none and operand_ty.isNonexhaustiveEnum(zcu) and !union_originally) {
+ } else if (special_prong == .none and cond_ty.isNonexhaustiveEnum(zcu) and !union_originally) {
return sema.fail(
block,
src,
@@ -12085,7 +12274,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
block,
&seen_errors,
&case_vals,
- operand_ty,
+ cond_ty,
inst_data,
scalar_cases_len,
multi_cases_len,
@@ -12106,7 +12295,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
block,
&range_set,
item_ref,
- operand_ty,
+ cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
@@ -12133,7 +12322,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
block,
&range_set,
item_ref,
- operand_ty,
+ cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
@@ -12155,7 +12344,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
&range_set,
item_first,
item_last,
- operand_ty,
+ cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
@@ -12171,9 +12360,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
}
check_range: {
- if (operand_ty.zigTypeTag(zcu) == .int) {
- const min_int = try operand_ty.minInt(pt, operand_ty);
- const max_int = try operand_ty.maxInt(pt, operand_ty);
+ if (cond_ty.zigTypeTag(zcu) == .int) {
+ const min_int = try cond_ty.minInt(pt, cond_ty);
+ const max_int = try cond_ty.maxInt(pt, cond_ty);
if (try range_set.spans(min_int.toIntern(), max_int.toIntern())) {
if (special_prong == .@"else") {
return sema.fail(
@@ -12246,7 +12435,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
));
}
- try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);
+ try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
}
}
switch (special_prong) {
@@ -12278,7 +12467,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
block,
src,
"else prong required when switching on type '{}'",
- .{operand_ty.fmt(pt)},
+ .{cond_ty.fmt(pt)},
);
}
@@ -12299,7 +12488,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
block,
&seen_values,
item_ref,
- operand_ty,
+ cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
@@ -12326,7 +12515,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
block,
&seen_values,
item_ref,
- operand_ty,
+ cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
@@ -12335,7 +12524,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
));
}
- try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);
+ try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
}
}
},
@@ -12354,16 +12543,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
.comptime_float,
.float,
=> return sema.fail(block, operand_src, "invalid switch operand type '{}'", .{
- operand_ty.fmt(pt),
+ raw_operand_ty.fmt(pt),
}),
}
const spa: SwitchProngAnalysis = .{
.sema = sema,
.parent_block = block,
- .operand = raw_operand_val,
- .operand_ptr = raw_operand_ptr,
- .cond = operand,
+ .operand = operand,
.else_error_ty = else_error_ty,
.switch_block_inst = inst,
.tag_capture_inst = tag_capture_inst,
@@ -12407,24 +12594,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
defer child_block.instructions.deinit(gpa);
defer merges.deinit(gpa);
- if (try sema.resolveDefinedValue(&child_block, src, operand)) |operand_val| {
- return resolveSwitchComptime(
- sema,
- spa,
- &child_block,
- operand,
- operand_val,
- operand_ty,
- src_node_offset,
- special,
- case_vals,
- scalar_cases_len,
- multi_cases_len,
- err_set,
- empty_enum,
- );
- }
-
if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline) {
if (empty_enum) {
return .void_value;
@@ -12432,54 +12601,90 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
if (special_prong == .none) {
return sema.fail(block, src, "switch must handle all possibilities", .{});
}
- if (err_set and try sema.maybeErrorUnwrap(block, special.body, operand, operand_src, false)) {
- return .unreachable_value;
- }
- if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and operand_ty.zigTypeTag(zcu) == .@"enum" and
- (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally))
+ const init_cond = switch (operand) {
+ .simple => |s| s.cond,
+ .loop => |l| l.init_cond,
+ };
+ if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
+ raw_operand_ty.zigTypeTag(zcu) == .@"enum" and !raw_operand_ty.isNonexhaustiveEnum(zcu))
{
try sema.zirDbgStmt(block, cond_dbg_node_index);
- const ok = try block.addUnOp(.is_named_enum_value, operand);
+ const ok = try block.addUnOp(.is_named_enum_value, init_cond);
try sema.addSafetyCheck(block, src, ok, .corrupt_switch);
}
+ if (err_set and try sema.maybeErrorUnwrap(block, special.body, init_cond, operand_src, false)) {
+ return .unreachable_value;
+ }
+ }
- return spa.resolveProngComptime(
- &child_block,
- .special,
- special.body,
- special.capture,
- block.src(.{ .switch_capture = .{
- .switch_node_offset = src_node_offset,
- .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,
- } }),
- undefined, // case_vals may be undefined for special prongs
- .none,
- false,
- merges,
- );
+ switch (operand) {
+ .loop => {}, // always runtime; evaluation in comptime scope uses `simple`
+ .simple => |s| {
+ if (try sema.resolveDefinedValue(&child_block, src, s.cond)) |cond_val| {
+ return resolveSwitchComptimeLoop(
+ sema,
+ spa,
+ &child_block,
+ if (operand_is_ref)
+ sema.typeOf(s.by_ref)
+ else
+ raw_operand_ty,
+ cond_ty,
+ cond_val,
+ src_node_offset,
+ special,
+ case_vals,
+ scalar_cases_len,
+ multi_cases_len,
+ err_set,
+ empty_enum,
+ operand_is_ref,
+ );
+ }
+
+ if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline and !extra.data.bits.has_continue) {
+ return spa.resolveProngComptime(
+ &child_block,
+ .special,
+ special.body,
+ special.capture,
+ block.src(.{ .switch_capture = .{
+ .switch_node_offset = src_node_offset,
+ .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,
+ } }),
+ undefined, // case_vals may be undefined for special prongs
+ .none,
+ false,
+ merges,
+ );
+ }
+ },
}
if (child_block.is_comptime) {
- _ = try sema.resolveConstDefinedValue(&child_block, operand_src, operand, .{
+ _ = try sema.resolveConstDefinedValue(&child_block, operand_src, operand.simple.cond, .{
.needed_comptime_reason = "condition in comptime switch must be comptime-known",
.block_comptime_reason = child_block.comptime_reason,
});
unreachable;
}
- _ = try sema.analyzeSwitchRuntimeBlock(
+ const air_switch_ref = try sema.analyzeSwitchRuntimeBlock(
spa,
&child_block,
src,
- operand,
- operand_ty,
+ switch (operand) {
+ .simple => |s| s.cond,
+ .loop => |l| l.init_cond,
+ },
+ cond_ty,
operand_src,
case_vals,
special,
scalar_cases_len,
multi_cases_len,
union_originally,
- maybe_union_ty,
+ raw_operand_ty,
err_set,
src_node_offset,
special_prong_src,
@@ -12492,6 +12697,67 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
false,
);
+ for (merges.extra_insts.items, merges.extra_src_locs.items) |placeholder_inst, dispatch_src| {
+ var replacement_block = block.makeSubBlock();
+ defer replacement_block.instructions.deinit(gpa);
+
+ assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .br);
+ const new_operand_maybe_ref = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].br.operand;
+
+ if (extra.data.bits.any_non_inline_capture) {
+ _ = try replacement_block.addBinOp(.store, operand.loop.operand_alloc, new_operand_maybe_ref);
+ }
+
+ const new_operand_val = if (operand_is_ref)
+ try sema.analyzeLoad(&replacement_block, dispatch_src, new_operand_maybe_ref, dispatch_src)
+ else
+ new_operand_maybe_ref;
+
+ const new_cond = try sema.switchCond(&replacement_block, dispatch_src, new_operand_val);
+
+ if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
+ cond_ty.zigTypeTag(zcu) == .@"enum" and !cond_ty.isNonexhaustiveEnum(zcu) and
+ !try sema.isComptimeKnown(new_cond))
+ {
+ const ok = try replacement_block.addUnOp(.is_named_enum_value, new_cond);
+ try sema.addSafetyCheck(&replacement_block, src, ok, .corrupt_switch);
+ }
+
+ _ = try replacement_block.addInst(.{
+ .tag = .switch_dispatch,
+ .data = .{ .br = .{
+ .block_inst = air_switch_ref.toIndex().?,
+ .operand = new_cond,
+ } },
+ });
+
+ if (replacement_block.instructions.items.len == 1) {
+ // Optimization: we don't need a block!
+ sema.air_instructions.set(
+ @intFromEnum(placeholder_inst),
+ sema.air_instructions.get(@intFromEnum(replacement_block.instructions.items[0])),
+ );
+ continue;
+ }
+
+ // Replace placeholder with a block.
+ // No `br` is needed as the block is a switch dispatch so necessarily `noreturn`.
+ try sema.air_extra.ensureUnusedCapacity(
+ gpa,
+ @typeInfo(Air.Block).@"struct".fields.len + replacement_block.instructions.items.len,
+ );
+ sema.air_instructions.set(@intFromEnum(placeholder_inst), .{
+ .tag = .block,
+ .data = .{ .ty_pl = .{
+ .ty = .noreturn_type,
+ .payload = sema.addExtraAssumeCapacity(Air.Block{
+ .body_len = @intCast(replacement_block.instructions.items.len),
+ }),
+ } },
+ });
+ sema.air_extra.appendSliceAssumeCapacity(@ptrCast(replacement_block.instructions.items));
+ }
+
return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false);
}
@@ -13123,7 +13389,7 @@ fn analyzeSwitchRuntimeBlock(
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_body));
return try child_block.addInst(.{
- .tag = .switch_br,
+ .tag = if (spa.operand == .loop) .loop_switch_br else .switch_br,
.data = .{ .pl_op = .{
.operand = operand,
.payload = payload_index,
@@ -13131,6 +13397,77 @@ fn analyzeSwitchRuntimeBlock(
});
}
+fn resolveSwitchComptimeLoop(
+ sema: *Sema,
+ init_spa: SwitchProngAnalysis,
+ child_block: *Block,
+ maybe_ptr_operand_ty: Type,
+ cond_ty: Type,
+ init_cond_val: Value,
+ switch_node_offset: i32,
+ special: SpecialProng,
+ case_vals: std.ArrayListUnmanaged(Air.Inst.Ref),
+ scalar_cases_len: u32,
+ multi_cases_len: u32,
+ err_set: bool,
+ empty_enum: bool,
+ operand_is_ref: bool,
+) CompileError!Air.Inst.Ref {
+ var spa = init_spa;
+ var cond_val = init_cond_val;
+
+ while (true) {
+ if (resolveSwitchComptime(
+ sema,
+ spa,
+ child_block,
+ spa.operand.simple.cond,
+ cond_val,
+ cond_ty,
+ switch_node_offset,
+ special,
+ case_vals,
+ scalar_cases_len,
+ multi_cases_len,
+ err_set,
+ empty_enum,
+ )) |result| {
+ return result;
+ } else |err| switch (err) {
+ error.ComptimeBreak => {
+ const break_inst = sema.code.instructions.get(@intFromEnum(sema.comptime_break_inst));
+ if (break_inst.tag != .switch_continue) return error.ComptimeBreak;
+ const extra = sema.code.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data;
+ if (extra.block_inst != spa.switch_block_inst) return error.ComptimeBreak;
+ // This is a `switch_continue` targeting this block. Change the operand and start over.
+ const src = child_block.nodeOffset(extra.operand_src_node);
+ const new_operand_uncoerced = try sema.resolveInst(break_inst.data.@"break".operand);
+ const new_operand = try sema.coerce(child_block, maybe_ptr_operand_ty, new_operand_uncoerced, src);
+
+ try sema.emitBackwardBranch(child_block, src);
+
+ const val, const ref = if (operand_is_ref)
+ .{ try sema.analyzeLoad(child_block, src, new_operand, src), new_operand }
+ else
+ .{ new_operand, undefined };
+
+ const cond_ref = try sema.switchCond(child_block, src, val);
+
+ cond_val = try sema.resolveConstDefinedValue(child_block, src, cond_ref, .{
+ .needed_comptime_reason = "condition in comptime switch must be comptime-known",
+ .block_comptime_reason = child_block.comptime_reason,
+ });
+ spa.operand = .{ .simple = .{
+ .by_val = val,
+ .by_ref = ref,
+ .cond = cond_ref,
+ } };
+ },
+ else => |e| return e,
+ }
+ }
+}
+
fn resolveSwitchComptime(
sema: *Sema,
spa: SwitchProngAnalysis,
@@ -13148,6 +13485,7 @@ fn resolveSwitchComptime(
) CompileError!Air.Inst.Ref {
const merges = &child_block.label.?.merges;
const resolved_operand_val = try sema.resolveLazyValue(operand_val);
+
var extra_index: usize = special.end;
{
var scalar_i: usize = 0;