aboutsummaryrefslogtreecommitdiff
path: root/src/Sema.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2024-09-04 18:31:28 -0700
committerGitHub <noreply@github.com>2024-09-04 18:31:28 -0700
commit3929cac154d71a3e19fd028fc67c1d1d15823ca2 (patch)
tree3ab01ff8d25313b57bbb485e30a09bc9947448f7 /src/Sema.zig
parent7e31804870cac14063b2468f544fc77a4cbb616f (diff)
parent289c704b60c3e4b65bc00be55266b3f1c3fc27a3 (diff)
downloadzig-3929cac154d71a3e19fd028fc67c1d1d15823ca2.tar.gz
zig-3929cac154d71a3e19fd028fc67c1d1d15823ca2.zip
Merge pull request #21257 from mlugg/computed-goto-3
compiler: implement labeled switch/continue
Diffstat (limited to 'src/Sema.zig')
-rw-r--r--src/Sema.zig1009
1 files changed, 639 insertions, 370 deletions
diff --git a/src/Sema.zig b/src/Sema.zig
index 57a4809f95..7ab60adbf2 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
@@ -1563,6 +1580,8 @@ fn analyzeBodyInner(
// We are definitely called by `zirLoop`, which will treat the
// fact that this body does not terminate `noreturn` as an
// implicit repeat.
+ // TODO: since AIR has `repeat` now, we could change ZIR to generate
+ // more optimal code utilizing `repeat` instructions across blocks!
break;
}
},
@@ -1573,6 +1592,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
@@ -5884,17 +5910,30 @@ fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError
// Use `analyzeBodyInner` directly to push any comptime control flow up the stack.
try sema.analyzeBodyInner(&loop_block, body);
+ // TODO: since AIR has `repeat` now, we could change ZIR to generate
+ // more optimal code utilizing `repeat` instructions across blocks!
+ // For now, if the generated loop body does not terminate `noreturn`,
+ // then `analyzeBodyInner` is signalling that it ended with `repeat`.
+
const loop_block_len = loop_block.instructions.items.len;
if (loop_block_len > 0 and sema.typeOf(loop_block.instructions.items[loop_block_len - 1].toRef()).isNoReturn(zcu)) {
// If the loop ended with a noreturn terminator, then there is no way for it to loop,
// so we can just use the block instead.
try child_block.instructions.appendSlice(gpa, loop_block.instructions.items);
} else {
+ _ = try loop_block.addInst(.{
+ .tag = .repeat,
+ .data = .{ .repeat = .{
+ .loop_inst = loop_inst,
+ } },
+ });
+ // Note that `loop_block_len` is now off by one.
+
try child_block.instructions.append(gpa, loop_inst);
- try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len + loop_block_len);
+ try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len + loop_block_len + 1);
sema.air_instructions.items(.data)[@intFromEnum(loop_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(
- Air.Block{ .body_len = @intCast(loop_block_len) },
+ Air.Block{ .body_len = @intCast(loop_block_len + 1) },
);
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(loop_block.instructions.items));
}
@@ -6589,6 +6628,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;
@@ -11046,12 +11135,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,
@@ -11061,6 +11145,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(
@@ -11192,7 +11304,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,
@@ -11223,10 +11343,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") {
@@ -11242,16 +11376,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());
@@ -11262,17 +11396,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,
}
}
@@ -11371,19 +11505,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);
@@ -11392,7 +11526,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.
@@ -11406,7 +11540,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);
};
@@ -11470,13 +11604,18 @@ 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);
- try cases_extra.ensureUnusedCapacity(3 + coerce_block.instructions.items.len);
- cases_extra.appendAssumeCapacity(1); // items_len
- cases_extra.appendAssumeCapacity(@intCast(coerce_block.instructions.items.len)); // body_len
+ try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
+ 1 + // `item`, no ranges
+ coerce_block.instructions.items.len);
+ cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
+ .items_len = 1,
+ .ranges_len = 0,
+ .body_len = @intCast(coerce_block.instructions.items.len),
+ }));
cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
}
@@ -11489,7 +11628,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);
@@ -11505,21 +11644,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();
},
@@ -11536,7 +11701,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 = .{};
@@ -11546,15 +11711,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;
}
},
}
@@ -11787,9 +11952,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,
@@ -11810,13 +11979,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));
@@ -11824,7 +11993,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,
@@ -11878,20 +12047,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,
@@ -11960,17 +12129,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
@@ -12020,9 +12235,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);
@@ -12038,13 +12250,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,
@@ -12070,11 +12279,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.
@@ -12092,7 +12301,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) },
@@ -12120,7 +12329,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) },
@@ -12129,7 +12338,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| {
@@ -12137,7 +12346,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",
@@ -12154,9 +12363,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: '{}'",
@@ -12164,15 +12373,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,
@@ -12186,7 +12395,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,
@@ -12207,7 +12416,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) },
@@ -12234,7 +12443,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) },
@@ -12256,7 +12465,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) },
@@ -12272,9 +12481,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(
@@ -12347,7 +12556,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) {
@@ -12379,7 +12588,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)},
);
}
@@ -12400,7 +12609,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) },
@@ -12427,7 +12636,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) },
@@ -12436,7 +12645,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);
}
}
},
@@ -12455,16 +12664,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,
@@ -12508,24 +12715,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;
@@ -12533,54 +12722,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,
@@ -12593,6 +12818,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);
}
@@ -12699,21 +12985,18 @@ fn analyzeSwitchRuntimeBlock(
};
try branch_hints.append(gpa, prong_hint);
- try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
- cases_extra.appendAssumeCapacity(1); // items_len
- cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));
+ try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
+ 1 + // `item`, no ranges
+ case_block.instructions.items.len);
+ cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
+ .items_len = 1,
+ .ranges_len = 0,
+ .body_len = @intCast(case_block.instructions.items.len),
+ }));
cases_extra.appendAssumeCapacity(@intFromEnum(item));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
- var is_first = true;
- var prev_cond_br: Air.Inst.Index = undefined;
- var prev_hint: std.builtin.BranchHint = undefined;
- var first_else_body: []const Air.Inst.Index = &.{};
- defer gpa.free(first_else_body);
- var prev_then_body: []const Air.Inst.Index = &.{};
- defer gpa.free(prev_then_body);
-
var cases_len = scalar_cases_len;
var case_val_idx: usize = scalar_cases_len;
var multi_i: u32 = 0;
@@ -12723,31 +13006,27 @@ fn analyzeSwitchRuntimeBlock(
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
- extra_index += 1 + items_len;
+ extra_index += 1 + items_len + 2 * ranges_len;
const items = case_vals.items[case_val_idx..][0..items_len];
case_val_idx += items_len;
+ // TODO: @ptrCast slice once Sema supports it
+ const ranges: []const [2]Air.Inst.Ref = @as([*]const [2]Air.Inst.Ref, @ptrCast(case_vals.items[case_val_idx..]))[0..ranges_len];
+ case_val_idx += ranges_len * 2;
+
+ const body = sema.code.bodySlice(extra_index, info.body_len);
+ extra_index += info.body_len;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index;
// Generate all possible cases as scalar prongs.
if (info.is_inline) {
- const body_start = extra_index + 2 * ranges_len;
- const body = sema.code.bodySlice(body_start, info.body_len);
var emit_bb = false;
- var range_i: u32 = 0;
- while (range_i < ranges_len) : (range_i += 1) {
- const range_items = case_vals.items[case_val_idx..][0..2];
- extra_index += 2;
- case_val_idx += 2;
-
- const item_first_ref = range_items[0];
- const item_last_ref = range_items[1];
-
- var item = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item_first_ref, undefined) catch unreachable;
- const item_last = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item_last_ref, undefined) catch unreachable;
+ for (ranges, 0..) |range_items, range_i| {
+ var item = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, range_items[0], undefined) catch unreachable;
+ const item_last = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, range_items[1], undefined) catch unreachable;
while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({
// Previous validation has resolved any possible lazy values.
@@ -12785,9 +13064,14 @@ fn analyzeSwitchRuntimeBlock(
);
try branch_hints.append(gpa, prong_hint);
- try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
- cases_extra.appendAssumeCapacity(1); // items_len
- cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));
+ try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
+ 1 + // `item`, no ranges
+ case_block.instructions.items.len);
+ cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
+ .items_len = 1,
+ .ranges_len = 0,
+ .body_len = @intCast(case_block.instructions.items.len),
+ }));
cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
@@ -12834,134 +13118,39 @@ fn analyzeSwitchRuntimeBlock(
};
try branch_hints.append(gpa, prong_hint);
- try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
- cases_extra.appendAssumeCapacity(1); // items_len
- cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));
+ try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
+ 1 + // `item`, no ranges
+ case_block.instructions.items.len);
+ cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
+ .items_len = 1,
+ .ranges_len = 0,
+ .body_len = @intCast(case_block.instructions.items.len),
+ }));
cases_extra.appendAssumeCapacity(@intFromEnum(item));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
- extra_index += info.body_len;
continue;
}
- var any_ok: Air.Inst.Ref = .none;
+ cases_len += 1;
- // If there are any ranges, we have to put all the items into the
- // else prong. Otherwise, we can take advantage of multiple items
- // mapping to the same body.
- if (ranges_len == 0) {
- cases_len += 1;
-
- const analyze_body = if (union_originally)
- for (items) |item| {
- const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
- const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
- if (field_ty.zigTypeTag(zcu) != .noreturn) break true;
- } else false
- else
- true;
-
- const body = sema.code.bodySlice(extra_index, info.body_len);
- extra_index += info.body_len;
- const prong_hint: std.builtin.BranchHint = if (err_set and
- try sema.maybeErrorUnwrap(&case_block, body, operand, operand_src, allow_err_code_unwrap))
- h: {
- // nothing to do here. weight against error branch
- break :h .unlikely;
- } else if (analyze_body) h: {
- break :h try spa.analyzeProngRuntime(
- &case_block,
- .normal,
- body,
- info.capture,
- child_block.src(.{ .switch_capture = .{
- .switch_node_offset = switch_node_offset,
- .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
- } }),
- items,
- .none,
- false,
- );
- } else h: {
- _ = try case_block.addNoOp(.unreach);
- break :h .none;
- };
-
- try branch_hints.append(gpa, prong_hint);
- try cases_extra.ensureUnusedCapacity(gpa, 2 + items.len +
- case_block.instructions.items.len);
-
- cases_extra.appendAssumeCapacity(@intCast(items.len));
- cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));
-
- for (items) |item| {
- cases_extra.appendAssumeCapacity(@intFromEnum(item));
- }
-
- cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
- } else {
+ const analyze_body = if (union_originally)
for (items) |item| {
- const cmp_ok = try case_block.addBinOp(if (case_block.float_mode == .optimized) .cmp_eq_optimized else .cmp_eq, operand, item);
- if (any_ok != .none) {
- any_ok = try case_block.addBinOp(.bool_or, any_ok, cmp_ok);
- } else {
- any_ok = cmp_ok;
- }
- }
-
- var range_i: usize = 0;
- while (range_i < ranges_len) : (range_i += 1) {
- const range_items = case_vals.items[case_val_idx..][0..2];
- extra_index += 2;
- case_val_idx += 2;
-
- const item_first = range_items[0];
- const item_last = range_items[1];
-
- // operand >= first and operand <= last
- const range_first_ok = try case_block.addBinOp(
- if (case_block.float_mode == .optimized) .cmp_gte_optimized else .cmp_gte,
- operand,
- item_first,
- );
- const range_last_ok = try case_block.addBinOp(
- if (case_block.float_mode == .optimized) .cmp_lte_optimized else .cmp_lte,
- operand,
- item_last,
- );
- const range_ok = try case_block.addBinOp(
- .bool_and,
- range_first_ok,
- range_last_ok,
- );
- if (any_ok != .none) {
- any_ok = try case_block.addBinOp(.bool_or, any_ok, range_ok);
- } else {
- any_ok = range_ok;
- }
- }
-
- const new_cond_br = try case_block.addInstAsIndex(.{ .tag = .cond_br, .data = .{
- .pl_op = .{
- .operand = any_ok,
- .payload = undefined,
- },
- } });
- var cond_body = try case_block.instructions.toOwnedSlice(gpa);
- defer gpa.free(cond_body);
-
- case_block.instructions.shrinkRetainingCapacity(0);
- case_block.error_return_trace_index = child_block.error_return_trace_index;
+ const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
+ const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
+ if (field_ty.zigTypeTag(zcu) != .noreturn) break true;
+ } else false
+ else
+ true;
- const body = sema.code.bodySlice(extra_index, info.body_len);
- extra_index += info.body_len;
- const prong_hint: std.builtin.BranchHint = if (err_set and
- try sema.maybeErrorUnwrap(&case_block, body, operand, operand_src, allow_err_code_unwrap))
- h: {
- // nothing to do here. weight against error branch
- break :h .unlikely;
- } else try spa.analyzeProngRuntime(
+ const prong_hint: std.builtin.BranchHint = if (err_set and
+ try sema.maybeErrorUnwrap(&case_block, body, operand, operand_src, allow_err_code_unwrap))
+ h: {
+ // nothing to do here. weight against error branch
+ break :h .unlikely;
+ } else if (analyze_body) h: {
+ break :h try spa.analyzeProngRuntime(
&case_block,
.normal,
body,
@@ -12974,40 +13163,36 @@ fn analyzeSwitchRuntimeBlock(
.none,
false,
);
+ } else h: {
+ _ = try case_block.addNoOp(.unreach);
+ break :h .none;
+ };
- if (is_first) {
- is_first = false;
- first_else_body = cond_body;
- cond_body = &.{};
- } else {
- try sema.air_extra.ensureUnusedCapacity(
- gpa,
- @typeInfo(Air.CondBr).@"struct".fields.len + prev_then_body.len + cond_body.len,
- );
+ try branch_hints.append(gpa, prong_hint);
- sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{
- .then_body_len = @intCast(prev_then_body.len),
- .else_body_len = @intCast(cond_body.len),
- .branch_hints = .{
- .true = prev_hint,
- .false = .none,
- // Code coverage is desired for error handling.
- .then_cov = .poi,
- .else_cov = .poi,
- },
- });
- sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body));
- sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cond_body));
- }
- gpa.free(prev_then_body);
- prev_then_body = try case_block.instructions.toOwnedSlice(gpa);
- prev_cond_br = new_cond_br;
- prev_hint = prong_hint;
+ try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
+ items.len + 2 * ranges_len +
+ case_block.instructions.items.len);
+ cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
+ .items_len = @intCast(items.len),
+ .ranges_len = @intCast(ranges_len),
+ .body_len = @intCast(case_block.instructions.items.len),
+ }));
+
+ for (items) |item| {
+ cases_extra.appendAssumeCapacity(@intFromEnum(item));
}
+ for (ranges) |range| {
+ cases_extra.appendSliceAssumeCapacity(&.{
+ @intFromEnum(range[0]),
+ @intFromEnum(range[1]),
+ });
+ }
+
+ cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
- var final_else_body: []const Air.Inst.Index = &.{};
- if (special.body.len != 0 or !is_first or case_block.wantSafety()) {
+ const else_body: []const Air.Inst.Index = if (special.body.len != 0 or case_block.wantSafety()) else_body: {
var emit_bb = false;
if (special.is_inline) switch (operand_ty.zigTypeTag(zcu)) {
.@"enum" => {
@@ -13054,9 +13239,14 @@ fn analyzeSwitchRuntimeBlock(
};
try branch_hints.append(gpa, prong_hint);
- try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
- cases_extra.appendAssumeCapacity(1); // items_len
- cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));
+ try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
+ 1 + // `item`, no ranges
+ case_block.instructions.items.len);
+ cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
+ .items_len = 1,
+ .ranges_len = 0,
+ .body_len = @intCast(case_block.instructions.items.len),
+ }));
cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
@@ -13100,9 +13290,14 @@ fn analyzeSwitchRuntimeBlock(
);
try branch_hints.append(gpa, prong_hint);
- try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
- cases_extra.appendAssumeCapacity(1); // items_len
- cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));
+ try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
+ 1 + // `item`, no ranges
+ case_block.instructions.items.len);
+ cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
+ .items_len = 1,
+ .ranges_len = 0,
+ .body_len = @intCast(case_block.instructions.items.len),
+ }));
cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
@@ -13135,9 +13330,14 @@ fn analyzeSwitchRuntimeBlock(
);
try branch_hints.append(gpa, prong_hint);
- try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
- cases_extra.appendAssumeCapacity(1); // items_len
- cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));
+ try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
+ 1 + // `item`, no ranges
+ case_block.instructions.items.len);
+ cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
+ .items_len = 1,
+ .ranges_len = 0,
+ .body_len = @intCast(case_block.instructions.items.len),
+ }));
cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
@@ -13167,9 +13367,14 @@ fn analyzeSwitchRuntimeBlock(
);
try branch_hints.append(gpa, prong_hint);
- try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
- cases_extra.appendAssumeCapacity(1); // items_len
- cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));
+ try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
+ 1 + // `item`, no ranges
+ case_block.instructions.items.len);
+ cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
+ .items_len = 1,
+ .ranges_len = 0,
+ .body_len = @intCast(case_block.instructions.items.len),
+ }));
cases_extra.appendAssumeCapacity(@intFromEnum(Air.Inst.Ref.bool_true));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
@@ -13197,9 +13402,14 @@ fn analyzeSwitchRuntimeBlock(
);
try branch_hints.append(gpa, prong_hint);
- try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
- cases_extra.appendAssumeCapacity(1); // items_len
- cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));
+ try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
+ 1 + // `item`, no ranges
+ case_block.instructions.items.len);
+ cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
+ .items_len = 1,
+ .ranges_len = 0,
+ .body_len = @intCast(case_block.instructions.items.len),
+ }));
cases_extra.appendAssumeCapacity(@intFromEnum(Air.Inst.Ref.bool_false));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
@@ -13263,41 +13473,22 @@ fn analyzeSwitchRuntimeBlock(
break :h .cold;
};
- if (is_first) {
- try branch_hints.append(gpa, else_hint);
- final_else_body = case_block.instructions.items;
- } else {
- try branch_hints.append(gpa, .none); // we have the range conditionals first
- try sema.air_extra.ensureUnusedCapacity(gpa, prev_then_body.len +
- @typeInfo(Air.CondBr).@"struct".fields.len + case_block.instructions.items.len);
-
- sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{
- .then_body_len = @intCast(prev_then_body.len),
- .else_body_len = @intCast(case_block.instructions.items.len),
- .branch_hints = .{
- .true = prev_hint,
- .false = else_hint,
- .then_cov = .poi,
- .else_cov = .poi,
- },
- });
- sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body));
- sema.air_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
- final_else_body = first_else_body;
- }
- } else {
+ try branch_hints.append(gpa, else_hint);
+ break :else_body case_block.instructions.items;
+ } else else_body: {
try branch_hints.append(gpa, .none);
- }
+ break :else_body &.{};
+ };
assert(branch_hints.items.len == cases_len + 1);
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +
- cases_extra.items.len + final_else_body.len +
+ cases_extra.items.len + else_body.len +
(std.math.divCeil(usize, branch_hints.items.len, 10) catch unreachable)); // branch hints
const payload_index = sema.addExtraAssumeCapacity(Air.SwitchBr{
.cases_len = @intCast(cases_len),
- .else_body_len = @intCast(final_else_body.len),
+ .else_body_len = @intCast(else_body.len),
});
{
@@ -13316,10 +13507,10 @@ fn analyzeSwitchRuntimeBlock(
}
}
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cases_extra.items));
- sema.air_extra.appendSliceAssumeCapacity(@ptrCast(final_else_body));
+ 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,
@@ -13327,6 +13518,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,
@@ -13344,6 +13606,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;
@@ -37507,15 +37770,21 @@ pub fn addExtra(sema: *Sema, extra: anytype) Allocator.Error!u32 {
}
pub fn addExtraAssumeCapacity(sema: *Sema, extra: anytype) u32 {
- const fields = std.meta.fields(@TypeOf(extra));
const result: u32 = @intCast(sema.air_extra.items.len);
- inline for (fields) |field| {
- sema.air_extra.appendAssumeCapacity(switch (field.type) {
- u32 => @field(extra, field.name),
- i32, Air.CondBr.BranchHints => @bitCast(@field(extra, field.name)),
- Air.Inst.Ref, InternPool.Index => @intFromEnum(@field(extra, field.name)),
+ sema.air_extra.appendSliceAssumeCapacity(&payloadToExtraItems(extra));
+ return result;
+}
+
+fn payloadToExtraItems(data: anytype) [@typeInfo(@TypeOf(data)).@"struct".fields.len]u32 {
+ const fields = @typeInfo(@TypeOf(data)).@"struct".fields;
+ var result: [fields.len]u32 = undefined;
+ inline for (&result, fields) |*val, field| {
+ val.* = switch (field.type) {
+ u32 => @field(data, field.name),
+ i32, Air.CondBr.BranchHints => @bitCast(@field(data, field.name)),
+ Air.Inst.Ref, InternPool.Index => @intFromEnum(@field(data, field.name)),
else => @compileError("bad field type: " ++ @typeName(field.type)),
- });
+ };
}
return result;
}