diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2023-06-13 08:45:12 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-06-13 08:45:12 -0700 |
| commit | df6319418a08b611bae23307ace6688f95bedea2 (patch) | |
| tree | 6cdf873c6800aebccdd8c03a443f9594acb84729 /src/Sema.zig | |
| parent | 387f9568ad0dabd426d382efb45b9c52a4ccc5bb (diff) | |
| parent | 42dc7539c5b0a39e9b64c5ad92757945b0ca05ad (diff) | |
| download | zig-df6319418a08b611bae23307ace6688f95bedea2.tar.gz zig-df6319418a08b611bae23307ace6688f95bedea2.zip | |
Merge pull request #15880 from mlugg/feat/better-switch-zir-2
Simplify and compact switch ZIR, and resolve union payload captures with PTR
Diffstat (limited to 'src/Sema.zig')
| -rw-r--r-- | src/Sema.zig | 1381 |
1 files changed, 950 insertions, 431 deletions
diff --git a/src/Sema.zig b/src/Sema.zig index cb69fa92d8..7c4968b0e5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -277,12 +277,6 @@ pub const Block = struct { c_import_buf: ?*std.ArrayList(u8) = null, - /// type of `err` in `else => |err|` - switch_else_err_ty: ?Type = null, - - /// Value for switch_capture in an inline case - inline_case_capture: Air.Inst.Ref = .none, - const ComptimeReason = union(enum) { c_import: struct { block: *Block, @@ -397,7 +391,6 @@ pub const Block = struct { .want_safety = parent.want_safety, .float_mode = parent.float_mode, .c_import_buf = parent.c_import_buf, - .switch_else_err_ty = parent.switch_else_err_ty, .error_return_trace_index = parent.error_return_trace_index, }; } @@ -1014,14 +1007,8 @@ fn analyzeBodyInner( .slice_start => try sema.zirSliceStart(block, inst), .slice_length => try sema.zirSliceLength(block, inst), .str => try sema.zirStr(block, inst), - .switch_block => try sema.zirSwitchBlock(block, inst), - .switch_cond => try sema.zirSwitchCond(block, inst, false), - .switch_cond_ref => try sema.zirSwitchCond(block, inst, true), - .switch_capture => try sema.zirSwitchCapture(block, inst, false, false), - .switch_capture_ref => try sema.zirSwitchCapture(block, inst, false, true), - .switch_capture_multi => try sema.zirSwitchCapture(block, inst, true, false), - .switch_capture_multi_ref => try sema.zirSwitchCapture(block, inst, true, true), - .switch_capture_tag => try sema.zirSwitchCaptureTag(block, inst), + .switch_block => try sema.zirSwitchBlock(block, inst, false), + .switch_block_ref => try sema.zirSwitchBlock(block, inst, true), .type_info => try sema.zirTypeInfo(block, inst), .size_of => try sema.zirSizeOf(block, inst), .bit_size_of => try sema.zirBitSizeOf(block, inst), @@ -1225,7 +1212,7 @@ fn analyzeBodyInner( i += 1; continue; }, - .errdefer_err_code => unreachable, // never appears in a body + .value_placeholder => unreachable, // never appears in a body }; }, @@ -2405,6 +2392,34 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { return error.AnalysisFail; } +/// Given an ErrorMsg, modify its message and source location to the given values, turning the +/// original message into a note. Notes on the original message are preserved as further notes. +/// Reference trace is preserved. +fn reparentOwnedErrorMsg( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + msg: *Module.ErrorMsg, + comptime format: []const u8, + args: anytype, +) !void { + const mod = sema.mod; + const src_decl = mod.declPtr(block.src_decl); + const resolved_src = src.toSrcLoc(src_decl, mod); + const msg_str = try std.fmt.allocPrint(mod.gpa, format, args); + + const orig_notes = msg.notes.len; + msg.notes = try sema.gpa.realloc(msg.notes, orig_notes + 1); + std.mem.copyBackwards(Module.ErrorMsg, msg.notes[1..], msg.notes[0..orig_notes]); + msg.notes[0] = .{ + .src_loc = msg.src_loc, + .msg = msg.msg, + }; + + msg.src_loc = resolved_src; + msg.msg = msg_str; +} + const align_ty = Type.u29; fn analyzeAsAlign( @@ -10085,251 +10100,544 @@ fn zirSliceLength(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError return sema.analyzeSlice(block, src, array_ptr, start, len, sentinel, sentinel_src, ptr_src, start_src, end_src, true); } -fn zirSwitchCapture( +/// Holds common data used when analyzing or resolving switch prong bodies, +/// including setting up captures. +const SwitchProngAnalysis = struct { sema: *Sema, - block: *Block, - inst: Zir.Inst.Index, - is_multi: bool, - is_ref: bool, -) CompileError!Air.Inst.Ref { - const tracy = trace(@src()); - defer tracy.end(); + /// 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, + /// 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, + /// The index of the `switch_block` instruction itself. + switch_block_inst: Zir.Inst.Index, + /// The dummy index into which inline tag captures should be placed. May be + /// undefined if no prong has a tag capture. + tag_capture_inst: Zir.Inst.Index, + + /// Resolve a switch prong which is determined at comptime to have no peers. + /// Uses `resolveBlockBody`. Sets up captures as needed. + fn resolveProngComptime( + spa: SwitchProngAnalysis, + child_block: *Block, + prong_type: enum { normal, special }, + prong_body: []const Zir.Inst.Index, + capture: Zir.Inst.SwitchBlock.ProngInfo.Capture, + /// Must use the `scalar_capture`, `special_capture`, or `multi_capture` union field. + raw_capture_src: Module.SwitchProngSrc, + /// The set of all values which can reach this prong. May be undefined + /// if the prong is special or contains ranges. + case_vals: []const Air.Inst.Ref, + /// The inline capture of this prong. If this is not an inline prong, + /// this is `.none`. + inline_case_capture: Air.Inst.Ref, + /// Whether this prong has an inline tag capture. If `true`, then + /// `inline_case_capture` cannot be `.none`. + has_tag_capture: bool, + merges: *Block.Merges, + ) CompileError!Air.Inst.Ref { + const sema = spa.sema; + const src = sema.code.instructions.items(.data)[spa.switch_block_inst].pl_node.src(); + + if (has_tag_capture) { + const tag_ref = try spa.analyzeTagCapture(child_block, raw_capture_src, inline_case_capture); + sema.inst_map.putAssumeCapacity(spa.tag_capture_inst, tag_ref); + } + defer if (has_tag_capture) assert(sema.inst_map.remove(spa.tag_capture_inst)); + + switch (capture) { + .none => { + return sema.resolveBlockBody(spa.parent_block, src, child_block, prong_body, spa.switch_block_inst, merges); + }, + + .by_val, .by_ref => { + const capture_ref = try spa.analyzeCapture( + child_block, + capture == .by_ref, + prong_type == .special, + raw_capture_src, + case_vals, + inline_case_capture, + ); - const mod = sema.mod; - const gpa = sema.gpa; - const zir_datas = sema.code.instructions.items(.data); - const capture_info = zir_datas[inst].switch_capture; - const switch_info = zir_datas[capture_info.switch_inst].pl_node; - const switch_extra = sema.code.extraData(Zir.Inst.SwitchBlock, switch_info.payload_index); - const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = switch_info.src_node }; - const cond_inst = Zir.refToIndex(switch_extra.data.operand).?; - const cond_info = zir_datas[cond_inst].un_node; - const cond_tag = sema.code.instructions.items(.tag)[cond_inst]; - const operand_is_ref = cond_tag == .switch_cond_ref; - const operand_ptr = try sema.resolveInst(cond_info.operand); - const operand_ptr_ty = sema.typeOf(operand_ptr); - const operand_ty = if (operand_is_ref) operand_ptr_ty.childType(mod) else operand_ptr_ty; - - if (block.inline_case_capture != .none) { - const item_val = sema.resolveConstValue(block, .unneeded, block.inline_case_capture, undefined) catch unreachable; - const resolved_item_val = try sema.resolveLazyValue(item_val); - if (operand_ty.zigTypeTag(mod) == .Union) { - const field_index = @intCast(u32, operand_ty.unionTagFieldIndex(resolved_item_val, mod).?); - const union_obj = mod.typeToUnion(operand_ty).?; - const field_ty = union_obj.fields.values()[field_index].ty; - if (try sema.resolveDefinedValue(block, sema.src, operand_ptr)) |union_val| { - if (is_ref) { + if (sema.typeOf(capture_ref).isNoReturn(sema.mod)) { + // This prong should be unreachable! + return Air.Inst.Ref.unreachable_value; + } + + sema.inst_map.putAssumeCapacity(spa.switch_block_inst, capture_ref); + defer assert(sema.inst_map.remove(spa.switch_block_inst)); + + return sema.resolveBlockBody(spa.parent_block, src, child_block, prong_body, spa.switch_block_inst, merges); + }, + } + } + + /// Analyze a switch prong which may have peers at runtime. + /// Uses `analyzeBodyRuntimeBreak`. Sets up captures as needed. + fn analyzeProngRuntime( + spa: SwitchProngAnalysis, + case_block: *Block, + prong_type: enum { normal, special }, + prong_body: []const Zir.Inst.Index, + capture: Zir.Inst.SwitchBlock.ProngInfo.Capture, + /// Must use the `scalar`, `special`, or `multi_capture` union field. + raw_capture_src: Module.SwitchProngSrc, + /// The set of all values which can reach this prong. May be undefined + /// if the prong is special or contains ranges. + case_vals: []const Air.Inst.Ref, + /// The inline capture of this prong. If this is not an inline prong, + /// this is `.none`. + inline_case_capture: Air.Inst.Ref, + /// Whether this prong has an inline tag capture. If `true`, then + /// `inline_case_capture` cannot be `.none`. + has_tag_capture: bool, + ) CompileError!void { + const sema = spa.sema; + + if (has_tag_capture) { + const tag_ref = try spa.analyzeTagCapture(case_block, raw_capture_src, inline_case_capture); + sema.inst_map.putAssumeCapacity(spa.tag_capture_inst, tag_ref); + } + defer if (has_tag_capture) assert(sema.inst_map.remove(spa.tag_capture_inst)); + + switch (capture) { + .none => { + return sema.analyzeBodyRuntimeBreak(case_block, prong_body); + }, + + .by_val, .by_ref => { + const capture_ref = try spa.analyzeCapture( + case_block, + capture == .by_ref, + prong_type == .special, + raw_capture_src, + case_vals, + inline_case_capture, + ); + + if (sema.typeOf(capture_ref).isNoReturn(sema.mod)) { + // No need to analyze any further, the prong is unreachable + return; + } + + sema.inst_map.putAssumeCapacity(spa.switch_block_inst, capture_ref); + defer assert(sema.inst_map.remove(spa.switch_block_inst)); + + return sema.analyzeBodyRuntimeBreak(case_block, prong_body); + }, + } + } + + fn analyzeTagCapture( + spa: SwitchProngAnalysis, + block: *Block, + raw_capture_src: Module.SwitchProngSrc, + inline_case_capture: Air.Inst.Ref, + ) CompileError!Air.Inst.Ref { + const sema = spa.sema; + const mod = sema.mod; + const operand_ty = sema.typeOf(spa.operand); + if (operand_ty.zigTypeTag(mod) != .Union) { + const zir_datas = sema.code.instructions.items(.data); + const switch_node_offset = zir_datas[spa.switch_block_inst].pl_node.src_node; + const raw_tag_capture_src: Module.SwitchProngSrc = switch (raw_capture_src) { + .scalar_capture => |i| .{ .scalar_tag_capture = i }, + .multi_capture => |i| .{ .multi_tag_capture = i }, + .special_capture => .special_tag_capture, + else => unreachable, + }; + const capture_src = raw_tag_capture_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .none); + const msg = msg: { + const msg = try sema.errMsg(block, capture_src, "cannot capture tag of non-union type '{}'", .{ + operand_ty.fmt(mod), + }); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, operand_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } + assert(inline_case_capture != .none); + return inline_case_capture; + } + + fn analyzeCapture( + spa: SwitchProngAnalysis, + block: *Block, + capture_byref: bool, + is_special_prong: bool, + raw_capture_src: Module.SwitchProngSrc, + case_vals: []const Air.Inst.Ref, + inline_case_capture: Air.Inst.Ref, + ) CompileError!Air.Inst.Ref { + const sema = spa.sema; + const mod = sema.mod; + + const zir_datas = sema.code.instructions.items(.data); + const switch_node_offset = zir_datas[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: LazySrcLoc = .{ .node_offset_switch_operand = switch_node_offset }; + + if (inline_case_capture != .none) { + const item_val = sema.resolveConstValue(block, .unneeded, inline_case_capture, "") catch unreachable; + if (operand_ty.zigTypeTag(mod) == .Union) { + const field_index = @intCast(u32, operand_ty.unionTagFieldIndex(item_val, mod).?); + const union_obj = mod.typeToUnion(operand_ty).?; + const field_ty = union_obj.fields.values()[field_index].ty; + if (capture_byref) { const ptr_field_ty = try Type.ptr(sema.arena, mod, .{ .pointee_type = field_ty, .mutable = operand_ptr_ty.ptrIsMutable(mod), .@"volatile" = operand_ptr_ty.isVolatilePtr(mod), .@"addrspace" = operand_ptr_ty.ptrAddressSpace(mod), }); - return sema.addConstant(ptr_field_ty, (try mod.intern(.{ .ptr = .{ - .ty = ptr_field_ty.toIntern(), - .addr = .{ .field = .{ - .base = union_val.toIntern(), - .index = field_index, - } }, - } })).toValue()); + if (try sema.resolveDefinedValue(block, sema.src, spa.operand_ptr)) |union_ptr| { + return sema.addConstant( + ptr_field_ty, + (try mod.intern(.{ .ptr = .{ + .ty = ptr_field_ty.toIntern(), + .addr = .{ .field = .{ + .base = union_ptr.toIntern(), + .index = field_index, + } }, + } })).toValue(), + ); + } + return block.addStructFieldPtr(spa.operand_ptr, field_index, ptr_field_ty); + } else { + if (try sema.resolveDefinedValue(block, sema.src, spa.operand)) |union_val| { + const tag_and_val = mod.intern_pool.indexToKey(union_val.toIntern()).un; + return sema.addConstant(field_ty, tag_and_val.val.toValue()); + } + return block.addStructFieldVal(spa.operand, field_index, field_ty); } - return sema.addConstant( - field_ty, - mod.intern_pool.indexToKey(union_val.toIntern()).un.val.toValue(), - ); - } - if (is_ref) { - const ptr_field_ty = try Type.ptr(sema.arena, mod, .{ - .pointee_type = field_ty, - .mutable = operand_ptr_ty.ptrIsMutable(mod), - .@"volatile" = operand_ptr_ty.isVolatilePtr(mod), - .@"addrspace" = operand_ptr_ty.ptrAddressSpace(mod), - }); - return block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty); + } else if (capture_byref) { + return sema.addConstantMaybeRef(block, operand_ty, item_val, true); } else { - return block.addStructFieldVal(operand_ptr, field_index, field_ty); + return inline_case_capture; } - } else if (is_ref) { - return sema.addConstantMaybeRef(block, operand_ty, resolved_item_val, true); - } else { - return block.inline_case_capture; } - } - const operand = if (operand_is_ref) - try sema.analyzeLoad(block, operand_src, operand_ptr, operand_src) - else - operand_ptr; + if (is_special_prong) { + if (capture_byref) { + return spa.operand_ptr; + } - if (capture_info.prong_index == std.math.maxInt(@TypeOf(capture_info.prong_index))) { - // It is the else/`_` prong. - if (is_ref) { - return operand_ptr; + switch (operand_ty.zigTypeTag(mod)) { + .ErrorSet => if (spa.else_error_ty) |ty| { + return sema.bitCast(block, ty, spa.operand, operand_src, null); + } else { + try block.addUnreachable(false); + return Air.Inst.Ref.unreachable_value; + }, + else => return spa.operand, + } } switch (operand_ty.zigTypeTag(mod)) { - .ErrorSet => if (block.switch_else_err_ty) |some| { - return sema.bitCast(block, some, operand, operand_src, null); - } else { - try block.addUnreachable(false); - return Air.Inst.Ref.unreachable_value; - }, - else => return operand, - } - } + .Union => { + const union_obj = mod.typeToUnion(operand_ty).?; + const first_item_val = sema.resolveConstValue(block, .unneeded, case_vals[0], "") catch unreachable; - const items = if (is_multi) - switch_extra.data.getMultiProng(sema.code, switch_extra.end, capture_info.prong_index).items - else - &[_]Zir.Inst.Ref{ - switch_extra.data.getScalarProng(sema.code, switch_extra.end, capture_info.prong_index).item, - }; + const first_field_index = @intCast(u32, operand_ty.unionTagFieldIndex(first_item_val, mod).?); + const first_field = union_obj.fields.values()[first_field_index]; - switch (operand_ty.zigTypeTag(mod)) { - .Union => { - const union_obj = mod.typeToUnion(operand_ty).?; - const first_item = try sema.resolveInst(items[0]); - // Previous switch validation ensured this will succeed - const first_item_val = sema.resolveConstValue(block, .unneeded, first_item, "") catch unreachable; - - const first_field_index = @intCast(u32, operand_ty.unionTagFieldIndex(first_item_val, mod).?); - const first_field = union_obj.fields.values()[first_field_index]; - - for (items[1..], 0..) |item, i| { - const item_ref = try sema.resolveInst(item); - // Previous switch validation ensured this will succeed - const item_val = sema.resolveConstValue(block, .unneeded, item_ref, "") catch unreachable; - - const field_index = operand_ty.unionTagFieldIndex(item_val, mod).?; - const field = union_obj.fields.values()[field_index]; - if (!field.ty.eql(first_field.ty, mod)) { - const msg = msg: { - const raw_capture_src = Module.SwitchProngSrc{ .multi_capture = capture_info.prong_index }; - const capture_src = raw_capture_src.resolve(mod, mod.declPtr(block.src_decl), switch_info.src_node, .first); + const field_tys = try sema.arena.alloc(Type, case_vals.len); + for (case_vals, field_tys) |item, *field_ty| { + const item_val = sema.resolveConstValue(block, .unneeded, item, "") catch unreachable; + const field_idx = @intCast(u32, operand_ty.unionTagFieldIndex(item_val, sema.mod).?); + field_ty.* = union_obj.fields.values()[field_idx].ty; + } - const msg = try sema.errMsg(block, capture_src, "capture group with incompatible types", .{}); - errdefer msg.destroy(gpa); + // Fast path: if all the operands are the same type already, we don't need to hit + // PTR! This will also allow us to emit simpler code. + const same_types = for (field_tys[1..]) |field_ty| { + if (!field_ty.eql(field_tys[0], sema.mod)) break false; + } else true; - const raw_first_item_src = Module.SwitchProngSrc{ .multi = .{ .prong = capture_info.prong_index, .item = 0 } }; - const first_item_src = raw_first_item_src.resolve(mod, mod.declPtr(block.src_decl), switch_info.src_node, .first); - const raw_item_src = Module.SwitchProngSrc{ .multi = .{ .prong = capture_info.prong_index, .item = 1 + @intCast(u32, i) } }; - const item_src = raw_item_src.resolve(mod, mod.declPtr(block.src_decl), switch_info.src_node, .first); - try sema.errNote(block, first_item_src, msg, "type '{}' here", .{first_field.ty.fmt(mod)}); - try sema.errNote(block, item_src, msg, "type '{}' here", .{field.ty.fmt(mod)}); - break :msg msg; + const capture_ty = if (same_types) field_tys[0] else capture_ty: { + // We need values to run PTR on, so make a bunch of undef constants. + const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len); + for (dummy_captures, field_tys) |*dummy, field_ty| { + dummy.* = try sema.addConstUndef(field_ty); + } + + const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len); + @memset(case_srcs, .unneeded); + + break :capture_ty sema.resolvePeerTypes(block, .unneeded, dummy_captures, .{ .override = case_srcs }) catch |err| switch (err) { + error.NeededSourceLocation => { + // This must be a multi-prong so this must be a `multi_capture` src + const multi_idx = raw_capture_src.multi_capture; + const src_decl_ptr = sema.mod.declPtr(block.src_decl); + for (case_srcs, 0..) |*case_src, i| { + const raw_case_src: Module.SwitchProngSrc = .{ .multi = .{ .prong = multi_idx, .item = @intCast(u32, i) } }; + case_src.* = raw_case_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); + } + const capture_src = raw_capture_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); + _ = sema.resolvePeerTypes(block, capture_src, dummy_captures, .{ .override = case_srcs }) catch |err1| switch (err1) { + error.AnalysisFail => { + const msg = sema.err orelse return error.AnalysisFail; + try sema.reparentOwnedErrorMsg(block, capture_src, msg, "capture group with incompatible types", .{}); + return error.AnalysisFail; + }, + else => |e| return e, + }; + unreachable; + }, + else => |e| return e, }; - return sema.failWithOwnedErrorMsg(msg); - } - } + }; - if (is_ref) { - const field_ty_ptr = try Type.ptr(sema.arena, mod, .{ - .pointee_type = first_field.ty, - .@"addrspace" = .generic, - .mutable = operand_ptr_ty.ptrIsMutable(mod), - }); + // By-reference captures have some further restrictions which make them easier to emit + if (capture_byref) { + const operand_ptr_info = operand_ptr_ty.ptrInfo(mod); + const capture_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = capture_ty, + .@"addrspace" = operand_ptr_info.@"addrspace", + .mutable = operand_ptr_info.mutable, + .@"volatile" = operand_ptr_info.@"volatile", + // TODO: alignment! + }); - if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |op_ptr_val| { - return sema.addConstant(field_ty_ptr, (try mod.intern(.{ .ptr = .{ - .ty = field_ty_ptr.toIntern(), - .addr = .{ .field = .{ - .base = op_ptr_val.toIntern(), - .index = first_field_index, - } }, - } })).toValue()); + // By-ref captures of hetereogeneous types are only allowed if each field + // pointer type is in-memory coercible to the capture pointer type. + if (!same_types) { + for (field_tys, 0..) |field_ty, i| { + const field_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = field_ty, + .@"addrspace" = operand_ptr_info.@"addrspace", + .mutable = operand_ptr_info.mutable, + .@"volatile" = operand_ptr_info.@"volatile", + // TODO: alignment! + }); + if (.ok != try sema.coerceInMemoryAllowed(block, capture_ptr_ty, field_ptr_ty, false, sema.mod.getTarget(), .unneeded, .unneeded)) { + const multi_idx = raw_capture_src.multi_capture; + const src_decl_ptr = sema.mod.declPtr(block.src_decl); + const capture_src = raw_capture_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); + const raw_case_src: Module.SwitchProngSrc = .{ .multi = .{ .prong = multi_idx, .item = @intCast(u32, i) } }; + const case_src = raw_case_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); + const msg = msg: { + const msg = try sema.errMsg(block, capture_src, "capture group with incompatible types", .{}); + errdefer msg.destroy(sema.gpa); + try sema.errNote(block, case_src, msg, "pointer type child '{}' cannot cast into resolved pointer type child '{}'", .{ + field_ty.fmt(sema.mod), + capture_ty.fmt(sema.mod), + }); + try sema.errNote(block, capture_src, msg, "this coercion is only possible when capturing by value", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } + } + } + + if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |op_ptr_val| { + if (op_ptr_val.isUndef(mod)) return sema.addConstUndef(capture_ptr_ty); + return sema.addConstant( + capture_ptr_ty, + (try mod.intern(.{ .ptr = .{ + .ty = capture_ptr_ty.toIntern(), + .addr = .{ .field = .{ + .base = op_ptr_val.toIntern(), + .index = first_field_index, + } }, + } })).toValue(), + ); + } + + try sema.requireRuntimeBlock(block, operand_src, null); + return block.addStructFieldPtr(spa.operand_ptr, first_field_index, capture_ptr_ty); + } + + if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |operand_val| { + if (operand_val.isUndef(mod)) return sema.addConstUndef(capture_ty); + const union_val = mod.intern_pool.indexToKey(operand_val.toIntern()).un; + if (union_val.tag.toValue().isUndef(mod)) return sema.addConstUndef(capture_ty); + const active_field_idx = @intCast(u32, operand_ty.unionTagFieldIndex(union_val.tag.toValue(), sema.mod).?); + const field_ty = union_obj.fields.values()[active_field_idx].ty; + const uncoerced = try sema.addConstant(field_ty, union_val.val.toValue()); + return sema.coerce(block, capture_ty, uncoerced, operand_src); } + try sema.requireRuntimeBlock(block, operand_src, null); - return block.addStructFieldPtr(operand_ptr, first_field_index, field_ty_ptr); - } - if (try sema.resolveDefinedValue(block, operand_src, operand)) |operand_val| { - return sema.addConstant( - first_field.ty, - mod.intern_pool.indexToKey(operand_val.toIntern()).un.val.toValue(), - ); - } - try sema.requireRuntimeBlock(block, operand_src, null); - return block.addStructFieldVal(operand, first_field_index, first_field.ty); - }, - .ErrorSet => { - if (is_multi) { - var names: Module.Fn.InferredErrorSet.NameMap = .{}; - try names.ensureUnusedCapacity(sema.arena, items.len); - for (items) |item| { - const item_ref = try sema.resolveInst(item); - // Previous switch validation ensured this will succeed - const item_val = sema.resolveConstLazyValue(block, .unneeded, item_ref, "") catch unreachable; - names.putAssumeCapacityNoClobber(item_val.getErrorName(mod).unwrap().?, {}); + if (same_types) { + return block.addStructFieldVal(spa.operand, first_field_index, capture_ty); } - const else_error_ty = try mod.errorSetFromUnsortedNames(names.keys()); - return sema.bitCast(block, else_error_ty, operand, operand_src, null); - } else { - const item_ref = try sema.resolveInst(items[0]); - // Previous switch validation ensured this will succeed - const item_val = sema.resolveConstLazyValue(block, .unneeded, item_ref, "") catch unreachable; + // We may have to emit a switch block which coerces the operand to the capture type. + // If we can, try to avoid that using in-memory coercions. + const first_non_imc = in_mem: { + for (field_tys, 0..) |field_ty, i| { + if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, sema.mod.getTarget(), .unneeded, .unneeded)) { + break :in_mem i; + } + } + // 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); + return block.addBitCast(capture_ty, uncoerced); + }; - const item_ty = try mod.singleErrorSetType(item_val.getErrorName(mod).unwrap().?); - return sema.bitCast(block, item_ty, operand, operand_src, null); - } - }, - else => { - // In this case the capture value is just the passed-through value of the - // switch condition. - if (is_ref) { - return operand_ptr; - } else { - return operand; - } - }, - } -} + // By-val capture with heterogeneous types which are not all in-memory coercible to + // the resolved capture type. We finally have to fall back to the ugly method. -fn zirSwitchCaptureTag(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const mod = sema.mod; - const zir_datas = sema.code.instructions.items(.data); - const inst_data = zir_datas[inst].un_tok; - const src = inst_data.src(); + // However, let's first track which operands are in-memory coercible. There may well + // be several, and we can squash all of these cases into the same switch prong using + // a simple bitcast. We'll make this the 'else' prong. - const switch_tag = sema.code.instructions.items(.tag)[Zir.refToIndex(inst_data.operand).?]; - const is_ref = switch_tag == .switch_cond_ref; - const cond_data = zir_datas[Zir.refToIndex(inst_data.operand).?].un_node; - const operand_ptr = try sema.resolveInst(cond_data.operand); - const operand_ptr_ty = sema.typeOf(operand_ptr); - const operand_ty = if (is_ref) operand_ptr_ty.childType(mod) else operand_ptr_ty; + var in_mem_coercible = try std.DynamicBitSet.initFull(sema.arena, field_tys.len); + in_mem_coercible.unset(first_non_imc); + { + const next = first_non_imc + 1; + for (field_tys[next..], next..) |field_ty, i| { + if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, sema.mod.getTarget(), .unneeded, .unneeded)) { + in_mem_coercible.unset(i); + } + } + } - if (operand_ty.zigTypeTag(mod) != .Union) { - const msg = msg: { - const msg = try sema.errMsg(block, src, "cannot capture tag of non-union type '{}'", .{ - operand_ty.fmt(mod), - }); - errdefer msg.destroy(sema.gpa); - try sema.addDeclaredHereNote(msg, operand_ty); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(msg); - } + const capture_block_inst = try block.addInstAsIndex(.{ + .tag = .block, + .data = .{ + .ty_pl = .{ + .ty = try sema.addType(capture_ty), + .payload = undefined, // updated below + }, + }, + }); - return block.inline_case_capture; -} + const prong_count = field_tys.len - in_mem_coercible.count(); + + const estimated_extra = prong_count * 6; // 2 for Case, 1 item, probably 3 insts + var cases_extra = try std.ArrayList(u32).initCapacity(sema.gpa, estimated_extra); + defer cases_extra.deinit(); + + { + // Non-bitcast cases + var it = in_mem_coercible.iterator(.{ .kind = .unset }); + while (it.next()) |idx| { + var coerce_block = block.makeSubBlock(); + defer coerce_block.instructions.deinit(sema.gpa); + + const uncoerced = try coerce_block.addStructFieldVal(spa.operand, @intCast(u32, idx), field_tys[idx]); + const coerced = sema.coerce(&coerce_block, capture_ty, uncoerced, .unneeded) catch |err| switch (err) { + error.NeededSourceLocation => { + const multi_idx = raw_capture_src.multi_capture; + const src_decl_ptr = sema.mod.declPtr(block.src_decl); + const raw_case_src: Module.SwitchProngSrc = .{ .multi = .{ .prong = multi_idx, .item = @intCast(u32, idx) } }; + const case_src = raw_case_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); + _ = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src); + unreachable; + }, + else => |e| return e, + }; + _ = 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(u32, coerce_block.instructions.items.len)); // body_len + cases_extra.appendAssumeCapacity(@enumToInt(case_vals[idx])); // item + cases_extra.appendSliceAssumeCapacity(coerce_block.instructions.items); // body + } + } + const else_body_len = len: { + // 'else' prong uses a bitcast + var coerce_block = block.makeSubBlock(); + defer coerce_block.instructions.deinit(sema.gpa); + + const first_imc = in_mem_coercible.findFirstSet().?; + const uncoerced = try coerce_block.addStructFieldVal(spa.operand, @intCast(u32, first_imc), field_tys[first_imc]); + const coerced = try coerce_block.addBitCast(capture_ty, uncoerced); + _ = try coerce_block.addBr(capture_block_inst, coerced); + + try cases_extra.appendSlice(coerce_block.instructions.items); + break :len coerce_block.instructions.items.len; + }; + + try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).Struct.fields.len + + cases_extra.items.len + + @typeInfo(Air.Block).Struct.fields.len + + 1); + + const switch_br_inst = @intCast(u32, 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(u32, prong_count), + .else_body_len = @intCast(u32, else_body_len), + }), + } }, + }); + sema.air_extra.appendSliceAssumeCapacity(cases_extra.items); + + // Set up block body + sema.air_instructions.items(.data)[capture_block_inst].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{ + .body_len = 1, + }); + sema.air_extra.appendAssumeCapacity(switch_br_inst); + + return Air.indexToRef(capture_block_inst); + }, + .ErrorSet => { + if (capture_byref) { + const capture_src = raw_capture_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .none); + return sema.fail( + block, + capture_src, + "error set cannot be captured by reference", + .{}, + ); + } + + if (case_vals.len == 1) { + const item_val = sema.resolveConstValue(block, .unneeded, case_vals[0], "") catch unreachable; + const item_ty = try mod.singleErrorSetType(item_val.getErrorName(mod).unwrap().?); + return sema.bitCast(block, item_ty, spa.operand, operand_src, null); + } + + var names: Module.Fn.InferredErrorSet.NameMap = .{}; + try names.ensureUnusedCapacity(sema.arena, case_vals.len); + for (case_vals) |err| { + const err_val = sema.resolveConstValue(block, .unneeded, err, "") catch unreachable; + names.putAssumeCapacityNoClobber(err_val.getErrorName(mod).unwrap().?, {}); + } + const error_ty = try mod.errorSetFromUnsortedNames(names.keys()); + return sema.bitCast(block, error_ty, spa.operand, 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; + } else { + return spa.operand; + } + }, + } + } +}; -fn zirSwitchCond( +fn switchCond( sema: *Sema, block: *Block, - inst: Zir.Inst.Index, - is_ref: bool, + src: LazySrcLoc, + operand: Air.Inst.Ref, ) CompileError!Air.Inst.Ref { const mod = sema.mod; - const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const src = inst_data.src(); - const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node }; - const operand_ptr = try sema.resolveInst(inst_data.operand); - const operand = if (is_ref) - try sema.analyzeLoad(block, src, operand_ptr, operand_src) - else - operand_ptr; const operand_ty = sema.typeOf(operand); - switch (operand_ty.zigTypeTag(mod)) { .Type, .Void, @@ -10386,7 +10694,7 @@ fn zirSwitchCond( const SwitchErrorSet = std.AutoHashMap(InternPool.NullTerminatedString, Module.SwitchProngSrc); -fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_ref: bool) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -10400,10 +10708,21 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset }; const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index); - const operand = try sema.resolveInst(extra.data.operand); - // AstGen guarantees that the instruction immediately following - // switch_cond(_ref) is a dbg_stmt - const cond_dbg_node_index = Zir.refToIndex(extra.data.operand).? + 1; + const raw_operand: struct { val: Air.Inst.Ref, ptr: Air.Inst.Ref } = blk: { + 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 = val, .ptr = maybe_ptr }; + } else { + break :blk .{ .val = maybe_ptr, .ptr = undefined }; + } + }; + + const operand = try sema.switchCond(block, operand_src, raw_operand.val); + + // AstGen guarantees that the instruction immediately preceding + // switch_block(_ref) is a dbg_stmt + const cond_dbg_node_index = inst - 1; var header_extra_index: usize = extra.end; @@ -10414,28 +10733,50 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError break :blk multi_cases_len; } else 0; + const tag_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_has_tag_capture) blk: { + const tag_capture_inst = sema.code.extra[header_extra_index]; + header_extra_index += 1; + // SwitchProngAnalysis wants inst_map to have space for the tag capture. + // Note that the normal capture is referred to via the switch block + // index, which there is already necessarily space for. + try sema.inst_map.ensureSpaceForInstructions(gpa, &.{tag_capture_inst}); + break :blk tag_capture_inst; + } else undefined; + + var case_vals = try std.ArrayListUnmanaged(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len); + defer case_vals.deinit(gpa); + + const Special = struct { + body: []const Zir.Inst.Index, + end: usize, + capture: Zir.Inst.SwitchBlock.ProngInfo.Capture, + is_inline: bool, + has_tag_capture: bool, + }; + const special_prong = extra.data.bits.specialProng(); - const special: struct { body: []const Zir.Inst.Index, end: usize, is_inline: bool } = switch (special_prong) { - .none => .{ .body = &.{}, .end = header_extra_index, .is_inline = false }, + const special: Special = switch (special_prong) { + .none => .{ + .body = &.{}, + .end = header_extra_index, + .capture = .none, + .is_inline = false, + .has_tag_capture = false, + }, .under, .@"else" => blk: { - const body_len = @truncate(u31, sema.code.extra[header_extra_index]); + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[header_extra_index]); const extra_body_start = header_extra_index + 1; break :blk .{ - .body = sema.code.extra[extra_body_start..][0..body_len], - .end = extra_body_start + body_len, - .is_inline = sema.code.extra[header_extra_index] >> 31 != 0, + .body = sema.code.extra[extra_body_start..][0..info.body_len], + .end = extra_body_start + info.body_len, + .capture = info.capture, + .is_inline = info.is_inline, + .has_tag_capture = info.has_tag_capture, }; }, }; - const maybe_union_ty = blk: { - const zir_tags = sema.code.instructions.items(.tag); - const zir_data = sema.code.instructions.items(.data); - const cond_index = Zir.refToIndex(extra.data.operand).?; - const raw_operand = sema.resolveInst(zir_data[cond_index].un_node.operand) catch unreachable; - const target_ty = sema.typeOf(raw_operand); - break :blk if (zir_tags[cond_index] == .switch_cond_ref) target_ty.childType(mod) else target_ty; - }; + const maybe_union_ty = sema.typeOf(raw_operand.val); const union_originally = maybe_union_ty.zigTypeTag(mod) == .Union; // Duplicate checking variables later also used for `inline else`. @@ -10495,18 +10836,18 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); - extra_index += 1; - extra_index += body_len; + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); + extra_index += 1 + info.body_len; - try sema.validateSwitchItemEnum( + case_vals.appendAssumeCapacity(try sema.validateSwitchItemEnum( block, seen_enum_fields, &range_set, item_ref, + operand_ty, src_node_offset, .{ .scalar = scalar_i }, - ); + )); } } { @@ -10516,20 +10857,22 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); extra_index += 1; const items = sema.code.refSlice(extra_index, items_len); - extra_index += items_len + body_len; + extra_index += items_len + info.body_len; + try case_vals.ensureUnusedCapacity(gpa, items.len); for (items, 0..) |item_ref, item_i| { - try sema.validateSwitchItemEnum( + case_vals.appendAssumeCapacity(try sema.validateSwitchItemEnum( block, seen_enum_fields, &range_set, item_ref, + operand_ty, src_node_offset, .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } }, - ); + )); } try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset); @@ -10592,17 +10935,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); - extra_index += 1; - extra_index += body_len; + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); + extra_index += 1 + info.body_len; - try sema.validateSwitchItemError( + case_vals.appendAssumeCapacity(try sema.validateSwitchItemError( block, &seen_errors, item_ref, + operand_ty, src_node_offset, .{ .scalar = scalar_i }, - ); + )); } } { @@ -10612,19 +10955,21 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); extra_index += 1; const items = sema.code.refSlice(extra_index, items_len); - extra_index += items_len + body_len; + extra_index += items_len + info.body_len; + try case_vals.ensureUnusedCapacity(gpa, items.len); for (items, 0..) |item_ref, item_i| { - try sema.validateSwitchItemError( + case_vals.appendAssumeCapacity(try sema.validateSwitchItemError( block, &seen_errors, item_ref, + operand_ty, src_node_offset, .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } }, - ); + )); } try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset); @@ -10687,7 +11032,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError .dbg_block_end, .dbg_stmt, .dbg_var_val, - .switch_capture, .ret_type, .as_node, .ret_node, @@ -10732,17 +11076,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); - extra_index += 1; - extra_index += body_len; + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); + extra_index += 1 + info.body_len; - try sema.validateSwitchItem( + case_vals.appendAssumeCapacity(try sema.validateSwitchItemInt( block, &range_set, item_ref, + operand_ty, src_node_offset, .{ .scalar = scalar_i }, - ); + )); } } { @@ -10752,21 +11096,24 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); extra_index += 1; const items = sema.code.refSlice(extra_index, items_len); extra_index += items_len; + try case_vals.ensureUnusedCapacity(gpa, items.len); for (items, 0..) |item_ref, item_i| { - try sema.validateSwitchItem( + case_vals.appendAssumeCapacity(try sema.validateSwitchItemInt( block, &range_set, item_ref, + operand_ty, src_node_offset, .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } }, - ); + )); } + try case_vals.ensureUnusedCapacity(gpa, 2 * ranges_len); var range_i: u32 = 0; while (range_i < ranges_len) : (range_i += 1) { const item_first = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); @@ -10774,17 +11121,20 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const item_last = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - try sema.validateSwitchRange( + const vals = try sema.validateSwitchRange( block, &range_set, item_first, item_last, + operand_ty, src_node_offset, .{ .range = .{ .prong = multi_i, .item = range_i } }, ); + case_vals.appendAssumeCapacity(vals[0]); + case_vals.appendAssumeCapacity(vals[1]); } - extra_index += body_len; + extra_index += info.body_len; } } @@ -10821,18 +11171,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); - extra_index += 1; - extra_index += body_len; + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); + extra_index += 1 + info.body_len; - try sema.validateSwitchItemBool( + case_vals.appendAssumeCapacity(try sema.validateSwitchItemBool( block, &true_count, &false_count, item_ref, src_node_offset, .{ .scalar = scalar_i }, - ); + )); } } { @@ -10842,20 +11191,21 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); extra_index += 1; const items = sema.code.refSlice(extra_index, items_len); - extra_index += items_len + body_len; + extra_index += items_len + info.body_len; + try case_vals.ensureUnusedCapacity(gpa, items.len); for (items, 0..) |item_ref, item_i| { - try sema.validateSwitchItemBool( + case_vals.appendAssumeCapacity(try sema.validateSwitchItemBool( block, &true_count, &false_count, item_ref, src_node_offset, .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } }, - ); + )); } try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset); @@ -10903,17 +11253,18 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); extra_index += 1; - extra_index += body_len; + extra_index += info.body_len; - try sema.validateSwitchItemSparse( + case_vals.appendAssumeCapacity(try sema.validateSwitchItemSparse( block, &seen_values, item_ref, + operand_ty, src_node_offset, .{ .scalar = scalar_i }, - ); + )); } } { @@ -10923,19 +11274,21 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); extra_index += 1; const items = sema.code.refSlice(extra_index, items_len); - extra_index += items_len + body_len; + extra_index += items_len + info.body_len; + try case_vals.ensureUnusedCapacity(gpa, items.len); for (items, 0..) |item_ref, item_i| { - try sema.validateSwitchItemSparse( + case_vals.appendAssumeCapacity(try sema.validateSwitchItemSparse( block, &seen_values, item_ref, + operand_ty, src_node_offset, .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } }, - ); + )); } try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset); @@ -10961,6 +11314,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError }), } + const spa: SwitchProngAnalysis = .{ + .sema = sema, + .parent_block = block, + .operand = raw_operand.val, + .operand_ptr = raw_operand.ptr, + .cond = operand, + .else_error_ty = else_error_ty, + .switch_block_inst = inst, + .tag_capture_inst = tag_capture_inst, + }; + const block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len); try sema.air_instructions.append(gpa, .{ .tag = .block, @@ -10988,7 +11352,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError .is_comptime = block.is_comptime, .comptime_reason = block.comptime_reason, .is_typeof = block.is_typeof, - .switch_else_err_ty = else_error_ty, .c_import_buf = block.c_import_buf, .runtime_cond = block.runtime_cond, .runtime_loop = block.runtime_loop, @@ -11005,79 +11368,110 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError { var scalar_i: usize = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { - const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); - const is_inline = sema.code.extra[extra_index] >> 31 != 0; + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); extra_index += 1; - const body = sema.code.extra[extra_index..][0..body_len]; - extra_index += body_len; - - const item = try sema.resolveInst(item_ref); - // Validation above ensured these will succeed. - const item_val = sema.resolveConstLazyValue(&child_block, .unneeded, item, "") catch unreachable; - if (resolved_operand_val.eql(item_val, operand_ty, mod)) { - if (is_inline) child_block.inline_case_capture = operand; + const body = sema.code.extra[extra_index..][0..info.body_len]; + extra_index += info.body_len; + const item = case_vals.items[scalar_i]; + const item_val = sema.resolveConstValue(&child_block, .unneeded, item, "") catch unreachable; + if (operand_val.eql(item_val, operand_ty, sema.mod)) { if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand); - return sema.resolveBlockBody(block, src, &child_block, body, inst, merges); + return spa.resolveProngComptime( + &child_block, + .normal, + body, + info.capture, + .{ .scalar_capture = @intCast(u32, scalar_i) }, + &.{item}, + if (info.is_inline) operand else .none, + info.has_tag_capture, + merges, + ); } } } { var multi_i: usize = 0; + var case_val_idx: usize = scalar_cases_len; while (multi_i < multi_cases_len) : (multi_i += 1) { const items_len = sema.code.extra[extra_index]; extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); - const is_inline = sema.code.extra[extra_index] >> 31 != 0; - extra_index += 1; - const items = sema.code.refSlice(extra_index, items_len); - extra_index += items_len; - const body = sema.code.extra[extra_index + 2 * ranges_len ..][0..body_len]; + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); + extra_index += 1 + items_len; + const body = sema.code.extra[extra_index + 2 * ranges_len ..][0..info.body_len]; - for (items) |item_ref| { - const item = try sema.resolveInst(item_ref); - // Validation above ensured these will succeed. - const item_val = sema.resolveConstLazyValue(&child_block, .unneeded, item, "") catch unreachable; - if (resolved_operand_val.eql(item_val, operand_ty, mod)) { - if (is_inline) child_block.inline_case_capture = operand; + const items = case_vals.items[case_val_idx..][0..items_len]; + case_val_idx += items_len; + for (items) |item| { + // Validation above ensured these will succeed. + const item_val = sema.resolveConstValue(&child_block, .unneeded, item, "") catch unreachable; + if (operand_val.eql(item_val, operand_ty, sema.mod)) { if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand); - return sema.resolveBlockBody(block, src, &child_block, body, inst, merges); + return spa.resolveProngComptime( + &child_block, + .normal, + body, + info.capture, + .{ .multi_capture = @intCast(u32, multi_i) }, + items, + if (info.is_inline) operand else .none, + info.has_tag_capture, + merges, + ); } } var range_i: usize = 0; while (range_i < ranges_len) : (range_i += 1) { - const item_first = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); - extra_index += 1; - const item_last = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); - extra_index += 1; + const range_items = case_vals.items[case_val_idx..][0..2]; + extra_index += 2; + case_val_idx += 2; // Validation above ensured these will succeed. - const first_tv = sema.resolveInstConst(&child_block, .unneeded, item_first, "") catch unreachable; - const last_tv = sema.resolveInstConst(&child_block, .unneeded, item_last, "") catch unreachable; - if ((try sema.compareAll(resolved_operand_val, .gte, first_tv.val, operand_ty)) and - (try sema.compareAll(resolved_operand_val, .lte, last_tv.val, operand_ty))) + const first_val = sema.resolveConstValue(&child_block, .unneeded, range_items[0], "") catch unreachable; + const last_val = sema.resolveConstValue(&child_block, .unneeded, range_items[1], "") catch unreachable; + if ((try sema.compareAll(resolved_operand_val, .gte, first_val, operand_ty)) and + (try sema.compareAll(resolved_operand_val, .lte, last_val, operand_ty))) { - if (is_inline) child_block.inline_case_capture = operand; if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand); - return sema.resolveBlockBody(block, src, &child_block, body, inst, merges); + return spa.resolveProngComptime( + &child_block, + .normal, + body, + info.capture, + .{ .multi_capture = @intCast(u32, multi_i) }, + undefined, // case_vals may be undefined for ranges + if (info.is_inline) operand else .none, + info.has_tag_capture, + merges, + ); } } - extra_index += body_len; + extra_index += info.body_len; } } if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, special.body, operand); - if (special.is_inline) child_block.inline_case_capture = operand; if (empty_enum) { return Air.Inst.Ref.void_value; } - return sema.resolveBlockBody(block, src, &child_block, special.body, inst, merges); + + return spa.resolveProngComptime( + &child_block, + .special, + special.body, + special.capture, + .special_capture, + undefined, // case_vals may be undefined for special prongs + if (special.is_inline) operand else .none, + special.has_tag_capture, + merges, + ); } if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline) { @@ -11097,7 +11491,18 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const ok = try block.addUnOp(.is_named_enum_value, operand); try sema.addSafetyCheck(block, ok, .corrupt_switch); } - return sema.resolveBlockBody(block, src, &child_block, special.body, inst, merges); + + return spa.resolveProngComptime( + &child_block, + .special, + special.body, + special.capture, + .special_capture, + undefined, // case_vals may be undefined for special prongs + .none, + false, + merges, + ); } if (child_block.is_comptime) { @@ -11123,23 +11528,19 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError var scalar_i: usize = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { - const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); - const is_inline = sema.code.extra[extra_index] >> 31 != 0; + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); extra_index += 1; - const body = sema.code.extra[extra_index..][0..body_len]; - extra_index += body_len; + const body = sema.code.extra[extra_index..][0..info.body_len]; + extra_index += info.body_len; var wip_captures = try WipCaptureScope.init(gpa, child_block.wip_capture_scope); defer wip_captures.deinit(); case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = wip_captures.scope; - case_block.inline_case_capture = .none; - const item = try sema.resolveInst(item_ref); - if (is_inline) case_block.inline_case_capture = item; + const item = case_vals.items[scalar_i]; // `item` is already guaranteed to be constant known. const analyze_body = if (union_originally) blk: { @@ -11151,7 +11552,16 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) { // nothing to do here } else if (analyze_body) { - try sema.analyzeBodyRuntimeBreak(&case_block, body); + try spa.analyzeProngRuntime( + &case_block, + .normal, + body, + info.capture, + .{ .scalar_capture = @intCast(u32, scalar_i) }, + &.{item}, + if (info.is_inline) item else .none, + info.has_tag_capture, + ); } else { _ = try case_block.addNoOp(.unreach); } @@ -11173,38 +11583,38 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError 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; while (multi_i < multi_cases_len) : (multi_i += 1) { const items_len = sema.code.extra[extra_index]; extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = @truncate(u31, sema.code.extra[extra_index]); - const is_inline = sema.code.extra[extra_index] >> 31 != 0; - extra_index += 1; - const items = sema.code.refSlice(extra_index, items_len); - extra_index += items_len; + const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, sema.code.extra[extra_index]); + extra_index += 1 + items_len; + + const items = case_vals.items[case_val_idx..][0..items_len]; + case_val_idx += items_len; case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = child_block.wip_capture_scope; - case_block.inline_case_capture = .none; // Generate all possible cases as scalar prongs. - if (is_inline) { + if (info.is_inline) { const body_start = extra_index + 2 * ranges_len; - const body = sema.code.extra[body_start..][0..body_len]; + const body = sema.code.extra[body_start..][0..info.body_len]; var emit_bb = false; var range_i: u32 = 0; while (range_i < ranges_len) : (range_i += 1) { - const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); - extra_index += 1; - const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); - extra_index += 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]; - const item_first_ref = try sema.resolveInst(first_ref); var item = sema.resolveConstValue(block, .unneeded, item_first_ref, undefined) catch unreachable; - const item_last_ref = try sema.resolveInst(last_ref); const item_last = sema.resolveConstValue(block, .unneeded, item_last_ref, undefined) catch unreachable; while (item.compareScalar(.lte, item_last, operand_ty, mod)) : ({ @@ -11217,7 +11627,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError cases_len += 1; const item_ref = try sema.addConstant(operand_ty, item); - case_block.inline_case_capture = item_ref; case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = child_block.wip_capture_scope; @@ -11233,22 +11642,28 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError }; emit_bb = true; - try sema.analyzeBodyRuntimeBreak(&case_block, body); + try spa.analyzeProngRuntime( + &case_block, + .normal, + body, + info.capture, + .{ .multi_capture = multi_i }, + undefined, // case_vals may be undefined for ranges + item_ref, + info.has_tag_capture, + ); try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); cases_extra.appendAssumeCapacity(1); // items_len cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); - cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendAssumeCapacity(@enumToInt(item_ref)); cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); } } - for (items, 0..) |item_ref, item_i| { + for (items, 0..) |item, item_i| { cases_len += 1; - const item = try sema.resolveInst(item_ref); - case_block.inline_case_capture = item; - case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = child_block.wip_capture_scope; @@ -11270,7 +11685,16 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError emit_bb = true; if (analyze_body) { - try sema.analyzeBodyRuntimeBreak(&case_block, body); + try spa.analyzeProngRuntime( + &case_block, + .normal, + body, + info.capture, + .{ .multi_capture = multi_i }, + &.{item}, + item, + info.has_tag_capture, + ); } else { _ = try case_block.addNoOp(.unreach); } @@ -11278,11 +11702,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); cases_extra.appendAssumeCapacity(1); // items_len cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); - cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendAssumeCapacity(@enumToInt(item)); cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); } - extra_index += body_len; + extra_index += info.body_len; continue; } @@ -11295,8 +11719,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError cases_len += 1; const analyze_body = if (union_originally) - for (items) |item_ref| { - const item = try sema.resolveInst(item_ref); + for (items) |item| { const item_val = sema.resolveConstValue(block, .unneeded, item, "") catch unreachable; const field_ty = maybe_union_ty.unionFieldType(item_val, mod); if (field_ty.zigTypeTag(mod) != .NoReturn) break true; @@ -11304,12 +11727,21 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError else true; - const body = sema.code.extra[extra_index..][0..body_len]; - extra_index += body_len; + const body = sema.code.extra[extra_index..][0..info.body_len]; + extra_index += info.body_len; if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) { // nothing to do here } else if (analyze_body) { - try sema.analyzeBodyRuntimeBreak(&case_block, body); + try spa.analyzeProngRuntime( + &case_block, + .normal, + body, + info.capture, + .{ .multi_capture = multi_i }, + items, + .none, + false, + ); } else { _ = try case_block.addNoOp(.unreach); } @@ -11320,15 +11752,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError cases_extra.appendAssumeCapacity(@intCast(u32, items.len)); cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); - for (items) |item_ref| { - const item = try sema.resolveInst(item_ref); + for (items) |item| { cases_extra.appendAssumeCapacity(@enumToInt(item)); } cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); } else { - for (items) |item_ref| { - const item = try sema.resolveInst(item_ref); + 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); @@ -11339,13 +11769,12 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError var range_i: usize = 0; while (range_i < ranges_len) : (range_i += 1) { - const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); - extra_index += 1; - const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); - extra_index += 1; + const range_items = case_vals.items[case_val_idx..][0..2]; + extra_index += 2; + case_val_idx += 2; - const item_first = try sema.resolveInst(first_ref); - const item_last = try sema.resolveInst(last_ref); + 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( @@ -11385,12 +11814,21 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = wip_captures.scope; - const body = sema.code.extra[extra_index..][0..body_len]; - extra_index += body_len; + const body = sema.code.extra[extra_index..][0..info.body_len]; + extra_index += info.body_len; if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) { // nothing to do here } else { - try sema.analyzeBodyRuntimeBreak(&case_block, body); + try spa.analyzeProngRuntime( + &case_block, + .normal, + body, + info.capture, + .{ .multi_capture = multi_i }, + items, + .none, + false, + ); } try wip_captures.finalize(); @@ -11435,7 +11873,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const item_val = try mod.enumValueFieldIndex(operand_ty, @intCast(u32, i)); const item_ref = try sema.addConstant(operand_ty, item_val); - case_block.inline_case_capture = item_ref; case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = child_block.wip_capture_scope; @@ -11449,7 +11886,16 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError emit_bb = true; if (analyze_body) { - try sema.analyzeBodyRuntimeBreak(&case_block, special.body); + try spa.analyzeProngRuntime( + &case_block, + .special, + special.body, + special.capture, + .special_capture, + &.{item_ref}, + item_ref, + special.has_tag_capture, + ); } else { _ = try case_block.addNoOp(.unreach); } @@ -11457,7 +11903,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); cases_extra.appendAssumeCapacity(1); // items_len cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); - cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendAssumeCapacity(@enumToInt(item_ref)); cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); } }, @@ -11477,7 +11923,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError .name = error_name, } }); const item_ref = try sema.addConstant(operand_ty, item_val.toValue()); - case_block.inline_case_capture = item_ref; case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = child_block.wip_capture_scope; @@ -11485,12 +11930,21 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); emit_bb = true; - try sema.analyzeBodyRuntimeBreak(&case_block, special.body); + try spa.analyzeProngRuntime( + &case_block, + .special, + special.body, + special.capture, + .special_capture, + &.{item_ref}, + item_ref, + special.has_tag_capture, + ); try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); cases_extra.appendAssumeCapacity(1); // items_len cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); - cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendAssumeCapacity(@enumToInt(item_ref)); cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); } }, @@ -11500,7 +11954,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError cases_len += 1; const item_ref = try sema.addConstant(operand_ty, cur.toValue()); - case_block.inline_case_capture = item_ref; case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = child_block.wip_capture_scope; @@ -11508,19 +11961,27 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); emit_bb = true; - try sema.analyzeBodyRuntimeBreak(&case_block, special.body); + try spa.analyzeProngRuntime( + &case_block, + .special, + special.body, + special.capture, + .special_capture, + &.{item_ref}, + item_ref, + special.has_tag_capture, + ); try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); cases_extra.appendAssumeCapacity(1); // items_len cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); - cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendAssumeCapacity(@enumToInt(item_ref)); cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); } }, .Bool => { if (true_count == 0) { cases_len += 1; - case_block.inline_case_capture = Air.Inst.Ref.bool_true; case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = child_block.wip_capture_scope; @@ -11528,17 +11989,25 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); emit_bb = true; - try sema.analyzeBodyRuntimeBreak(&case_block, special.body); + try spa.analyzeProngRuntime( + &case_block, + .special, + special.body, + special.capture, + .special_capture, + &.{Air.Inst.Ref.bool_true}, + Air.Inst.Ref.bool_true, + special.has_tag_capture, + ); try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); cases_extra.appendAssumeCapacity(1); // items_len cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); - cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendAssumeCapacity(@enumToInt(Air.Inst.Ref.bool_true)); cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); } if (false_count == 0) { cases_len += 1; - case_block.inline_case_capture = Air.Inst.Ref.bool_false; case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = child_block.wip_capture_scope; @@ -11546,12 +12015,21 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); emit_bb = true; - try sema.analyzeBodyRuntimeBreak(&case_block, special.body); + try spa.analyzeProngRuntime( + &case_block, + .special, + special.body, + special.capture, + .special_capture, + &.{Air.Inst.Ref.bool_false}, + Air.Inst.Ref.bool_false, + special.has_tag_capture, + ); try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); cases_extra.appendAssumeCapacity(1); // items_len cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); - cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendAssumeCapacity(@enumToInt(Air.Inst.Ref.bool_false)); cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); } }, @@ -11565,7 +12043,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = wip_captures.scope; - case_block.inline_case_capture = .none; if (mod.backendSupportsFeature(.is_named_enum_value) and special.body.len != 0 and block.wantSafety() and operand_ty.zigTypeTag(mod) == .Enum and (!operand_ty.isNonexhaustiveEnum(mod) or union_originally)) @@ -11589,7 +12066,16 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError { // nothing to do here } else if (special.body.len != 0 and analyze_body and !special.is_inline) { - try sema.analyzeBodyRuntimeBreak(&case_block, special.body); + try spa.analyzeProngRuntime( + &case_block, + .special, + special.body, + special.capture, + .special_capture, + undefined, // case_vals may be undefined for special prongs + .none, + false, + ); } else { // We still need a terminator in this block, but we have proven // that it is unreachable. @@ -11704,29 +12190,51 @@ const RangeSetUnhandledIterator = struct { } }; +const ResolvedSwitchItem = struct { + ref: Air.Inst.Ref, + val: InternPool.Index, +}; fn resolveSwitchItemVal( sema: *Sema, block: *Block, item_ref: Zir.Inst.Ref, + /// Coerce `item_ref` to this type. + coerce_ty: Type, switch_node_offset: i32, switch_prong_src: Module.SwitchProngSrc, range_expand: Module.SwitchProngSrc.RangeExpand, -) CompileError!InternPool.Index { +) CompileError!ResolvedSwitchItem { const mod = sema.mod; - const item = try sema.resolveInst(item_ref); + const uncoerced_item = try sema.resolveInst(item_ref); + // Constructing a LazySrcLoc is costly because we only have the switch AST node. // Only if we know for sure we need to report a compile error do we resolve the // full source locations. - if (sema.resolveConstLazyValue(block, .unneeded, item, "")) |val| { - return val.toIntern(); - } else |err| switch (err) { + + const item = sema.coerce(block, coerce_ty, uncoerced_item, .unneeded) catch |err| switch (err) { + error.NeededSourceLocation => { + const src = switch_prong_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, range_expand); + _ = try sema.coerce(block, coerce_ty, uncoerced_item, src); + unreachable; + }, + else => |e| return e, + }; + + const maybe_lazy = sema.resolveConstValue(block, .unneeded, item, "") catch |err| switch (err) { error.NeededSourceLocation => { const src = switch_prong_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, range_expand); _ = try sema.resolveConstValue(block, src, item, "switch prong values must be comptime-known"); unreachable; }, else => |e| return e, - } + }; + + const val = try sema.resolveLazyValue(maybe_lazy); + const new_item = if (val.toIntern() != maybe_lazy.toIntern()) blk: { + break :blk try sema.addConstant(coerce_ty, val); + } else item; + + return .{ .ref = new_item, .val = val.toIntern() }; } fn validateSwitchRange( @@ -11735,31 +12243,35 @@ fn validateSwitchRange( range_set: *RangeSet, first_ref: Zir.Inst.Ref, last_ref: Zir.Inst.Ref, + operand_ty: Type, src_node_offset: i32, switch_prong_src: Module.SwitchProngSrc, -) CompileError!void { +) CompileError![2]Air.Inst.Ref { const mod = sema.mod; - const first = try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first); - const last = try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last); - if (first.toValue().compareScalar(.gt, last.toValue(), mod.intern_pool.typeOf(first).toType(), mod)) { + const first = try sema.resolveSwitchItemVal(block, first_ref, operand_ty, src_node_offset, switch_prong_src, .first); + const last = try sema.resolveSwitchItemVal(block, last_ref, operand_ty, src_node_offset, switch_prong_src, .last); + if (try first.val.toValue().compareAll(.gt, last.val.toValue(), operand_ty, mod)) { const src = switch_prong_src.resolve(mod, mod.declPtr(block.src_decl), src_node_offset, .first); return sema.fail(block, src, "range start value is greater than the end value", .{}); } - const maybe_prev_src = try range_set.add(first, last, switch_prong_src); - return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); + const maybe_prev_src = try range_set.add(first.val, last.val, switch_prong_src); + try sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); + return .{ first.ref, last.ref }; } -fn validateSwitchItem( +fn validateSwitchItemInt( sema: *Sema, block: *Block, range_set: *RangeSet, item_ref: Zir.Inst.Ref, + operand_ty: Type, src_node_offset: i32, switch_prong_src: Module.SwitchProngSrc, -) CompileError!void { - const item = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none); - const maybe_prev_src = try range_set.add(item, item, switch_prong_src); - return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); +) CompileError!Air.Inst.Ref { + const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, src_node_offset, switch_prong_src, .none); + const maybe_prev_src = try range_set.add(item.val, item.val, switch_prong_src); + try sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); + return item.ref; } fn validateSwitchItemEnum( @@ -11768,19 +12280,22 @@ fn validateSwitchItemEnum( seen_fields: []?Module.SwitchProngSrc, range_set: *RangeSet, item_ref: Zir.Inst.Ref, + operand_ty: Type, src_node_offset: i32, switch_prong_src: Module.SwitchProngSrc, -) CompileError!void { +) CompileError!Air.Inst.Ref { const ip = &sema.mod.intern_pool; - const item = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none); - const int = ip.indexToKey(item).enum_tag.int; - const field_index = ip.indexToKey(ip.typeOf(item)).enum_type.tagValueIndex(ip, int) orelse { + const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, src_node_offset, switch_prong_src, .none); + const int = ip.indexToKey(item.val).enum_tag.int; + const field_index = ip.indexToKey(ip.typeOf(item.val)).enum_type.tagValueIndex(ip, int) orelse { const maybe_prev_src = try range_set.add(int, int, switch_prong_src); - return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); + try sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); + return item.ref; }; const maybe_prev_src = seen_fields[field_index]; seen_fields[field_index] = switch_prong_src; - return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); + try sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); + return item.ref; } fn validateSwitchItemError( @@ -11788,18 +12303,19 @@ fn validateSwitchItemError( block: *Block, seen_errors: *SwitchErrorSet, item_ref: Zir.Inst.Ref, + operand_ty: Type, src_node_offset: i32, switch_prong_src: Module.SwitchProngSrc, -) CompileError!void { +) CompileError!Air.Inst.Ref { const ip = &sema.mod.intern_pool; - const item = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none); - // TODO: Do i need to typecheck here? - const error_name = ip.indexToKey(item).err.name; + const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, src_node_offset, switch_prong_src, .none); + const error_name = ip.indexToKey(item.val).err.name; const maybe_prev_src = if (try seen_errors.fetchPut(error_name, switch_prong_src)) |prev| prev.value else null; - return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); + try sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); + return item.ref; } fn validateSwitchDupe( @@ -11842,19 +12358,20 @@ fn validateSwitchItemBool( item_ref: Zir.Inst.Ref, src_node_offset: i32, switch_prong_src: Module.SwitchProngSrc, -) CompileError!void { +) CompileError!Air.Inst.Ref { const mod = sema.mod; - const item = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none); - if (item.toValue().toBool()) { + const item = try sema.resolveSwitchItemVal(block, item_ref, Type.bool, src_node_offset, switch_prong_src, .none); + if (item.val.toValue().toBool()) { true_count.* += 1; } else { false_count.* += 1; } - if (true_count.* + false_count.* > 2) { - const block_src_decl = mod.declPtr(block.src_decl); + if (true_count.* > 1 or false_count.* > 1) { + const block_src_decl = sema.mod.declPtr(block.src_decl); const src = switch_prong_src.resolve(mod, block_src_decl, src_node_offset, .none); return sema.fail(block, src, "duplicate switch value", .{}); } + return item.ref; } const ValueSrcMap = std.AutoHashMapUnmanaged(InternPool.Index, Module.SwitchProngSrc); @@ -11864,12 +12381,14 @@ fn validateSwitchItemSparse( block: *Block, seen_values: *ValueSrcMap, item_ref: Zir.Inst.Ref, + operand_ty: Type, src_node_offset: i32, switch_prong_src: Module.SwitchProngSrc, -) CompileError!void { - const item = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none); - const kv = (try seen_values.fetchPut(sema.gpa, item, switch_prong_src)) orelse return; - return sema.validateSwitchDupe(block, kv.value, switch_prong_src, src_node_offset); +) CompileError!Air.Inst.Ref { + const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, src_node_offset, switch_prong_src, .none); + const kv = (try seen_values.fetchPut(sema.gpa, item.val, switch_prong_src)) orelse return item.ref; + try sema.validateSwitchDupe(block, kv.value, switch_prong_src, src_node_offset); + unreachable; } fn validateSwitchNoRange( |
