authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-04-28 21:44:57+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-09-01 18:30:31+01:00
log5e12ca9fe3c77ce1d2a3ea1c22c4bcb6d9b2bb0c
treea4badc5eab3da4901e1c0c3f3239b07628fc339f
parent5fb4a7df38deb705f77088d7788f0acc09da613d
signature Commit is signed but in an unrecognized format.

compiler: implement labeled switch/continue


22 files changed, 1602 insertions(+), 382 deletions(-)

lib/std/zig/Ast.zig+45-9
...@@ -1184,14 +1184,7 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex {...@@ -1184,14 +1184,7 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex {
1184 n = extra.sentinel;1184 n = extra.sentinel;
1185 },1185 },
11861186
1187 .@"continue" => {1187 .@"continue", .@"break" => {
1188 if (datas[n].lhs != 0) {
1189 return datas[n].lhs + end_offset;
1190 } else {
1191 return main_tokens[n] + end_offset;
1192 }
1193 },
1194 .@"break" => {
1195 if (datas[n].rhs != 0) {1188 if (datas[n].rhs != 0) {
1196 n = datas[n].rhs;1189 n = datas[n].rhs;
1197 } else if (datas[n].lhs != 0) {1190 } else if (datas[n].lhs != 0) {
...@@ -1895,6 +1888,15 @@ pub fn taggedUnionEnumTag(tree: Ast, node: Node.Index) full.ContainerDecl {...@@ -1895,6 +1888,15 @@ pub fn taggedUnionEnumTag(tree: Ast, node: Node.Index) full.ContainerDecl {
1895 });1888 });
1896}1889}
18971890
1891pub fn switchFull(tree: Ast, node: Node.Index) full.Switch {
1892 const data = &tree.nodes.items(.data)[node];
1893 return tree.fullSwitchComponents(.{
1894 .switch_token = tree.nodes.items(.main_token)[node],
1895 .condition = data.lhs,
1896 .sub_range = data.rhs,
1897 });
1898}
1899
1898pub fn switchCaseOne(tree: Ast, node: Node.Index) full.SwitchCase {1900pub fn switchCaseOne(tree: Ast, node: Node.Index) full.SwitchCase {
1899 const data = &tree.nodes.items(.data)[node];1901 const data = &tree.nodes.items(.data)[node];
1900 const values: *[1]Node.Index = &data.lhs;1902 const values: *[1]Node.Index = &data.lhs;
...@@ -2206,6 +2208,21 @@ fn fullContainerDeclComponents(tree: Ast, info: full.ContainerDecl.Components) f...@@ -2206,6 +2208,21 @@ fn fullContainerDeclComponents(tree: Ast, info: full.ContainerDecl.Components) f
2206 return result;2208 return result;
2207}2209}
22082210
2211fn fullSwitchComponents(tree: Ast, info: full.Switch.Components) full.Switch {
2212 const token_tags = tree.tokens.items(.tag);
2213 const tok_i = info.switch_token -| 1;
2214 var result: full.Switch = .{
2215 .ast = info,
2216 .label_token = null,
2217 };
2218 if (token_tags[tok_i] == .colon and
2219 token_tags[tok_i -| 1] == .identifier)
2220 {
2221 result.label_token = tok_i - 1;
2222 }
2223 return result;
2224}
2225
2209fn fullSwitchCaseComponents(tree: Ast, info: full.SwitchCase.Components, node: Node.Index) full.SwitchCase {2226fn fullSwitchCaseComponents(tree: Ast, info: full.SwitchCase.Components, node: Node.Index) full.SwitchCase {
2210 const token_tags = tree.tokens.items(.tag);2227 const token_tags = tree.tokens.items(.tag);
2211 const node_tags = tree.nodes.items(.tag);2228 const node_tags = tree.nodes.items(.tag);
...@@ -2477,6 +2494,13 @@ pub fn fullContainerDecl(tree: Ast, buffer: *[2]Ast.Node.Index, node: Node.Index...@@ -2477,6 +2494,13 @@ pub fn fullContainerDecl(tree: Ast, buffer: *[2]Ast.Node.Index, node: Node.Index
2477 };2494 };
2478}2495}
24792496
2497pub fn fullSwitch(tree: Ast, node: Node.Index) ?full.Switch {
2498 return switch (tree.nodes.items(.tag)[node]) {
2499 .@"switch", .switch_comma => tree.switchFull(node),
2500 else => null,
2501 };
2502}
2503
2480pub fn fullSwitchCase(tree: Ast, node: Node.Index) ?full.SwitchCase {2504pub fn fullSwitchCase(tree: Ast, node: Node.Index) ?full.SwitchCase {
2481 return switch (tree.nodes.items(.tag)[node]) {2505 return switch (tree.nodes.items(.tag)[node]) {
2482 .switch_case_one, .switch_case_inline_one => tree.switchCaseOne(node),2506 .switch_case_one, .switch_case_inline_one => tree.switchCaseOne(node),
...@@ -2829,6 +2853,17 @@ pub const full = struct {...@@ -2829,6 +2853,17 @@ pub const full = struct {
2829 };2853 };
2830 };2854 };
28312855
2856 pub const Switch = struct {
2857 ast: Components,
2858 label_token: ?TokenIndex,
2859
2860 pub const Components = struct {
2861 switch_token: TokenIndex,
2862 condition: Node.Index,
2863 sub_range: Node.Index,
2864 };
2865 };
2866
2832 pub const SwitchCase = struct {2867 pub const SwitchCase = struct {
2833 inline_token: ?TokenIndex,2868 inline_token: ?TokenIndex,
2834 /// Points to the first token after the `|`. Will either be an identifier or2869 /// Points to the first token after the `|`. Will either be an identifier or
...@@ -3287,7 +3322,8 @@ pub const Node = struct {...@@ -3287,7 +3322,8 @@ pub const Node = struct {
3287 @"suspend",3322 @"suspend",
3288 /// `resume lhs`. rhs is unused.3323 /// `resume lhs`. rhs is unused.
3289 @"resume",3324 @"resume",
3290 /// `continue`. lhs is token index of label if any. rhs is unused.3325 /// `continue :lhs rhs`
3326 /// both lhs and rhs may be omitted.
3291 @"continue",3327 @"continue",
3292 /// `break :lhs rhs`3328 /// `break :lhs rhs`
3293 /// both lhs and rhs may be omitted.3329 /// both lhs and rhs may be omitted.
lib/std/zig/AstGen.zig+97-16
...@@ -1144,7 +1144,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE...@@ -1144,7 +1144,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
1144 .error_set_decl => return errorSetDecl(gz, ri, node),1144 .error_set_decl => return errorSetDecl(gz, ri, node),
1145 .array_access => return arrayAccess(gz, scope, ri, node),1145 .array_access => return arrayAccess(gz, scope, ri, node),
1146 .@"comptime" => return comptimeExprAst(gz, scope, ri, node),1146 .@"comptime" => return comptimeExprAst(gz, scope, ri, node),
1147 .@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node),1147 .@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node, tree.fullSwitch(node).?),
11481148
1149 .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node),1149 .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node),
1150 .@"suspend" => return suspendExpr(gz, scope, node),1150 .@"suspend" => return suspendExpr(gz, scope, node),
...@@ -2160,6 +2160,11 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn...@@ -2160,6 +2160,11 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
2160 if (break_label != 0) {2160 if (break_label != 0) {
2161 if (block_gz.label) |*label| {2161 if (block_gz.label) |*label| {
2162 if (try astgen.tokenIdentEql(label.token, break_label)) {2162 if (try astgen.tokenIdentEql(label.token, break_label)) {
2163 const maybe_switch_tag = astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)];
2164 switch (maybe_switch_tag) {
2165 .switch_block, .switch_block_ref => return astgen.failNode(node, "cannot break from switch", .{}),
2166 else => {},
2167 }
2163 label.used = true;2168 label.used = true;
2164 break :blk label.block_inst;2169 break :blk label.block_inst;
2165 }2170 }
...@@ -2234,6 +2239,11 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)...@@ -2234,6 +2239,11 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
2234 const tree = astgen.tree;2239 const tree = astgen.tree;
2235 const node_datas = tree.nodes.items(.data);2240 const node_datas = tree.nodes.items(.data);
2236 const break_label = node_datas[node].lhs;2241 const break_label = node_datas[node].lhs;
2242 const rhs = node_datas[node].rhs;
2243
2244 if (break_label == 0 and rhs != 0) {
2245 return astgen.failNode(node, "cannot continue with operand without label", .{});
2246 }
22372247
2238 // Look for the label in the scope.2248 // Look for the label in the scope.
2239 var scope = parent_scope;2249 var scope = parent_scope;
...@@ -2258,6 +2268,15 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)...@@ -2258,6 +2268,15 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
2258 if (break_label != 0) blk: {2268 if (break_label != 0) blk: {
2259 if (gen_zir.label) |*label| {2269 if (gen_zir.label) |*label| {
2260 if (try astgen.tokenIdentEql(label.token, break_label)) {2270 if (try astgen.tokenIdentEql(label.token, break_label)) {
2271 const maybe_switch_tag = astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)];
2272 if (rhs != 0) switch (maybe_switch_tag) {
2273 .switch_block, .switch_block_ref => {},
2274 else => return astgen.failNode(node, "cannot continue loop with operand", .{}),
2275 } else switch (maybe_switch_tag) {
2276 .switch_block, .switch_block_ref => return astgen.failNode(node, "cannot continue switch without operand", .{}),
2277 else => {},
2278 }
2279
2261 label.used = true;2280 label.used = true;
2262 break :blk;2281 break :blk;
2263 }2282 }
...@@ -2265,8 +2284,35 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)...@@ -2265,8 +2284,35 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
2265 // found continue but either it has a different label, or no label2284 // found continue but either it has a different label, or no label
2266 scope = gen_zir.parent;2285 scope = gen_zir.parent;
2267 continue;2286 continue;
2287 } else if (gen_zir.label) |label| {
2288 // This `continue` is unlabeled. If the gz we've found corresponds to a labeled
2289 // `switch`, ignore it and continue to parent scopes.
2290 switch (astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)]) {
2291 .switch_block, .switch_block_ref => {
2292 scope = gen_zir.parent;
2293 continue;
2294 },
2295 else => {},
2296 }
2297 }
2298
2299 if (rhs != 0) {
2300 // We need to figure out the result info to use.
2301 // The type should match
2302 const operand = try reachableExpr(parent_gz, parent_scope, gen_zir.continue_result_info, rhs, node);
2303
2304 try genDefers(parent_gz, scope, parent_scope, .normal_only);
2305
2306 // As our last action before the continue, "pop" the error trace if needed
2307 if (!gen_zir.is_comptime)
2308 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = continue_block }, .always, node);
2309
2310 _ = try parent_gz.addBreakWithSrcNode(.switch_continue, continue_block, operand, rhs);
2311 return Zir.Inst.Ref.unreachable_value;
2268 }2312 }
22692313
2314 try genDefers(parent_gz, scope, parent_scope, .normal_only);
2315
2270 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)2316 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)
2271 .break_inline2317 .break_inline
2272 else2318 else
...@@ -2284,12 +2330,7 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)...@@ -2284,12 +2330,7 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
2284 },2330 },
2285 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,2331 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
2286 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,2332 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
2287 .defer_normal => {2333 .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
2288 const defer_scope = scope.cast(Scope.Defer).?;
2289 scope = defer_scope.parent;
2290 try parent_gz.addDefer(defer_scope.index, defer_scope.len);
2291 },
2292 .defer_error => scope = scope.cast(Scope.Defer).?.parent,
2293 .namespace => break,2334 .namespace => break,
2294 .top => unreachable,2335 .top => unreachable,
2295 }2336 }
...@@ -2881,6 +2922,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As...@@ -2881,6 +2922,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
2881 .panic,2922 .panic,
2882 .trap,2923 .trap,
2883 .check_comptime_control_flow,2924 .check_comptime_control_flow,
2925 .switch_continue,
2884 => {2926 => {
2885 noreturn_src_node = statement;2927 noreturn_src_node = statement;
2886 break :b true;2928 break :b true;
...@@ -7546,7 +7588,8 @@ fn switchExpr(...@@ -7546,7 +7588,8 @@ fn switchExpr(
7546 parent_gz: *GenZir,7588 parent_gz: *GenZir,
7547 scope: *Scope,7589 scope: *Scope,
7548 ri: ResultInfo,7590 ri: ResultInfo,
7549 switch_node: Ast.Node.Index,7591 node: Ast.Node.Index,
7592 switch_full: Ast.full.Switch,
7550) InnerError!Zir.Inst.Ref {7593) InnerError!Zir.Inst.Ref {
7551 const astgen = parent_gz.astgen;7594 const astgen = parent_gz.astgen;
7552 const gpa = astgen.gpa;7595 const gpa = astgen.gpa;
...@@ -7555,14 +7598,14 @@ fn switchExpr(...@@ -7555,14 +7598,14 @@ fn switchExpr(
7555 const node_tags = tree.nodes.items(.tag);7598 const node_tags = tree.nodes.items(.tag);
7556 const main_tokens = tree.nodes.items(.main_token);7599 const main_tokens = tree.nodes.items(.main_token);
7557 const token_tags = tree.tokens.items(.tag);7600 const token_tags = tree.tokens.items(.tag);
7558 const operand_node = node_datas[switch_node].lhs;7601 const operand_node = node_datas[node].lhs;
7559 const extra = tree.extraData(node_datas[switch_node].rhs, Ast.Node.SubRange);7602 const extra = tree.extraData(node_datas[node].rhs, Ast.Node.SubRange);
7560 const case_nodes = tree.extra_data[extra.start..extra.end];7603 const case_nodes = tree.extra_data[extra.start..extra.end];
75617604
7562 const need_rl = astgen.nodes_need_rl.contains(switch_node);7605 const need_rl = astgen.nodes_need_rl.contains(node);
7563 const block_ri: ResultInfo = if (need_rl) ri else .{7606 const block_ri: ResultInfo = if (need_rl) ri else .{
7564 .rl = switch (ri.rl) {7607 .rl = switch (ri.rl) {
7565 .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, switch_node)).? },7608 .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? },
7566 .inferred_ptr => .none,7609 .inferred_ptr => .none,
7567 else => ri.rl,7610 else => ri.rl,
7568 },7611 },
...@@ -7573,11 +7616,16 @@ fn switchExpr(...@@ -7573,11 +7616,16 @@ fn switchExpr(
7573 const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?;7616 const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?;
7574 const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);7617 const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);
75757618
7619 if (switch_full.label_token) |label_token| {
7620 try astgen.checkLabelRedefinition(scope, label_token);
7621 }
7622
7576 // We perform two passes over the AST. This first pass is to collect information7623 // We perform two passes over the AST. This first pass is to collect information
7577 // for the following variables, make note of the special prong AST node index,7624 // for the following variables, make note of the special prong AST node index,
7578 // and bail out with a compile error if there are multiple special prongs present.7625 // and bail out with a compile error if there are multiple special prongs present.
7579 var any_payload_is_ref = false;7626 var any_payload_is_ref = false;
7580 var any_has_tag_capture = false;7627 var any_has_tag_capture = false;
7628 var any_non_inline_capture = false;
7581 var scalar_cases_len: u32 = 0;7629 var scalar_cases_len: u32 = 0;
7582 var multi_cases_len: u32 = 0;7630 var multi_cases_len: u32 = 0;
7583 var inline_cases_len: u32 = 0;7631 var inline_cases_len: u32 = 0;
...@@ -7595,6 +7643,15 @@ fn switchExpr(...@@ -7595,6 +7643,15 @@ fn switchExpr(
7595 if (token_tags[ident + 1] == .comma) {7643 if (token_tags[ident + 1] == .comma) {
7596 any_has_tag_capture = true;7644 any_has_tag_capture = true;
7597 }7645 }
7646
7647 // If the first capture is ignored, then there is no runtime-known
7648 // capture, as the tag capture must be for an inline prong.
7649 // This check isn't perfect, because for things like enums, the
7650 // first prong *is* comptime-known for inline prongs! But such
7651 // knowledge requires semantic analysis.
7652 if (!mem.eql(u8, tree.tokenSlice(ident), "_")) {
7653 any_non_inline_capture = true;
7654 }
7598 }7655 }
7599 // Check for else/`_` prong.7656 // Check for else/`_` prong.
7600 if (case.ast.values.len == 0) {7657 if (case.ast.values.len == 0) {
...@@ -7614,7 +7671,7 @@ fn switchExpr(...@@ -7614,7 +7671,7 @@ fn switchExpr(
7614 );7671 );
7615 } else if (underscore_src) |some_underscore| {7672 } else if (underscore_src) |some_underscore| {
7616 return astgen.failNodeNotes(7673 return astgen.failNodeNotes(
7617 switch_node,7674 node,
7618 "else and '_' prong in switch expression",7675 "else and '_' prong in switch expression",
7619 .{},7676 .{},
7620 &[_]u32{7677 &[_]u32{
...@@ -7655,7 +7712,7 @@ fn switchExpr(...@@ -7655,7 +7712,7 @@ fn switchExpr(
7655 );7712 );
7656 } else if (else_src) |some_else| {7713 } else if (else_src) |some_else| {
7657 return astgen.failNodeNotes(7714 return astgen.failNodeNotes(
7658 switch_node,7715 node,
7659 "else and '_' prong in switch expression",7716 "else and '_' prong in switch expression",
7660 .{},7717 .{},
7661 &[_]u32{7718 &[_]u32{
...@@ -7704,6 +7761,12 @@ fn switchExpr(...@@ -7704,6 +7761,12 @@ fn switchExpr(
7704 const raw_operand = try expr(parent_gz, scope, operand_ri, operand_node);7761 const raw_operand = try expr(parent_gz, scope, operand_ri, operand_node);
7705 const item_ri: ResultInfo = .{ .rl = .none };7762 const item_ri: ResultInfo = .{ .rl = .none };
77067763
7764 // If this switch is labeled, it will have `continue`s targeting it, and thus we need the operand type
7765 // to provide a result type.
7766 const raw_operand_ty_ref = if (switch_full.label_token != null) t: {
7767 break :t try parent_gz.addUnNode(.typeof, raw_operand, operand_node);
7768 } else undefined;
7769
7707 // This contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti,7770 // This contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti,
7708 // except the first cases_nodes.len slots are a table that indexes payloads later in the array, with7771 // except the first cases_nodes.len slots are a table that indexes payloads later in the array, with
7709 // the special case index coming first, then scalar_case_len indexes, then multi_cases_len indexes7772 // the special case index coming first, then scalar_case_len indexes, then multi_cases_len indexes
...@@ -7725,7 +7788,22 @@ fn switchExpr(...@@ -7725,7 +7788,22 @@ fn switchExpr(
7725 try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc);7788 try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc);
7726 // This gets added to the parent block later, after the item expressions.7789 // This gets added to the parent block later, after the item expressions.
7727 const switch_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_block_ref else .switch_block;7790 const switch_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_block_ref else .switch_block;
7728 const switch_block = try parent_gz.makeBlockInst(switch_tag, switch_node);7791 const switch_block = try parent_gz.makeBlockInst(switch_tag, node);
7792
7793 if (switch_full.label_token) |label_token| {
7794 block_scope.continue_block = switch_block.toOptional();
7795 block_scope.continue_result_info = .{
7796 .rl = if (any_payload_is_ref)
7797 .{ .ref_coerced_ty = raw_operand_ty_ref }
7798 else
7799 .{ .coerced_ty = raw_operand_ty_ref },
7800 };
7801
7802 block_scope.label = .{
7803 .token = label_token,
7804 .block_inst = switch_block,
7805 };
7806 }
77297807
7730 // We re-use this same scope for all cases, including the special prong, if any.7808 // We re-use this same scope for all cases, including the special prong, if any.
7731 var case_scope = parent_gz.makeSubBlock(&block_scope.base);7809 var case_scope = parent_gz.makeSubBlock(&block_scope.base);
...@@ -7946,6 +8024,8 @@ fn switchExpr(...@@ -7946,6 +8024,8 @@ fn switchExpr(
7946 .has_else = special_prong == .@"else",8024 .has_else = special_prong == .@"else",
7947 .has_under = special_prong == .under,8025 .has_under = special_prong == .under,
7948 .any_has_tag_capture = any_has_tag_capture,8026 .any_has_tag_capture = any_has_tag_capture,
8027 .any_non_inline_capture = any_non_inline_capture,
8028 .has_continue = switch_full.label_token != null,
7949 .scalar_cases_len = @intCast(scalar_cases_len),8029 .scalar_cases_len = @intCast(scalar_cases_len),
7950 },8030 },
7951 });8031 });
...@@ -7982,7 +8062,7 @@ fn switchExpr(...@@ -7982,7 +8062,7 @@ fn switchExpr(
7982 }8062 }
79838063
7984 if (need_result_rvalue) {8064 if (need_result_rvalue) {
7985 return rvalue(parent_gz, ri, switch_block.toRef(), switch_node);8065 return rvalue(parent_gz, ri, switch_block.toRef(), node);
7986 } else {8066 } else {
7987 return switch_block.toRef();8067 return switch_block.toRef();
7988 }8068 }
...@@ -11824,6 +11904,7 @@ const GenZir = struct {...@@ -11824,6 +11904,7 @@ const GenZir = struct {
11824 continue_block: Zir.Inst.OptionalIndex = .none,11904 continue_block: Zir.Inst.OptionalIndex = .none,
11825 /// Only valid when setBreakResultInfo is called.11905 /// Only valid when setBreakResultInfo is called.
11826 break_result_info: AstGen.ResultInfo = undefined,11906 break_result_info: AstGen.ResultInfo = undefined,
11907 continue_result_info: AstGen.ResultInfo = undefined,
1182711908
11828 suspend_node: Ast.Node.Index = 0,11909 suspend_node: Ast.Node.Index = 0,
11829 nosuspend_node: Ast.Node.Index = 0,11910 nosuspend_node: Ast.Node.Index = 0,
lib/std/zig/Parse.zig+20-6
...@@ -924,7 +924,6 @@ fn expectContainerField(p: *Parse) !Node.Index {...@@ -924,7 +924,6 @@ fn expectContainerField(p: *Parse) !Node.Index {
924/// / KEYWORD_errdefer Payload? BlockExprStatement924/// / KEYWORD_errdefer Payload? BlockExprStatement
925/// / IfStatement925/// / IfStatement
926/// / LabeledStatement926/// / LabeledStatement
927/// / SwitchExpr
928/// / VarDeclExprStatement927/// / VarDeclExprStatement
929fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index {928fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index {
930 if (p.eatToken(.keyword_comptime)) |comptime_token| {929 if (p.eatToken(.keyword_comptime)) |comptime_token| {
...@@ -995,7 +994,6 @@ fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index {...@@ -995,7 +994,6 @@ fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index {
995 .rhs = try p.expectBlockExprStatement(),994 .rhs = try p.expectBlockExprStatement(),
996 },995 },
997 }),996 }),
998 .keyword_switch => return p.expectSwitchExpr(),
999 .keyword_if => return p.expectIfStatement(),997 .keyword_if => return p.expectIfStatement(),
1000 .keyword_enum, .keyword_struct, .keyword_union => {998 .keyword_enum, .keyword_struct, .keyword_union => {
1001 const identifier = p.tok_i + 1;999 const identifier = p.tok_i + 1;
...@@ -1238,7 +1236,7 @@ fn expectIfStatement(p: *Parse) !Node.Index {...@@ -1238,7 +1236,7 @@ fn expectIfStatement(p: *Parse) !Node.Index {
1238 });1236 });
1239}1237}
12401238
1241/// LabeledStatement <- BlockLabel? (Block / LoopStatement)1239/// LabeledStatement <- BlockLabel? (Block / LoopStatement / SwitchExpr)
1242fn parseLabeledStatement(p: *Parse) !Node.Index {1240fn parseLabeledStatement(p: *Parse) !Node.Index {
1243 const label_token = p.parseBlockLabel();1241 const label_token = p.parseBlockLabel();
1244 const block = try p.parseBlock();1242 const block = try p.parseBlock();
...@@ -1247,6 +1245,9 @@ fn parseLabeledStatement(p: *Parse) !Node.Index {...@@ -1247,6 +1245,9 @@ fn parseLabeledStatement(p: *Parse) !Node.Index {
1247 const loop_stmt = try p.parseLoopStatement();1245 const loop_stmt = try p.parseLoopStatement();
1248 if (loop_stmt != 0) return loop_stmt;1246 if (loop_stmt != 0) return loop_stmt;
12491247
1248 const switch_expr = try p.parseSwitchExpr();
1249 if (switch_expr != 0) return switch_expr;
1250
1250 if (label_token != 0) {1251 if (label_token != 0) {
1251 const after_colon = p.tok_i;1252 const after_colon = p.tok_i;
1252 const node = try p.parseTypeExpr();1253 const node = try p.parseTypeExpr();
...@@ -2072,7 +2073,7 @@ fn expectTypeExpr(p: *Parse) Error!Node.Index {...@@ -2072,7 +2073,7 @@ fn expectTypeExpr(p: *Parse) Error!Node.Index {
2072/// / KEYWORD_break BreakLabel? Expr?2073/// / KEYWORD_break BreakLabel? Expr?
2073/// / KEYWORD_comptime Expr2074/// / KEYWORD_comptime Expr
2074/// / KEYWORD_nosuspend Expr2075/// / KEYWORD_nosuspend Expr
2075/// / KEYWORD_continue BreakLabel?2076/// / KEYWORD_continue BreakLabel? Expr?
2076/// / KEYWORD_resume Expr2077/// / KEYWORD_resume Expr
2077/// / KEYWORD_return Expr?2078/// / KEYWORD_return Expr?
2078/// / BlockLabel? LoopExpr2079/// / BlockLabel? LoopExpr
...@@ -2098,7 +2099,7 @@ fn parsePrimaryExpr(p: *Parse) !Node.Index {...@@ -2098,7 +2099,7 @@ fn parsePrimaryExpr(p: *Parse) !Node.Index {
2098 .main_token = p.nextToken(),2099 .main_token = p.nextToken(),
2099 .data = .{2100 .data = .{
2100 .lhs = try p.parseBreakLabel(),2101 .lhs = try p.parseBreakLabel(),
2101 .rhs = undefined,2102 .rhs = try p.parseExpr(),
2102 },2103 },
2103 });2104 });
2104 },2105 },
...@@ -2627,7 +2628,6 @@ fn parseSuffixExpr(p: *Parse) !Node.Index {...@@ -2627,7 +2628,6 @@ fn parseSuffixExpr(p: *Parse) !Node.Index {
2627/// / KEYWORD_anyframe2628/// / KEYWORD_anyframe
2628/// / KEYWORD_unreachable2629/// / KEYWORD_unreachable
2629/// / STRINGLITERAL2630/// / STRINGLITERAL
2630/// / SwitchExpr
2631///2631///
2632/// ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto2632/// ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto
2633///2633///
...@@ -2647,6 +2647,7 @@ fn parseSuffixExpr(p: *Parse) !Node.Index {...@@ -2647,6 +2647,7 @@ fn parseSuffixExpr(p: *Parse) !Node.Index {
2647/// LabeledTypeExpr2647/// LabeledTypeExpr
2648/// <- BlockLabel Block2648/// <- BlockLabel Block
2649/// / BlockLabel? LoopTypeExpr2649/// / BlockLabel? LoopTypeExpr
2650/// / BlockLabel? SwitchExpr
2650///2651///
2651/// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)2652/// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
2652fn parsePrimaryTypeExpr(p: *Parse) !Node.Index {2653fn parsePrimaryTypeExpr(p: *Parse) !Node.Index {
...@@ -2753,6 +2754,10 @@ fn parsePrimaryTypeExpr(p: *Parse) !Node.Index {...@@ -2753,6 +2754,10 @@ fn parsePrimaryTypeExpr(p: *Parse) !Node.Index {
2753 p.tok_i += 2;2754 p.tok_i += 2;
2754 return p.parseWhileTypeExpr();2755 return p.parseWhileTypeExpr();
2755 },2756 },
2757 .keyword_switch => {
2758 p.tok_i += 2;
2759 return p.expectSwitchExpr();
2760 },
2756 .l_brace => {2761 .l_brace => {
2757 p.tok_i += 2;2762 p.tok_i += 2;
2758 return p.parseBlock();2763 return p.parseBlock();
...@@ -3029,8 +3034,17 @@ fn parseWhileTypeExpr(p: *Parse) !Node.Index {...@@ -3029,8 +3034,17 @@ fn parseWhileTypeExpr(p: *Parse) !Node.Index {
3029}3034}
30303035
3031/// SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE3036/// SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE
3037fn parseSwitchExpr(p: *Parse) !Node.Index {
3038 const switch_token = p.eatToken(.keyword_switch) orelse return null_node;
3039 return p.expectSwitchSuffix(switch_token);
3040}
3041
3032fn expectSwitchExpr(p: *Parse) !Node.Index {3042fn expectSwitchExpr(p: *Parse) !Node.Index {
3033 const switch_token = p.assertToken(.keyword_switch);3043 const switch_token = p.assertToken(.keyword_switch);
3044 return p.expectSwitchSuffix(switch_token);
3045}
3046
3047fn expectSwitchSuffix(p: *Parse, switch_token: TokenIndex) !Node.Index {
3034 _ = try p.expectToken(.l_paren);3048 _ = try p.expectToken(.l_paren);
3035 const expr_node = try p.expectExpr();3049 const expr_node = try p.expectExpr();
3036 _ = try p.expectToken(.r_paren);3050 _ = try p.expectToken(.r_paren);
lib/std/zig/Zir.zig+13-1
...@@ -314,6 +314,9 @@ pub const Inst = struct {...@@ -314,6 +314,9 @@ pub const Inst = struct {
314 /// break instruction in a block, and the target block is the parent.314 /// break instruction in a block, and the target block is the parent.
315 /// Uses the `break` union field.315 /// Uses the `break` union field.
316 break_inline,316 break_inline,
317 /// Branch from within a switch case to the case specified by the operand.
318 /// Uses the `break` union field. `block_inst` refers to a `switch_block` or `switch_block_ref`.
319 switch_continue,
317 /// Checks that comptime control flow does not happen inside a runtime block.320 /// Checks that comptime control flow does not happen inside a runtime block.
318 /// Uses the `un_node` union field.321 /// Uses the `un_node` union field.
319 check_comptime_control_flow,322 check_comptime_control_flow,
...@@ -1273,6 +1276,7 @@ pub const Inst = struct {...@@ -1273,6 +1276,7 @@ pub const Inst = struct {
1273 .panic,1276 .panic,
1274 .trap,1277 .trap,
1275 .check_comptime_control_flow,1278 .check_comptime_control_flow,
1279 .switch_continue,
1276 => true,1280 => true,
1277 };1281 };
1278 }1282 }
...@@ -1512,6 +1516,7 @@ pub const Inst = struct {...@@ -1512,6 +1516,7 @@ pub const Inst = struct {
1512 .break_inline,1516 .break_inline,
1513 .condbr,1517 .condbr,
1514 .condbr_inline,1518 .condbr_inline,
1519 .switch_continue,
1515 .compile_error,1520 .compile_error,
1516 .ret_node,1521 .ret_node,
1517 .ret_load,1522 .ret_load,
...@@ -1597,6 +1602,7 @@ pub const Inst = struct {...@@ -1597,6 +1602,7 @@ pub const Inst = struct {
1597 .bool_br_or = .pl_node,1602 .bool_br_or = .pl_node,
1598 .@"break" = .@"break",1603 .@"break" = .@"break",
1599 .break_inline = .@"break",1604 .break_inline = .@"break",
1605 .switch_continue = .@"break",
1600 .check_comptime_control_flow = .un_node,1606 .check_comptime_control_flow = .un_node,
1601 .for_len = .pl_node,1607 .for_len = .pl_node,
1602 .call = .pl_node,1608 .call = .pl_node,
...@@ -2288,6 +2294,7 @@ pub const Inst = struct {...@@ -2288,6 +2294,7 @@ pub const Inst = struct {
2288 },2294 },
2289 @"break": struct {2295 @"break": struct {
2290 operand: Ref,2296 operand: Ref,
2297 /// Index of a `Break` payload.
2291 payload_index: u32,2298 payload_index: u32,
2292 },2299 },
2293 dbg_stmt: LineColumn,2300 dbg_stmt: LineColumn,
...@@ -2945,9 +2952,13 @@ pub const Inst = struct {...@@ -2945,9 +2952,13 @@ pub const Inst = struct {
2945 has_under: bool,2952 has_under: bool,
2946 /// If true, at least one prong has an inline tag capture.2953 /// If true, at least one prong has an inline tag capture.
2947 any_has_tag_capture: bool,2954 any_has_tag_capture: bool,
2955 /// If true, at least one prong has a capture which may not
2956 /// be comptime-known via `inline`.
2957 any_non_inline_capture: bool,
2958 has_continue: bool,
2948 scalar_cases_len: ScalarCasesLen,2959 scalar_cases_len: ScalarCasesLen,
29492960
2950 pub const ScalarCasesLen = u28;2961 pub const ScalarCasesLen = u26;
29512962
2952 pub fn specialProng(bits: Bits) SpecialProng {2963 pub fn specialProng(bits: Bits) SpecialProng {
2953 const has_else: u2 = @intFromBool(bits.has_else);2964 const has_else: u2 = @intFromBool(bits.has_else);
...@@ -3750,6 +3761,7 @@ fn findDeclsInner(...@@ -3750,6 +3761,7 @@ fn findDeclsInner(
3750 .bool_br_or,3761 .bool_br_or,
3751 .@"break",3762 .@"break",
3752 .break_inline,3763 .break_inline,
3764 .switch_continue,
3753 .check_comptime_control_flow,3765 .check_comptime_control_flow,
3754 .builtin_call,3766 .builtin_call,
3755 .cmp_lt,3767 .cmp_lt,
src/Air.zig+16-1
...@@ -429,6 +429,14 @@ pub const Inst = struct {...@@ -429,6 +429,14 @@ pub const Inst = struct {
429 /// Result type is always noreturn; no instructions in a block follow this one.429 /// Result type is always noreturn; no instructions in a block follow this one.
430 /// Uses the `pl_op` field. Operand is the condition. Payload is `SwitchBr`.430 /// Uses the `pl_op` field. Operand is the condition. Payload is `SwitchBr`.
431 switch_br,431 switch_br,
432 /// Switch branch which can dispatch back to itself with a different operand.
433 /// Result type is always noreturn; no instructions in a block follow this one.
434 /// Uses the `pl_op` field. Operand is the condition. Payload is `SwitchBr`.
435 loop_switch_br,
436 /// Dispatches back to a branch of a parent `loop_switch_br`.
437 /// Result type is always noreturn; no instructions in a block follow this one.
438 /// Uses the `br` field. `block_inst` is a `loop_switch_br` instruction.
439 switch_dispatch,
432 /// Given an operand which is an error union, splits control flow. In440 /// Given an operand which is an error union, splits control flow. In
433 /// case of error, control flow goes into the block that is part of this441 /// case of error, control flow goes into the block that is part of this
434 /// instruction, which is guaranteed to end with a return instruction442 /// instruction, which is guaranteed to end with a return instruction
...@@ -1454,6 +1462,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)...@@ -1454,6 +1462,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
1454 .br,1462 .br,
1455 .cond_br,1463 .cond_br,
1456 .switch_br,1464 .switch_br,
1465 .loop_switch_br,
1466 .switch_dispatch,
1457 .ret,1467 .ret,
1458 .ret_safe,1468 .ret_safe,
1459 .ret_load,1469 .ret_load,
...@@ -1618,6 +1628,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {...@@ -1618,6 +1628,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
1618 .call_never_inline,1628 .call_never_inline,
1619 .cond_br,1629 .cond_br,
1620 .switch_br,1630 .switch_br,
1631 .loop_switch_br,
1632 .switch_dispatch,
1621 .@"try",1633 .@"try",
1622 .try_cold,1634 .try_cold,
1623 .try_ptr,1635 .try_ptr,
...@@ -1903,7 +1915,10 @@ pub const UnwrappedSwitch = struct {...@@ -1903,7 +1915,10 @@ pub const UnwrappedSwitch = struct {
19031915
1904pub fn unwrapSwitch(air: *const Air, switch_inst: Inst.Index) UnwrappedSwitch {1916pub fn unwrapSwitch(air: *const Air, switch_inst: Inst.Index) UnwrappedSwitch {
1905 const inst = air.instructions.get(@intFromEnum(switch_inst));1917 const inst = air.instructions.get(@intFromEnum(switch_inst));
1906 assert(inst.tag == .switch_br);1918 switch (inst.tag) {
1919 .switch_br, .loop_switch_br => {},
1920 else => unreachable, // assertion failure
1921 }
1907 const pl_op = inst.data.pl_op;1922 const pl_op = inst.data.pl_op;
1908 const extra = air.extraData(SwitchBr, pl_op.payload);1923 const extra = air.extraData(SwitchBr, pl_op.payload);
1909 const hint_bag_count = std.math.divCeil(usize, extra.data.cases_len + 1, 10) catch unreachable;1924 const hint_bag_count = std.math.divCeil(usize, extra.data.cases_len + 1, 10) catch unreachable;
src/Air/types_resolved.zig+2-2
...@@ -222,7 +222,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {...@@ -222,7 +222,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
222 if (!checkRef(data.un_op, zcu)) return false;222 if (!checkRef(data.un_op, zcu)) return false;
223 },223 },
224224
225 .br => {225 .br, .switch_dispatch => {
226 if (!checkRef(data.br.operand, zcu)) return false;226 if (!checkRef(data.br.operand, zcu)) return false;
227 },227 },
228228
...@@ -380,7 +380,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {...@@ -380,7 +380,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
380 )) return false;380 )) return false;
381 },381 },
382382
383 .switch_br => {383 .switch_br, .loop_switch_br => {
384 const switch_br = air.unwrapSwitch(inst);384 const switch_br = air.unwrapSwitch(inst);
385 if (!checkRef(switch_br.operand, zcu)) return false;385 if (!checkRef(switch_br.operand, zcu)) return false;
386 var it = switch_br.iterateCases();386 var it = switch_br.iterateCases();
src/Liveness.zig+174-93
...@@ -31,6 +31,7 @@ tomb_bits: []usize,...@@ -31,6 +31,7 @@ tomb_bits: []usize,
31/// * `try`, `try_ptr` - points to a `CondBr` in `extra` at this index. The error path (the block31/// * `try`, `try_ptr` - points to a `CondBr` in `extra` at this index. The error path (the block
32/// in the instruction) is considered the "else" path, and the rest of the block the "then".32/// in the instruction) is considered the "else" path, and the rest of the block the "then".
33/// * `switch_br` - points to a `SwitchBr` in `extra` at this index.33/// * `switch_br` - points to a `SwitchBr` in `extra` at this index.
34/// * `loop_switch_br` - points to a `SwitchBr` in `extra` at this index.
34/// * `block` - points to a `Block` in `extra` at this index.35/// * `block` - points to a `Block` in `extra` at this index.
35/// * `asm`, `call`, `aggregate_init` - the value is a set of bits which are the extra tomb36/// * `asm`, `call`, `aggregate_init` - the value is a set of bits which are the extra tomb
36/// bits of operands.37/// bits of operands.
...@@ -68,8 +69,8 @@ pub const Block = struct {...@@ -68,8 +69,8 @@ pub const Block = struct {
68/// Liveness analysis runs in several passes. Each pass iterates backwards over instructions in69/// Liveness analysis runs in several passes. Each pass iterates backwards over instructions in
69/// bodies, and recurses into bodies.70/// bodies, and recurses into bodies.
70const LivenessPass = enum {71const LivenessPass = enum {
71 /// In this pass, we perform some basic analysis of loops to gain information the main pass72 /// In this pass, we perform some basic analysis of loops to gain information the main pass needs.
72 /// needs. In particular, for every `loop`, we track the following information:73 /// In particular, for every `loop` and `loop_switch_br`, we track the following information:
73 /// * Every outer block which the loop body contains a `br` to.74 /// * Every outer block which the loop body contains a `br` to.
74 /// * Every outer loop which the loop body contains a `repeat` to.75 /// * Every outer loop which the loop body contains a `repeat` to.
75 /// * Every operand referenced within the loop body but created outside the loop.76 /// * Every operand referenced within the loop body but created outside the loop.
...@@ -91,7 +92,8 @@ fn LivenessPassData(comptime pass: LivenessPass) type {...@@ -91,7 +92,8 @@ fn LivenessPassData(comptime pass: LivenessPass) type {
91 .loop_analysis => struct {92 .loop_analysis => struct {
92 /// The set of blocks which are exited with a `br` instruction at some point within this93 /// The set of blocks which are exited with a `br` instruction at some point within this
93 /// body and which we are currently within. Also includes `loop`s which are the target94 /// body and which we are currently within. Also includes `loop`s which are the target
94 /// of a `repeat` instruction.95 /// of a `repeat` instruction, and `loop_switch_br`s which are the target of a
96 /// `switch_dispatch` instruction.
95 breaks: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},97 breaks: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
9698
97 /// The set of operands for which we have seen at least one usage but not their birth.99 /// The set of operands for which we have seen at least one usage but not their birth.
...@@ -330,6 +332,7 @@ pub fn categorizeOperand(...@@ -330,6 +332,7 @@ pub fn categorizeOperand(
330 .trap,332 .trap,
331 .breakpoint,333 .breakpoint,
332 .repeat,334 .repeat,
335 .switch_dispatch,
333 .dbg_stmt,336 .dbg_stmt,
334 .unreach,337 .unreach,
335 .ret_addr,338 .ret_addr,
...@@ -662,21 +665,17 @@ pub fn categorizeOperand(...@@ -662,21 +665,17 @@ pub fn categorizeOperand(
662665
663 return .complex;666 return .complex;
664 },667 },
665 .@"try", .try_cold => {668
666 return .complex;669 .@"try",
667 },670 .try_cold,
668 .try_ptr, .try_ptr_cold => {671 .try_ptr,
669 return .complex;672 .try_ptr_cold,
670 },673 .loop,
671 .loop => {674 .cond_br,
672 return .complex;675 .switch_br,
673 },676 .loop_switch_br,
674 .cond_br => {677 => return .complex,
675 return .complex;678
676 },
677 .switch_br => {
678 return .complex;
679 },
680 .wasm_memory_grow => {679 .wasm_memory_grow => {
681 const pl_op = air_datas[@intFromEnum(inst)].pl_op;680 const pl_op = air_datas[@intFromEnum(inst)].pl_op;
682 if (pl_op.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);681 if (pl_op.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none);
...@@ -1206,6 +1205,7 @@ fn analyzeInst(...@@ -1206,6 +1205,7 @@ fn analyzeInst(
12061205
1207 .br => return analyzeInstBr(a, pass, data, inst),1206 .br => return analyzeInstBr(a, pass, data, inst),
1208 .repeat => return analyzeInstRepeat(a, pass, data, inst),1207 .repeat => return analyzeInstRepeat(a, pass, data, inst),
1208 .switch_dispatch => return analyzeInstSwitchDispatch(a, pass, data, inst),
12091209
1210 .assembly => {1210 .assembly => {
1211 const extra = a.air.extraData(Air.Asm, inst_datas[@intFromEnum(inst)].ty_pl.payload);1211 const extra = a.air.extraData(Air.Asm, inst_datas[@intFromEnum(inst)].ty_pl.payload);
...@@ -1262,7 +1262,8 @@ fn analyzeInst(...@@ -1262,7 +1262,8 @@ fn analyzeInst(
1262 .@"try", .try_cold => return analyzeInstCondBr(a, pass, data, inst, .@"try"),1262 .@"try", .try_cold => return analyzeInstCondBr(a, pass, data, inst, .@"try"),
1263 .try_ptr, .try_ptr_cold => return analyzeInstCondBr(a, pass, data, inst, .try_ptr),1263 .try_ptr, .try_ptr_cold => return analyzeInstCondBr(a, pass, data, inst, .try_ptr),
1264 .cond_br => return analyzeInstCondBr(a, pass, data, inst, .cond_br),1264 .cond_br => return analyzeInstCondBr(a, pass, data, inst, .cond_br),
1265 .switch_br => return analyzeInstSwitchBr(a, pass, data, inst),1265 .switch_br => return analyzeInstSwitchBr(a, pass, data, inst, false),
1266 .loop_switch_br => return analyzeInstSwitchBr(a, pass, data, inst, true),
12661267
1267 .wasm_memory_grow => {1268 .wasm_memory_grow => {
1268 const pl_op = inst_datas[@intFromEnum(inst)].pl_op;1269 const pl_op = inst_datas[@intFromEnum(inst)].pl_op;
...@@ -1412,6 +1413,35 @@ fn analyzeInstRepeat(...@@ -1412,6 +1413,35 @@ fn analyzeInstRepeat(
1412 return analyzeOperands(a, pass, data, inst, .{ .none, .none, .none });1413 return analyzeOperands(a, pass, data, inst, .{ .none, .none, .none });
1413}1414}
14141415
1416fn analyzeInstSwitchDispatch(
1417 a: *Analysis,
1418 comptime pass: LivenessPass,
1419 data: *LivenessPassData(pass),
1420 inst: Air.Inst.Index,
1421) !void {
1422 // This happens to be identical to `analyzeInstBr`, but is separated anyway for clarity.
1423
1424 const inst_datas = a.air.instructions.items(.data);
1425 const br = inst_datas[@intFromEnum(inst)].br;
1426 const gpa = a.gpa;
1427
1428 switch (pass) {
1429 .loop_analysis => {
1430 try data.breaks.put(gpa, br.block_inst, {});
1431 },
1432
1433 .main_analysis => {
1434 const block_scope = data.block_scopes.get(br.block_inst).?; // we should always be repeating an enclosing loop
1435
1436 const new_live_set = try block_scope.live_set.clone(gpa);
1437 data.live_set.deinit(gpa);
1438 data.live_set = new_live_set;
1439 },
1440 }
1441
1442 return analyzeOperands(a, pass, data, inst, .{ br.operand, .none, .none });
1443}
1444
1415fn analyzeInstBlock(1445fn analyzeInstBlock(
1416 a: *Analysis,1446 a: *Analysis,
1417 comptime pass: LivenessPass,1447 comptime pass: LivenessPass,
...@@ -1482,109 +1512,133 @@ fn analyzeInstBlock(...@@ -1482,109 +1512,133 @@ fn analyzeInstBlock(
1482 }1512 }
1483}1513}
14841514
1485fn analyzeInstLoop(1515fn writeLoopInfo(
1486 a: *Analysis,1516 a: *Analysis,
1487 comptime pass: LivenessPass,1517 data: *LivenessPassData(.loop_analysis),
1488 data: *LivenessPassData(pass),
1489 inst: Air.Inst.Index,1518 inst: Air.Inst.Index,
1519 old_breaks: std.AutoHashMapUnmanaged(Air.Inst.Index, void),
1520 old_live: std.AutoHashMapUnmanaged(Air.Inst.Index, void),
1490) !void {1521) !void {
1491 const inst_datas = a.air.instructions.items(.data);
1492 const extra = a.air.extraData(Air.Block, inst_datas[@intFromEnum(inst)].ty_pl.payload);
1493 const body: []const Air.Inst.Index = @ptrCast(a.air.extra[extra.end..][0..extra.data.body_len]);
1494 const gpa = a.gpa;1522 const gpa = a.gpa;
14951523
1496 try analyzeOperands(a, pass, data, inst, .{ .none, .none, .none });1524 // `loop`s are guaranteed to have at least one matching `repeat`.
1525 // Similarly, `loop_switch_br`s have a matching `switch_dispatch`.
1526 // However, we no longer care about repeats of this loop for resolving
1527 // which operands must live within it.
1528 assert(data.breaks.remove(inst));
14971529
1498 switch (pass) {1530 const extra_index: u32 = @intCast(a.extra.items.len);
1499 .loop_analysis => {
1500 var old_breaks = data.breaks.move();
1501 defer old_breaks.deinit(gpa);
15021531
1503 var old_live = data.live_set.move();1532 const num_breaks = data.breaks.count();
1504 defer old_live.deinit(gpa);1533 try a.extra.ensureUnusedCapacity(gpa, 1 + num_breaks);
15051534
1506 try analyzeBody(a, pass, data, body);1535 a.extra.appendAssumeCapacity(num_breaks);
15071536
1508 // `loop`s are guaranteed to have at least one matching `repeat`.1537 var it = data.breaks.keyIterator();
1509 // However, we no longer care about repeats of this loop itself.1538 while (it.next()) |key| {
1510 assert(data.breaks.remove(inst));1539 const block_inst = key.*;
1540 a.extra.appendAssumeCapacity(@intFromEnum(block_inst));
1541 }
1542 log.debug("[{}] %{}: includes breaks to {}", .{ LivenessPass.loop_analysis, inst, fmtInstSet(&data.breaks) });
15111543
1512 const extra_index: u32 = @intCast(a.extra.items.len);1544 // Now we put the live operands from the loop body in too
1545 const num_live = data.live_set.count();
1546 try a.extra.ensureUnusedCapacity(gpa, 1 + num_live);
15131547
1514 const num_breaks = data.breaks.count();1548 a.extra.appendAssumeCapacity(num_live);
1515 try a.extra.ensureUnusedCapacity(gpa, 1 + num_breaks);1549 it = data.live_set.keyIterator();
1550 while (it.next()) |key| {
1551 const alive = key.*;
1552 a.extra.appendAssumeCapacity(@intFromEnum(alive));
1553 }
1554 log.debug("[{}] %{}: maintain liveness of {}", .{ LivenessPass.loop_analysis, inst, fmtInstSet(&data.live_set) });
15161555
1517 a.extra.appendAssumeCapacity(num_breaks);1556 try a.special.put(gpa, inst, extra_index);
15181557
1519 var it = data.breaks.keyIterator();1558 // Add back operands which were previously alive
1520 while (it.next()) |key| {1559 it = old_live.keyIterator();
1521 const block_inst = key.*;1560 while (it.next()) |key| {
1522 a.extra.appendAssumeCapacity(@intFromEnum(block_inst));1561 const alive = key.*;
1523 }1562 try data.live_set.put(gpa, alive, {});
1524 log.debug("[{}] %{}: includes breaks to {}", .{ pass, inst, fmtInstSet(&data.breaks) });1563 }
15251564
1526 // Now we put the live operands from the loop body in too1565 // And the same for breaks
1527 const num_live = data.live_set.count();1566 it = old_breaks.keyIterator();
1528 try a.extra.ensureUnusedCapacity(gpa, 1 + num_live);1567 while (it.next()) |key| {
1568 const block_inst = key.*;
1569 try data.breaks.put(gpa, block_inst, {});
1570 }
1571}
15291572
1530 a.extra.appendAssumeCapacity(num_live);1573/// When analyzing a loop in the main pass, sets up `data.live_set` to be the set
1531 it = data.live_set.keyIterator();1574/// of operands known to be alive when the loop repeats.
1532 while (it.next()) |key| {1575fn resolveLoopLiveSet(
1533 const alive = key.*;1576 a: *Analysis,
1534 a.extra.appendAssumeCapacity(@intFromEnum(alive));1577 data: *LivenessPassData(.main_analysis),
1535 }1578 inst: Air.Inst.Index,
1536 log.debug("[{}] %{}: maintain liveness of {}", .{ pass, inst, fmtInstSet(&data.live_set) });1579) !void {
1580 const gpa = a.gpa;
15371581
1538 try a.special.put(gpa, inst, extra_index);1582 const extra_idx = a.special.fetchRemove(inst).?.value;
1583 const num_breaks = data.old_extra.items[extra_idx];
1584 const breaks: []const Air.Inst.Index = @ptrCast(data.old_extra.items[extra_idx + 1 ..][0..num_breaks]);
15391585
1540 // Add back operands which were previously alive1586 const num_loop_live = data.old_extra.items[extra_idx + num_breaks + 1];
1541 it = old_live.keyIterator();1587 const loop_live: []const Air.Inst.Index = @ptrCast(data.old_extra.items[extra_idx + num_breaks + 2 ..][0..num_loop_live]);
1542 while (it.next()) |key| {
1543 const alive = key.*;
1544 try data.live_set.put(gpa, alive, {});
1545 }
15461588
1547 // And the same for breaks1589 // This is necessarily not in the same control flow branch, because loops are noreturn
1548 it = old_breaks.keyIterator();1590 data.live_set.clearRetainingCapacity();
1549 while (it.next()) |key| {
1550 const block_inst = key.*;
1551 try data.breaks.put(gpa, block_inst, {});
1552 }
1553 },
15541591
1555 .main_analysis => {1592 try data.live_set.ensureUnusedCapacity(gpa, @intCast(loop_live.len));
1556 const extra_idx = a.special.fetchRemove(inst).?.value; // remove because this data does not exist after analysis1593 for (loop_live) |alive| data.live_set.putAssumeCapacity(alive, {});
15571594
1558 const num_breaks = data.old_extra.items[extra_idx];1595 log.debug("[{}] %{}: block live set is {}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) });
1559 const breaks: []const Air.Inst.Index = @ptrCast(data.old_extra.items[extra_idx + 1 ..][0..num_breaks]);
15601596
1561 const num_loop_live = data.old_extra.items[extra_idx + num_breaks + 1];1597 for (breaks) |block_inst| {
1562 const loop_live: []const Air.Inst.Index = @ptrCast(data.old_extra.items[extra_idx + num_breaks + 2 ..][0..num_loop_live]);1598 // We might break to this block, so include every operand that the block needs alive
1599 const block_scope = data.block_scopes.get(block_inst).?;
15631600
1564 // This is necessarily not in the same control flow branch, because loops are noreturn1601 var it = block_scope.live_set.keyIterator();
1565 data.live_set.clearRetainingCapacity();1602 while (it.next()) |key| {
1603 const alive = key.*;
1604 try data.live_set.put(gpa, alive, {});
1605 }
1606 }
15661607
1567 try data.live_set.ensureUnusedCapacity(gpa, @intCast(loop_live.len));1608 log.debug("[{}] %{}: loop live set is {}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) });
1568 for (loop_live) |alive| {1609}
1569 data.live_set.putAssumeCapacity(alive, {});
1570 }
15711610
1572 log.debug("[{}] %{}: block live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });1611fn analyzeInstLoop(
1612 a: *Analysis,
1613 comptime pass: LivenessPass,
1614 data: *LivenessPassData(pass),
1615 inst: Air.Inst.Index,
1616) !void {
1617 const inst_datas = a.air.instructions.items(.data);
1618 const extra = a.air.extraData(Air.Block, inst_datas[@intFromEnum(inst)].ty_pl.payload);
1619 const body: []const Air.Inst.Index = @ptrCast(a.air.extra[extra.end..][0..extra.data.body_len]);
1620 const gpa = a.gpa;
15731621
1574 for (breaks) |block_inst| {1622 try analyzeOperands(a, pass, data, inst, .{ .none, .none, .none });
1575 // We might break to this block, so include every operand that the block needs alive
1576 const block_scope = data.block_scopes.get(block_inst).?;
15771623
1578 var it = block_scope.live_set.keyIterator();1624 switch (pass) {
1579 while (it.next()) |key| {1625 .loop_analysis => {
1580 const alive = key.*;1626 var old_breaks = data.breaks.move();
1581 try data.live_set.put(gpa, alive, {});1627 defer old_breaks.deinit(gpa);
1582 }1628
1583 }1629 var old_live = data.live_set.move();
1630 defer old_live.deinit(gpa);
1631
1632 try analyzeBody(a, pass, data, body);
1633
1634 try writeLoopInfo(a, data, inst, old_breaks, old_live);
1635 },
1636
1637 .main_analysis => {
1638 try resolveLoopLiveSet(a, data, inst);
15841639
1585 // Now, `data.live_set` is the operands which must be alive when the loop repeats.1640 // Now, `data.live_set` is the operands which must be alive when the loop repeats.
1586 // Move them into a block scope for corresponding `repeat` instructions to notice.1641 // Move them into a block scope for corresponding `repeat` instructions to notice.
1587 log.debug("[{}] %{}: loop live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
1588 try data.block_scopes.putNoClobber(gpa, inst, .{1642 try data.block_scopes.putNoClobber(gpa, inst, .{
1589 .live_set = data.live_set.move(),1643 .live_set = data.live_set.move(),
1590 });1644 });
...@@ -1720,6 +1774,7 @@ fn analyzeInstSwitchBr(...@@ -1720,6 +1774,7 @@ fn analyzeInstSwitchBr(
1720 comptime pass: LivenessPass,1774 comptime pass: LivenessPass,
1721 data: *LivenessPassData(pass),1775 data: *LivenessPassData(pass),
1722 inst: Air.Inst.Index,1776 inst: Air.Inst.Index,
1777 is_dispatch_loop: bool,
1723) !void {1778) !void {
1724 const inst_datas = a.air.instructions.items(.data);1779 const inst_datas = a.air.instructions.items(.data);
1725 const pl_op = inst_datas[@intFromEnum(inst)].pl_op;1780 const pl_op = inst_datas[@intFromEnum(inst)].pl_op;
...@@ -1730,6 +1785,17 @@ fn analyzeInstSwitchBr(...@@ -1730,6 +1785,17 @@ fn analyzeInstSwitchBr(
17301785
1731 switch (pass) {1786 switch (pass) {
1732 .loop_analysis => {1787 .loop_analysis => {
1788 var old_breaks: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{};
1789 defer old_breaks.deinit(gpa);
1790
1791 var old_live: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{};
1792 defer old_live.deinit(gpa);
1793
1794 if (is_dispatch_loop) {
1795 old_breaks = data.breaks.move();
1796 old_live = data.live_set.move();
1797 }
1798
1733 var it = switch_br.iterateCases();1799 var it = switch_br.iterateCases();
1734 while (it.next()) |case| {1800 while (it.next()) |case| {
1735 try analyzeBody(a, pass, data, case.body);1801 try analyzeBody(a, pass, data, case.body);
...@@ -1738,9 +1804,24 @@ fn analyzeInstSwitchBr(...@@ -1738,9 +1804,24 @@ fn analyzeInstSwitchBr(
1738 const else_body = it.elseBody();1804 const else_body = it.elseBody();
1739 try analyzeBody(a, pass, data, else_body);1805 try analyzeBody(a, pass, data, else_body);
1740 }1806 }
1807
1808 if (is_dispatch_loop) {
1809 try writeLoopInfo(a, data, inst, old_breaks, old_live);
1810 }
1741 },1811 },
17421812
1743 .main_analysis => {1813 .main_analysis => {
1814 if (is_dispatch_loop) {
1815 try resolveLoopLiveSet(a, data, inst);
1816 try data.block_scopes.putNoClobber(gpa, inst, .{
1817 .live_set = data.live_set.move(),
1818 });
1819 }
1820 defer if (is_dispatch_loop) {
1821 log.debug("[{}] %{}: popped loop block scop", .{ pass, inst });
1822 var scope = data.block_scopes.fetchRemove(inst).?.value;
1823 scope.live_set.deinit(gpa);
1824 };
1744 // This is, all in all, just a messier version of the `cond_br` logic. If you're trying1825 // This is, all in all, just a messier version of the `cond_br` logic. If you're trying
1745 // to understand it, I encourage looking at `analyzeInstCondBr` first.1826 // to understand it, I encourage looking at `analyzeInstCondBr` first.
17461827
src/Liveness/Verify.zig+25-6
...@@ -447,6 +447,16 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -447,6 +447,16 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
447447
448 try self.verifyMatchingLiveness(repeat.loop_inst, expected_live);448 try self.verifyMatchingLiveness(repeat.loop_inst, expected_live);
449 },449 },
450 .switch_dispatch => {
451 const br = data[@intFromEnum(inst)].br;
452
453 try self.verifyOperand(inst, br.operand, self.liveness.operandDies(inst, 0));
454
455 const expected_live = self.loops.get(br.block_inst) orelse
456 return invalid("%{}: loop %{} not in scope", .{ @intFromEnum(inst), @intFromEnum(br.block_inst) });
457
458 try self.verifyMatchingLiveness(br.block_inst, expected_live);
459 },
450 .block, .dbg_inline_block => |tag| {460 .block, .dbg_inline_block => |tag| {
451 const ty_pl = data[@intFromEnum(inst)].ty_pl;461 const ty_pl = data[@intFromEnum(inst)].ty_pl;
452 const block_ty = ty_pl.ty.toType();462 const block_ty = ty_pl.ty.toType();
...@@ -494,11 +504,11 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -494,11 +504,11 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
494504
495 // The same stuff should be alive after the loop as before it.505 // The same stuff should be alive after the loop as before it.
496 const gop = try self.loops.getOrPut(self.gpa, inst);506 const gop = try self.loops.getOrPut(self.gpa, inst);
507 if (gop.found_existing) return invalid("%{}: loop already exists", .{@intFromEnum(inst)});
497 defer {508 defer {
498 var live = self.loops.fetchRemove(inst).?;509 var live = self.loops.fetchRemove(inst).?;
499 live.value.deinit(self.gpa);510 live.value.deinit(self.gpa);
500 }511 }
501 if (gop.found_existing) return invalid("%{}: loop already exists", .{@intFromEnum(inst)});
502 gop.value_ptr.* = try self.live.clone(self.gpa);512 gop.value_ptr.* = try self.live.clone(self.gpa);
503513
504 try self.verifyBody(loop_body);514 try self.verifyBody(loop_body);
...@@ -528,7 +538,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -528,7 +538,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
528538
529 try self.verifyInst(inst);539 try self.verifyInst(inst);
530 },540 },
531 .switch_br => {541 .switch_br, .loop_switch_br => {
532 const switch_br = self.air.unwrapSwitch(inst);542 const switch_br = self.air.unwrapSwitch(inst);
533 const switch_br_liveness = try self.liveness.getSwitchBr(543 const switch_br_liveness = try self.liveness.getSwitchBr(
534 self.gpa,544 self.gpa,
...@@ -539,13 +549,22 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -539,13 +549,22 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
539549
540 try self.verifyOperand(inst, switch_br.operand, self.liveness.operandDies(inst, 0));550 try self.verifyOperand(inst, switch_br.operand, self.liveness.operandDies(inst, 0));
541551
542 var live = self.live.move();552 // Excluding the operand (which we just handled), the same stuff should be alive
543 defer live.deinit(self.gpa);553 // after the loop as before it.
554 {
555 const gop = try self.loops.getOrPut(self.gpa, inst);
556 if (gop.found_existing) return invalid("%{}: loop already exists", .{@intFromEnum(inst)});
557 gop.value_ptr.* = self.live.move();
558 }
559 defer {
560 var live = self.loops.fetchRemove(inst).?;
561 live.value.deinit(self.gpa);
562 }
544563
545 var it = switch_br.iterateCases();564 var it = switch_br.iterateCases();
546 while (it.next()) |case| {565 while (it.next()) |case| {
547 self.live.deinit(self.gpa);566 self.live.deinit(self.gpa);
548 self.live = try live.clone(self.gpa);567 self.live = try self.loops.get(inst).?.clone(self.gpa);
549568
550 for (switch_br_liveness.deaths[case.idx]) |death| try self.verifyDeath(inst, death);569 for (switch_br_liveness.deaths[case.idx]) |death| try self.verifyDeath(inst, death);
551 try self.verifyBody(case.body);570 try self.verifyBody(case.body);
...@@ -554,7 +573,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -554,7 +573,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
554 const else_body = it.elseBody();573 const else_body = it.elseBody();
555 if (else_body.len > 0) {574 if (else_body.len > 0) {
556 self.live.deinit(self.gpa);575 self.live.deinit(self.gpa);
557 self.live = try live.clone(self.gpa);576 self.live = try self.loops.get(inst).?.clone(self.gpa);
558 for (switch_br_liveness.deaths[switch_br.cases_len]) |death| try self.verifyDeath(inst, death);577 for (switch_br_liveness.deaths[switch_br.cases_len]) |death| try self.verifyDeath(inst, death);
559 try self.verifyBody(else_body);578 try self.verifyBody(else_body);
560 }579 }
src/Sema.zig+479-141
...@@ -503,11 +503,21 @@ pub const Block = struct {...@@ -503,11 +503,21 @@ pub const Block = struct {
503 /// to enable more precise compile errors.503 /// to enable more precise compile errors.
504 /// Same indexes, capacity, length as `results`.504 /// Same indexes, capacity, length as `results`.
505 src_locs: std.ArrayListUnmanaged(?LazySrcLoc),505 src_locs: std.ArrayListUnmanaged(?LazySrcLoc),
506506 /// Most blocks do not utilize this field. When it is used, its use is
507 pub fn deinit(merges: *@This(), allocator: mem.Allocator) void {507 /// contextual. The possible uses are as follows:
508 /// * for a `switch_block[_ref]`, this refers to dummy `br` instructions
509 /// which correspond to `switch_continue` ZIR. The switch logic will
510 /// rewrite these to appropriate AIR switch dispatches.
511 extra_insts: std.ArrayListUnmanaged(Air.Inst.Index) = .{},
512 /// Same indexes, capacity, length as `extra_insts`.
513 extra_src_locs: std.ArrayListUnmanaged(LazySrcLoc) = .{},
514
515 pub fn deinit(merges: *@This(), allocator: Allocator) void {
508 merges.results.deinit(allocator);516 merges.results.deinit(allocator);
509 merges.br_list.deinit(allocator);517 merges.br_list.deinit(allocator);
510 merges.src_locs.deinit(allocator);518 merges.src_locs.deinit(allocator);
519 merges.extra_insts.deinit(allocator);
520 merges.extra_src_locs.deinit(allocator);
511 }521 }
512 };522 };
513523
...@@ -946,14 +956,21 @@ fn analyzeInlineBody(...@@ -946,14 +956,21 @@ fn analyzeInlineBody(
946 error.ComptimeBreak => {},956 error.ComptimeBreak => {},
947 else => |e| return e,957 else => |e| return e,
948 }958 }
949 const break_inst = sema.comptime_break_inst;959 const break_inst = sema.code.instructions.get(@intFromEnum(sema.comptime_break_inst));
950 const break_data = sema.code.instructions.items(.data)[@intFromEnum(break_inst)].@"break";960 switch (break_inst.tag) {
951 const extra = sema.code.extraData(Zir.Inst.Break, break_data.payload_index).data;961 .switch_continue => {
962 // This is handled by separate logic.
963 return error.ComptimeBreak;
964 },
965 .break_inline, .@"break" => {},
966 else => unreachable,
967 }
968 const extra = sema.code.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data;
952 if (extra.block_inst != break_target) {969 if (extra.block_inst != break_target) {
953 // This control flow goes further up the stack.970 // This control flow goes further up the stack.
954 return error.ComptimeBreak;971 return error.ComptimeBreak;
955 }972 }
956 return try sema.resolveInst(break_data.operand);973 return try sema.resolveInst(break_inst.data.@"break".operand);
957}974}
958975
959/// Like `analyzeInlineBody`, but if the body does not break with a value, returns976/// Like `analyzeInlineBody`, but if the body does not break with a value, returns
...@@ -1571,6 +1588,13 @@ fn analyzeBodyInner(...@@ -1571,6 +1588,13 @@ fn analyzeBodyInner(
1571 i = 0;1588 i = 0;
1572 continue;1589 continue;
1573 },1590 },
1591 .switch_continue => if (block.is_comptime) {
1592 sema.comptime_break_inst = inst;
1593 return error.ComptimeBreak;
1594 } else {
1595 try sema.zirSwitchContinue(block, inst);
1596 break;
1597 },
1574 .loop => blk: {1598 .loop => blk: {
1575 if (!block.is_comptime) break :blk try sema.zirLoop(block, inst);1599 if (!block.is_comptime) break :blk try sema.zirLoop(block, inst);
1576 // Same as `block_inline`. TODO https://github.com/ziglang/zig/issues/82201600 // Same as `block_inline`. TODO https://github.com/ziglang/zig/issues/8220
...@@ -6531,6 +6555,56 @@ fn zirBreak(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError...@@ -6531,6 +6555,56 @@ fn zirBreak(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError
6531 }6555 }
6532}6556}
65336557
6558fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError!void {
6559 const tracy = trace(@src());
6560 defer tracy.end();
6561
6562 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].@"break";
6563 const extra = sema.code.extraData(Zir.Inst.Break, inst_data.payload_index).data;
6564 assert(extra.operand_src_node != Zir.Inst.Break.no_src_node);
6565 const operand_src = start_block.nodeOffset(extra.operand_src_node);
6566 const uncoerced_operand = try sema.resolveInst(inst_data.operand);
6567 const switch_inst = extra.block_inst;
6568
6569 switch (sema.code.instructions.items(.tag)[@intFromEnum(switch_inst)]) {
6570 .switch_block, .switch_block_ref => {},
6571 else => unreachable, // assertion failure
6572 }
6573
6574 const switch_payload_index = sema.code.instructions.items(.data)[@intFromEnum(switch_inst)].pl_node.payload_index;
6575 const switch_operand_ref = sema.code.extraData(Zir.Inst.SwitchBlock, switch_payload_index).data.operand;
6576 const switch_operand_ty = sema.typeOf(try sema.resolveInst(switch_operand_ref));
6577
6578 const operand = try sema.coerce(start_block, switch_operand_ty, uncoerced_operand, operand_src);
6579
6580 try sema.validateRuntimeValue(start_block, operand_src, operand);
6581
6582 // We want to generate a `switch_dispatch` instruction with the switch condition,
6583 // possibly preceded by a store to the stack alloc containing the raw operand.
6584 // However, to avoid too much special-case state in Sema, this is handled by the
6585 // `switch` lowering logic. As such, we will find the `Block` corresponding to the
6586 // parent `switch_block[_ref]` instruction, create a dummy `br`, and add a merge
6587 // to signal to the switch logic to rewrite this into an appropriate dispatch.
6588
6589 var block = start_block;
6590 while (true) {
6591 if (block.label) |label| {
6592 if (label.zir_block == switch_inst) {
6593 const br_ref = try start_block.addBr(label.merges.block_inst, operand);
6594 try label.merges.extra_insts.append(sema.gpa, br_ref.toIndex().?);
6595 try label.merges.extra_src_locs.append(sema.gpa, operand_src);
6596 block.runtime_index.increment();
6597 if (block.runtime_cond == null and block.runtime_loop == null) {
6598 block.runtime_cond = start_block.runtime_cond orelse start_block.runtime_loop;
6599 block.runtime_loop = start_block.runtime_loop;
6600 }
6601 return;
6602 }
6603 }
6604 block = block.parent.?;
6605 }
6606}
6607
6534fn zirDbgStmt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {6608fn zirDbgStmt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
6535 if (block.is_comptime or block.ownerModule().strip) return;6609 if (block.is_comptime or block.ownerModule().strip) return;
65366610
...@@ -10940,12 +11014,7 @@ const SwitchProngAnalysis = struct {...@@ -10940,12 +11014,7 @@ const SwitchProngAnalysis = struct {
10940 sema: *Sema,11014 sema: *Sema,
10941 /// The block containing the `switch_block` itself.11015 /// The block containing the `switch_block` itself.
10942 parent_block: *Block,11016 parent_block: *Block,
10943 /// The raw switch operand value (*not* the condition). Always defined.11017 operand: Operand,
10944 operand: Air.Inst.Ref,
10945 /// May be `undefined` if no prong has a by-ref capture.
10946 operand_ptr: Air.Inst.Ref,
10947 /// The switch condition value. For unions, `operand` is the union and `cond` is its tag.
10948 cond: Air.Inst.Ref,
10949 /// If this switch is on an error set, this is the type to assign to the11018 /// If this switch is on an error set, this is the type to assign to the
10950 /// `else` prong. If `null`, the prong should be unreachable.11019 /// `else` prong. If `null`, the prong should be unreachable.
10951 else_error_ty: ?Type,11020 else_error_ty: ?Type,
...@@ -10955,6 +11024,34 @@ const SwitchProngAnalysis = struct {...@@ -10955,6 +11024,34 @@ const SwitchProngAnalysis = struct {
10955 /// undefined if no prong has a tag capture.11024 /// undefined if no prong has a tag capture.
10956 tag_capture_inst: Zir.Inst.Index,11025 tag_capture_inst: Zir.Inst.Index,
1095711026
11027 const Operand = union(enum) {
11028 /// This switch will be dispatched only once, with the given operand.
11029 simple: struct {
11030 /// The raw switch operand value. Always defined.
11031 by_val: Air.Inst.Ref,
11032 /// The switch operand *pointer*. Defined only if there is a prong
11033 /// with a by-ref capture.
11034 by_ref: Air.Inst.Ref,
11035 /// The switch condition value. For unions, `operand` is the union
11036 /// and `cond` is its enum tag value.
11037 cond: Air.Inst.Ref,
11038 },
11039 /// This switch may be dispatched multiple times with `continue` syntax.
11040 /// As such, the operand is stored in an alloc if needed.
11041 loop: struct {
11042 /// The `alloc` containing the `switch` operand for the active dispatch.
11043 /// Each prong must load from this `alloc` to get captures.
11044 /// If there are no captures, this may be undefined.
11045 operand_alloc: Air.Inst.Ref,
11046 /// Whether `operand_alloc` contains a by-val operand or a by-ref
11047 /// operand.
11048 operand_is_ref: bool,
11049 /// The switch condition value for the *initial* dispatch. For
11050 /// unions, this is the enum tag value.
11051 init_cond: Air.Inst.Ref,
11052 },
11053 };
11054
10958 /// Resolve a switch prong which is determined at comptime to have no peers.11055 /// Resolve a switch prong which is determined at comptime to have no peers.
10959 /// Uses `resolveBlockBody`. Sets up captures as needed.11056 /// Uses `resolveBlockBody`. Sets up captures as needed.
10960 fn resolveProngComptime(11057 fn resolveProngComptime(
...@@ -11086,7 +11183,15 @@ const SwitchProngAnalysis = struct {...@@ -11086,7 +11183,15 @@ const SwitchProngAnalysis = struct {
11086 const sema = spa.sema;11183 const sema = spa.sema;
11087 const pt = sema.pt;11184 const pt = sema.pt;
11088 const zcu = pt.zcu;11185 const zcu = pt.zcu;
11089 const operand_ty = sema.typeOf(spa.operand);11186 const operand_ty = switch (spa.operand) {
11187 .simple => |s| sema.typeOf(s.by_val),
11188 .loop => |l| ty: {
11189 const alloc_ty = sema.typeOf(l.operand_alloc);
11190 const alloc_child = alloc_ty.childType(zcu);
11191 if (l.operand_is_ref) break :ty alloc_child.childType(zcu);
11192 break :ty alloc_child;
11193 },
11194 };
11090 if (operand_ty.zigTypeTag(zcu) != .@"union") {11195 if (operand_ty.zigTypeTag(zcu) != .@"union") {
11091 const tag_capture_src: LazySrcLoc = .{11196 const tag_capture_src: LazySrcLoc = .{
11092 .base_node_inst = capture_src.base_node_inst,11197 .base_node_inst = capture_src.base_node_inst,
...@@ -11117,10 +11222,24 @@ const SwitchProngAnalysis = struct {...@@ -11117,10 +11222,24 @@ const SwitchProngAnalysis = struct {
11117 const zir_datas = sema.code.instructions.items(.data);11222 const zir_datas = sema.code.instructions.items(.data);
11118 const switch_node_offset = zir_datas[@intFromEnum(spa.switch_block_inst)].pl_node.src_node;11223 const switch_node_offset = zir_datas[@intFromEnum(spa.switch_block_inst)].pl_node.src_node;
1111911224
11120 const operand_ty = sema.typeOf(spa.operand);
11121 const operand_ptr_ty = if (capture_byref) sema.typeOf(spa.operand_ptr) else undefined;
11122 const operand_src = block.src(.{ .node_offset_switch_operand = switch_node_offset });11225 const operand_src = block.src(.{ .node_offset_switch_operand = switch_node_offset });
1112311226
11227 const operand_val, const operand_ptr = switch (spa.operand) {
11228 .simple => |s| .{ s.by_val, s.by_ref },
11229 .loop => |l| op: {
11230 const loaded = try sema.analyzeLoad(block, operand_src, l.operand_alloc, operand_src);
11231 if (l.operand_is_ref) {
11232 const by_val = try sema.analyzeLoad(block, operand_src, loaded, operand_src);
11233 break :op .{ by_val, loaded };
11234 } else {
11235 break :op .{ loaded, undefined };
11236 }
11237 },
11238 };
11239
11240 const operand_ty = sema.typeOf(operand_val);
11241 const operand_ptr_ty = if (capture_byref) sema.typeOf(operand_ptr) else undefined;
11242
11124 if (inline_case_capture != .none) {11243 if (inline_case_capture != .none) {
11125 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inline_case_capture, undefined) catch unreachable;11244 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inline_case_capture, undefined) catch unreachable;
11126 if (operand_ty.zigTypeTag(zcu) == .@"union") {11245 if (operand_ty.zigTypeTag(zcu) == .@"union") {
...@@ -11136,16 +11255,16 @@ const SwitchProngAnalysis = struct {...@@ -11136,16 +11255,16 @@ const SwitchProngAnalysis = struct {
11136 .address_space = operand_ptr_ty.ptrAddressSpace(zcu),11255 .address_space = operand_ptr_ty.ptrAddressSpace(zcu),
11137 },11256 },
11138 });11257 });
11139 if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |union_ptr| {11258 if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |union_ptr| {
11140 return Air.internedToRef((try union_ptr.ptrField(field_index, pt)).toIntern());11259 return Air.internedToRef((try union_ptr.ptrField(field_index, pt)).toIntern());
11141 }11260 }
11142 return block.addStructFieldPtr(spa.operand_ptr, field_index, ptr_field_ty);11261 return block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);
11143 } else {11262 } else {
11144 if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |union_val| {11263 if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |union_val| {
11145 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;11264 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
11146 return Air.internedToRef(tag_and_val.val);11265 return Air.internedToRef(tag_and_val.val);
11147 }11266 }
11148 return block.addStructFieldVal(spa.operand, field_index, field_ty);11267 return block.addStructFieldVal(operand_val, field_index, field_ty);
11149 }11268 }
11150 } else if (capture_byref) {11269 } else if (capture_byref) {
11151 return sema.uavRef(item_val.toIntern());11270 return sema.uavRef(item_val.toIntern());
...@@ -11156,17 +11275,17 @@ const SwitchProngAnalysis = struct {...@@ -11156,17 +11275,17 @@ const SwitchProngAnalysis = struct {
1115611275
11157 if (is_special_prong) {11276 if (is_special_prong) {
11158 if (capture_byref) {11277 if (capture_byref) {
11159 return spa.operand_ptr;11278 return operand_ptr;
11160 }11279 }
1116111280
11162 switch (operand_ty.zigTypeTag(zcu)) {11281 switch (operand_ty.zigTypeTag(zcu)) {
11163 .error_set => if (spa.else_error_ty) |ty| {11282 .error_set => if (spa.else_error_ty) |ty| {
11164 return sema.bitCast(block, ty, spa.operand, operand_src, null);11283 return sema.bitCast(block, ty, operand_val, operand_src, null);
11165 } else {11284 } else {
11166 try sema.analyzeUnreachable(block, operand_src, false);11285 try sema.analyzeUnreachable(block, operand_src, false);
11167 return .unreachable_value;11286 return .unreachable_value;
11168 },11287 },
11169 else => return spa.operand,11288 else => return operand_val,
11170 }11289 }
11171 }11290 }
1117211291
...@@ -11265,19 +11384,19 @@ const SwitchProngAnalysis = struct {...@@ -11265,19 +11384,19 @@ const SwitchProngAnalysis = struct {
11265 };11384 };
11266 };11385 };
1126711386
11268 if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |op_ptr_val| {11387 if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |op_ptr_val| {
11269 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);11388 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
11270 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);11389 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
11271 return Air.internedToRef((try pt.getCoerced(field_ptr_val, capture_ptr_ty)).toIntern());11390 return Air.internedToRef((try pt.getCoerced(field_ptr_val, capture_ptr_ty)).toIntern());
11272 }11391 }
1127311392
11274 try sema.requireRuntimeBlock(block, operand_src, null);11393 try sema.requireRuntimeBlock(block, operand_src, null);
11275 return block.addStructFieldPtr(spa.operand_ptr, first_field_index, capture_ptr_ty);11394 return block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);
11276 }11395 }
1127711396
11278 if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |operand_val| {11397 if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |operand_val_val| {
11279 if (operand_val.isUndef(zcu)) return pt.undefRef(capture_ty);11398 if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);
11280 const union_val = ip.indexToKey(operand_val.toIntern()).un;11399 const union_val = ip.indexToKey(operand_val_val.toIntern()).un;
11281 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);11400 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
11282 const uncoerced = Air.internedToRef(union_val.val);11401 const uncoerced = Air.internedToRef(union_val.val);
11283 return sema.coerce(block, capture_ty, uncoerced, operand_src);11402 return sema.coerce(block, capture_ty, uncoerced, operand_src);
...@@ -11286,7 +11405,7 @@ const SwitchProngAnalysis = struct {...@@ -11286,7 +11405,7 @@ const SwitchProngAnalysis = struct {
11286 try sema.requireRuntimeBlock(block, operand_src, null);11405 try sema.requireRuntimeBlock(block, operand_src, null);
1128711406
11288 if (same_types) {11407 if (same_types) {
11289 return block.addStructFieldVal(spa.operand, first_field_index, capture_ty);11408 return block.addStructFieldVal(operand_val, first_field_index, capture_ty);
11290 }11409 }
1129111410
11292 // We may have to emit a switch block which coerces the operand to the capture type.11411 // We may have to emit a switch block which coerces the operand to the capture type.
...@@ -11300,7 +11419,7 @@ const SwitchProngAnalysis = struct {...@@ -11300,7 +11419,7 @@ const SwitchProngAnalysis = struct {
11300 }11419 }
11301 // All fields are in-memory coercible to the resolved type!11420 // All fields are in-memory coercible to the resolved type!
11302 // Just take the first field and bitcast the result.11421 // Just take the first field and bitcast the result.
11303 const uncoerced = try block.addStructFieldVal(spa.operand, first_field_index, first_field_ty);11422 const uncoerced = try block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
11304 return block.addBitCast(capture_ty, uncoerced);11423 return block.addBitCast(capture_ty, uncoerced);
11305 };11424 };
1130611425
...@@ -11364,7 +11483,7 @@ const SwitchProngAnalysis = struct {...@@ -11364,7 +11483,7 @@ const SwitchProngAnalysis = struct {
1136411483
11365 const field_idx = field_indices[idx];11484 const field_idx = field_indices[idx];
11366 const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_idx]);11485 const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_idx]);
11367 const uncoerced = try coerce_block.addStructFieldVal(spa.operand, field_idx, field_ty);11486 const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);
11368 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);11487 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
11369 _ = try coerce_block.addBr(capture_block_inst, coerced);11488 _ = try coerce_block.addBr(capture_block_inst, coerced);
1137011489
...@@ -11388,7 +11507,7 @@ const SwitchProngAnalysis = struct {...@@ -11388,7 +11507,7 @@ const SwitchProngAnalysis = struct {
11388 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;11507 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
11389 const first_imc_field_idx = field_indices[first_imc_item_idx];11508 const first_imc_field_idx = field_indices[first_imc_item_idx];
11390 const first_imc_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);11509 const first_imc_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
11391 const uncoerced = try coerce_block.addStructFieldVal(spa.operand, first_imc_field_idx, first_imc_field_ty);11510 const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
11392 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);11511 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
11393 _ = try coerce_block.addBr(capture_block_inst, coerced);11512 _ = try coerce_block.addBr(capture_block_inst, coerced);
1139411513
...@@ -11404,21 +11523,47 @@ const SwitchProngAnalysis = struct {...@@ -11404,21 +11523,47 @@ const SwitchProngAnalysis = struct {
11404 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);11523 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
11405 try sema.air_instructions.append(sema.gpa, .{11524 try sema.air_instructions.append(sema.gpa, .{
11406 .tag = .switch_br,11525 .tag = .switch_br,
11407 .data = .{ .pl_op = .{11526 .data = .{
11408 .operand = spa.cond,11527 .pl_op = .{
11409 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{11528 .operand = undefined, // set by switch below
11410 .cases_len = @intCast(prong_count),11529 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
11411 .else_body_len = @intCast(else_body_len),11530 .cases_len = @intCast(prong_count),
11412 }),11531 .else_body_len = @intCast(else_body_len),
11413 } },11532 }),
11533 },
11534 },
11414 });11535 });
11415 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);11536 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
1141611537
11417 // Set up block body11538 // Set up block body
11418 sema.air_instructions.items(.data)[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{11539 switch (spa.operand) {
11419 .body_len = 1,11540 .simple => |s| {
11420 });11541 const air_datas = sema.air_instructions.items(.data);
11421 sema.air_extra.appendAssumeCapacity(switch_br_inst);11542 air_datas[switch_br_inst].pl_op.operand = s.cond;
11543 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
11544 .body_len = 1,
11545 });
11546 sema.air_extra.appendAssumeCapacity(switch_br_inst);
11547 },
11548 .loop => {
11549 // The block must first extract the tag from the loaded union.
11550 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
11551 try sema.air_instructions.append(sema.gpa, .{
11552 .tag = .get_union_tag,
11553 .data = .{ .ty_op = .{
11554 .ty = Air.internedToRef(union_obj.enum_tag_ty),
11555 .operand = operand_val,
11556 } },
11557 });
11558 const air_datas = sema.air_instructions.items(.data);
11559 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
11560 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
11561 .body_len = 2,
11562 });
11563 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
11564 sema.air_extra.appendAssumeCapacity(switch_br_inst);
11565 },
11566 }
1142211567
11423 return capture_block_inst.toRef();11568 return capture_block_inst.toRef();
11424 },11569 },
...@@ -11435,7 +11580,7 @@ const SwitchProngAnalysis = struct {...@@ -11435,7 +11580,7 @@ const SwitchProngAnalysis = struct {
11435 if (case_vals.len == 1) {11580 if (case_vals.len == 1) {
11436 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable;11581 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable;
11437 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);11582 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
11438 return sema.bitCast(block, item_ty, spa.operand, operand_src, null);11583 return sema.bitCast(block, item_ty, operand_val, operand_src, null);
11439 }11584 }
1144011585
11441 var names: InferredErrorSet.NameMap = .{};11586 var names: InferredErrorSet.NameMap = .{};
...@@ -11445,15 +11590,15 @@ const SwitchProngAnalysis = struct {...@@ -11445,15 +11590,15 @@ const SwitchProngAnalysis = struct {
11445 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});11590 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
11446 }11591 }
11447 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());11592 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
11448 return sema.bitCast(block, error_ty, spa.operand, operand_src, null);11593 return sema.bitCast(block, error_ty, operand_val, operand_src, null);
11449 },11594 },
11450 else => {11595 else => {
11451 // In this case the capture value is just the passed-through value11596 // In this case the capture value is just the passed-through value
11452 // of the switch condition.11597 // of the switch condition.
11453 if (capture_byref) {11598 if (capture_byref) {
11454 return spa.operand_ptr;11599 return operand_ptr;
11455 } else {11600 } else {
11456 return spa.operand;11601 return operand_val;
11457 }11602 }
11458 },11603 },
11459 }11604 }
...@@ -11686,9 +11831,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp...@@ -11686,9 +11831,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
11686 var spa: SwitchProngAnalysis = .{11831 var spa: SwitchProngAnalysis = .{
11687 .sema = sema,11832 .sema = sema,
11688 .parent_block = block,11833 .parent_block = block,
11689 .operand = undefined, // must be set to the unwrapped error code before use11834 .operand = .{
11690 .operand_ptr = .none,11835 .simple = .{
11691 .cond = raw_operand_val,11836 .by_val = undefined, // must be set to the unwrapped error code before use
11837 .by_ref = undefined,
11838 .cond = raw_operand_val,
11839 },
11840 },
11692 .else_error_ty = else_error_ty,11841 .else_error_ty = else_error_ty,
11693 .switch_block_inst = inst,11842 .switch_block_inst = inst,
11694 .tag_capture_inst = undefined,11843 .tag_capture_inst = undefined,
...@@ -11709,13 +11858,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp...@@ -11709,13 +11858,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
11709 .name = operand_val.getErrorName(zcu).unwrap().?,11858 .name = operand_val.getErrorName(zcu).unwrap().?,
11710 },11859 },
11711 }));11860 }));
11712 spa.operand = if (extra.data.bits.payload_is_ref)11861 spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref)
11713 try sema.analyzeErrUnionCodePtr(block, switch_operand_src, raw_operand_val)11862 try sema.analyzeErrUnionCodePtr(block, switch_operand_src, raw_operand_val)
11714 else11863 else
11715 try sema.analyzeErrUnionCode(block, switch_operand_src, raw_operand_val);11864 try sema.analyzeErrUnionCode(block, switch_operand_src, raw_operand_val);
1171611865
11717 if (extra.data.bits.any_uses_err_capture) {11866 if (extra.data.bits.any_uses_err_capture) {
11718 sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand);11867 sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val);
11719 }11868 }
11720 defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));11869 defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));
1172111870
...@@ -11723,7 +11872,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp...@@ -11723,7 +11872,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
11723 sema,11872 sema,
11724 spa,11873 spa,
11725 &child_block,11874 &child_block,
11726 try sema.switchCond(block, switch_operand_src, spa.operand),11875 try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val),
11727 err_val,11876 err_val,
11728 operand_err_set_ty,11877 operand_err_set_ty,
11729 switch_src_node_offset,11878 switch_src_node_offset,
...@@ -11777,20 +11926,20 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp...@@ -11777,20 +11926,20 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
11777 const true_instructions = try sub_block.instructions.toOwnedSlice(gpa);11926 const true_instructions = try sub_block.instructions.toOwnedSlice(gpa);
11778 defer gpa.free(true_instructions);11927 defer gpa.free(true_instructions);
1177911928
11780 spa.operand = if (extra.data.bits.payload_is_ref)11929 spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref)
11781 try sema.analyzeErrUnionCodePtr(&sub_block, switch_operand_src, raw_operand_val)11930 try sema.analyzeErrUnionCodePtr(&sub_block, switch_operand_src, raw_operand_val)
11782 else11931 else
11783 try sema.analyzeErrUnionCode(&sub_block, switch_operand_src, raw_operand_val);11932 try sema.analyzeErrUnionCode(&sub_block, switch_operand_src, raw_operand_val);
1178411933
11785 if (extra.data.bits.any_uses_err_capture) {11934 if (extra.data.bits.any_uses_err_capture) {
11786 sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand);11935 sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val);
11787 }11936 }
11788 defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));11937 defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));
11789 _ = try sema.analyzeSwitchRuntimeBlock(11938 _ = try sema.analyzeSwitchRuntimeBlock(
11790 spa,11939 spa,
11791 &sub_block,11940 &sub_block,
11792 switch_src,11941 switch_src,
11793 try sema.switchCond(block, switch_operand_src, spa.operand),11942 try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val),
11794 operand_err_set_ty,11943 operand_err_set_ty,
11795 switch_operand_src,11944 switch_operand_src,
11796 case_vals,11945 case_vals,
...@@ -11859,17 +12008,63 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11859,17 +12008,63 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11859 const special_prong_src = block.src(.{ .node_offset_switch_special_prong = src_node_offset });12008 const special_prong_src = block.src(.{ .node_offset_switch_special_prong = src_node_offset });
11860 const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);12009 const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
1186112010
11862 const raw_operand_val: Air.Inst.Ref, const raw_operand_ptr: Air.Inst.Ref = blk: {12011 const operand: SwitchProngAnalysis.Operand, const raw_operand_ty: Type = op: {
11863 const maybe_ptr = try sema.resolveInst(extra.data.operand);12012 const maybe_ptr = try sema.resolveInst(extra.data.operand);
11864 if (operand_is_ref) {12013 const val, const ref = if (operand_is_ref)
11865 const val = try sema.analyzeLoad(block, src, maybe_ptr, operand_src);12014 .{ try sema.analyzeLoad(block, src, maybe_ptr, operand_src), maybe_ptr }
11866 break :blk .{ val, maybe_ptr };12015 else
11867 } else {12016 .{ maybe_ptr, undefined };
11868 break :blk .{ maybe_ptr, undefined };12017
12018 const init_cond = try sema.switchCond(block, operand_src, val);
12019
12020 const operand_ty = sema.typeOf(val);
12021
12022 if (extra.data.bits.has_continue and !block.is_comptime) {
12023 // Even if the operand is comptime-known, this `switch` is runtime.
12024 if (try operand_ty.comptimeOnlySema(pt)) {
12025 return sema.failWithOwnedErrorMsg(block, msg: {
12026 const msg = try sema.errMsg(operand_src, "operand of switch loop has comptime-only type '{}'", .{operand_ty.fmt(pt)});
12027 errdefer msg.destroy(gpa);
12028 try sema.errNote(operand_src, msg, "switch loops are evalauted at runtime outside of comptime scopes", .{});
12029 break :msg msg;
12030 });
12031 }
12032 try sema.validateRuntimeValue(block, operand_src, maybe_ptr);
12033 const operand_alloc = if (extra.data.bits.any_non_inline_capture) a: {
12034 const operand_ptr_ty = try pt.singleMutPtrType(sema.typeOf(maybe_ptr));
12035 const operand_alloc = try block.addTy(.alloc, operand_ptr_ty);
12036 _ = try block.addBinOp(.store, operand_alloc, maybe_ptr);
12037 break :a operand_alloc;
12038 } else undefined;
12039 break :op .{
12040 .{ .loop = .{
12041 .operand_alloc = operand_alloc,
12042 .operand_is_ref = operand_is_ref,
12043 .init_cond = init_cond,
12044 } },
12045 operand_ty,
12046 };
11869 }12047 }
12048
12049 // We always use `simple` in the comptime case, because as far as the dispatching logic
12050 // is concerned, it really is dispatching a single prong. `resolveSwitchComptime` will
12051 // be resposible for recursively resolving different prongs as needed.
12052 break :op .{
12053 .{ .simple = .{
12054 .by_val = val,
12055 .by_ref = ref,
12056 .cond = init_cond,
12057 } },
12058 operand_ty,
12059 };
11870 };12060 };
1187112061
11872 const operand = try sema.switchCond(block, operand_src, raw_operand_val);12062 const union_originally = raw_operand_ty.zigTypeTag(zcu) == .@"union";
12063 const err_set = raw_operand_ty.zigTypeTag(zcu) == .error_set;
12064 const cond_ty = switch (raw_operand_ty.zigTypeTag(zcu)) {
12065 .@"union" => raw_operand_ty.unionTagType(zcu).?, // validated by `switchCond` above
12066 else => raw_operand_ty,
12067 };
1187312068
11874 // AstGen guarantees that the instruction immediately preceding12069 // AstGen guarantees that the instruction immediately preceding
11875 // switch_block(_ref) is a dbg_stmt12070 // switch_block(_ref) is a dbg_stmt
...@@ -11919,9 +12114,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11919,9 +12114,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11919 },12114 },
11920 };12115 };
1192112116
11922 const maybe_union_ty = sema.typeOf(raw_operand_val);
11923 const union_originally = maybe_union_ty.zigTypeTag(zcu) == .@"union";
11924
11925 // Duplicate checking variables later also used for `inline else`.12117 // Duplicate checking variables later also used for `inline else`.
11926 var seen_enum_fields: []?LazySrcLoc = &.{};12118 var seen_enum_fields: []?LazySrcLoc = &.{};
11927 var seen_errors = SwitchErrorSet.init(gpa);12119 var seen_errors = SwitchErrorSet.init(gpa);
...@@ -11937,13 +12129,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11937,13 +12129,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
1193712129
11938 var empty_enum = false;12130 var empty_enum = false;
1193912131
11940 const operand_ty = sema.typeOf(operand);
11941 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
11942
11943 var else_error_ty: ?Type = null;12132 var else_error_ty: ?Type = null;
1194412133
11945 // Validate usage of '_' prongs.12134 // Validate usage of '_' prongs.
11946 if (special_prong == .under and (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally)) {12135 if (special_prong == .under and !raw_operand_ty.isNonexhaustiveEnum(zcu)) {
11947 const msg = msg: {12136 const msg = msg: {
11948 const msg = try sema.errMsg(12137 const msg = try sema.errMsg(
11949 src,12138 src,
...@@ -11969,11 +12158,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11969,11 +12158,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11969 }12158 }
1197012159
11971 // Validate for duplicate items, missing else prong, and invalid range.12160 // Validate for duplicate items, missing else prong, and invalid range.
11972 switch (operand_ty.zigTypeTag(zcu)) {12161 switch (cond_ty.zigTypeTag(zcu)) {
11973 .@"union" => unreachable, // handled in `switchCond`12162 .@"union" => unreachable, // handled in `switchCond`
11974 .@"enum" => {12163 .@"enum" => {
11975 seen_enum_fields = try gpa.alloc(?LazySrcLoc, operand_ty.enumFieldCount(zcu));12164 seen_enum_fields = try gpa.alloc(?LazySrcLoc, cond_ty.enumFieldCount(zcu));
11976 empty_enum = seen_enum_fields.len == 0 and !operand_ty.isNonexhaustiveEnum(zcu);12165 empty_enum = seen_enum_fields.len == 0 and !cond_ty.isNonexhaustiveEnum(zcu);
11977 @memset(seen_enum_fields, null);12166 @memset(seen_enum_fields, null);
11978 // `range_set` is used for non-exhaustive enum values that do not correspond to any tags.12167 // `range_set` is used for non-exhaustive enum values that do not correspond to any tags.
1197912168
...@@ -11991,7 +12180,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11991,7 +12180,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11991 seen_enum_fields,12180 seen_enum_fields,
11992 &range_set,12181 &range_set,
11993 item_ref,12182 item_ref,
11994 operand_ty,12183 cond_ty,
11995 block.src(.{ .switch_case_item = .{12184 block.src(.{ .switch_case_item = .{
11996 .switch_node_offset = src_node_offset,12185 .switch_node_offset = src_node_offset,
11997 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },12186 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
...@@ -12019,7 +12208,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12019,7 +12208,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12019 seen_enum_fields,12208 seen_enum_fields,
12020 &range_set,12209 &range_set,
12021 item_ref,12210 item_ref,
12022 operand_ty,12211 cond_ty,
12023 block.src(.{ .switch_case_item = .{12212 block.src(.{ .switch_case_item = .{
12024 .switch_node_offset = src_node_offset,12213 .switch_node_offset = src_node_offset,
12025 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },12214 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
...@@ -12028,7 +12217,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12028,7 +12217,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12028 ));12217 ));
12029 }12218 }
1203012219
12031 try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);12220 try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
12032 }12221 }
12033 }12222 }
12034 const all_tags_handled = for (seen_enum_fields) |seen_src| {12223 const all_tags_handled = for (seen_enum_fields) |seen_src| {
...@@ -12036,7 +12225,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12036,7 +12225,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12036 } else true;12225 } else true;
1203712226
12038 if (special_prong == .@"else") {12227 if (special_prong == .@"else") {
12039 if (all_tags_handled and !operand_ty.isNonexhaustiveEnum(zcu)) return sema.fail(12228 if (all_tags_handled and !cond_ty.isNonexhaustiveEnum(zcu)) return sema.fail(
12040 block,12229 block,
12041 special_prong_src,12230 special_prong_src,
12042 "unreachable else prong; all cases already handled",12231 "unreachable else prong; all cases already handled",
...@@ -12053,9 +12242,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12053,9 +12242,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12053 for (seen_enum_fields, 0..) |seen_src, i| {12242 for (seen_enum_fields, 0..) |seen_src, i| {
12054 if (seen_src != null) continue;12243 if (seen_src != null) continue;
1205512244
12056 const field_name = operand_ty.enumFieldName(i, zcu);12245 const field_name = cond_ty.enumFieldName(i, zcu);
12057 try sema.addFieldErrNote(12246 try sema.addFieldErrNote(
12058 operand_ty,12247 cond_ty,
12059 i,12248 i,
12060 msg,12249 msg,
12061 "unhandled enumeration value: '{}'",12250 "unhandled enumeration value: '{}'",
...@@ -12063,15 +12252,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12063,15 +12252,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12063 );12252 );
12064 }12253 }
12065 try sema.errNote(12254 try sema.errNote(
12066 operand_ty.srcLoc(zcu),12255 cond_ty.srcLoc(zcu),
12067 msg,12256 msg,
12068 "enum '{}' declared here",12257 "enum '{}' declared here",
12069 .{operand_ty.fmt(pt)},12258 .{cond_ty.fmt(pt)},
12070 );12259 );
12071 break :msg msg;12260 break :msg msg;
12072 };12261 };
12073 return sema.failWithOwnedErrorMsg(block, msg);12262 return sema.failWithOwnedErrorMsg(block, msg);
12074 } else if (special_prong == .none and operand_ty.isNonexhaustiveEnum(zcu) and !union_originally) {12263 } else if (special_prong == .none and cond_ty.isNonexhaustiveEnum(zcu) and !union_originally) {
12075 return sema.fail(12264 return sema.fail(
12076 block,12265 block,
12077 src,12266 src,
...@@ -12085,7 +12274,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12085,7 +12274,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12085 block,12274 block,
12086 &seen_errors,12275 &seen_errors,
12087 &case_vals,12276 &case_vals,
12088 operand_ty,12277 cond_ty,
12089 inst_data,12278 inst_data,
12090 scalar_cases_len,12279 scalar_cases_len,
12091 multi_cases_len,12280 multi_cases_len,
...@@ -12106,7 +12295,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12106,7 +12295,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12106 block,12295 block,
12107 &range_set,12296 &range_set,
12108 item_ref,12297 item_ref,
12109 operand_ty,12298 cond_ty,
12110 block.src(.{ .switch_case_item = .{12299 block.src(.{ .switch_case_item = .{
12111 .switch_node_offset = src_node_offset,12300 .switch_node_offset = src_node_offset,
12112 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },12301 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
...@@ -12133,7 +12322,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12133,7 +12322,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12133 block,12322 block,
12134 &range_set,12323 &range_set,
12135 item_ref,12324 item_ref,
12136 operand_ty,12325 cond_ty,
12137 block.src(.{ .switch_case_item = .{12326 block.src(.{ .switch_case_item = .{
12138 .switch_node_offset = src_node_offset,12327 .switch_node_offset = src_node_offset,
12139 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },12328 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
...@@ -12155,7 +12344,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12155,7 +12344,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12155 &range_set,12344 &range_set,
12156 item_first,12345 item_first,
12157 item_last,12346 item_last,
12158 operand_ty,12347 cond_ty,
12159 block.src(.{ .switch_case_item = .{12348 block.src(.{ .switch_case_item = .{
12160 .switch_node_offset = src_node_offset,12349 .switch_node_offset = src_node_offset,
12161 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },12350 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
...@@ -12171,9 +12360,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12171,9 +12360,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12171 }12360 }
1217212361
12173 check_range: {12362 check_range: {
12174 if (operand_ty.zigTypeTag(zcu) == .int) {12363 if (cond_ty.zigTypeTag(zcu) == .int) {
12175 const min_int = try operand_ty.minInt(pt, operand_ty);12364 const min_int = try cond_ty.minInt(pt, cond_ty);
12176 const max_int = try operand_ty.maxInt(pt, operand_ty);12365 const max_int = try cond_ty.maxInt(pt, cond_ty);
12177 if (try range_set.spans(min_int.toIntern(), max_int.toIntern())) {12366 if (try range_set.spans(min_int.toIntern(), max_int.toIntern())) {
12178 if (special_prong == .@"else") {12367 if (special_prong == .@"else") {
12179 return sema.fail(12368 return sema.fail(
...@@ -12246,7 +12435,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12246,7 +12435,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12246 ));12435 ));
12247 }12436 }
1224812437
12249 try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);12438 try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
12250 }12439 }
12251 }12440 }
12252 switch (special_prong) {12441 switch (special_prong) {
...@@ -12278,7 +12467,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12278,7 +12467,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12278 block,12467 block,
12279 src,12468 src,
12280 "else prong required when switching on type '{}'",12469 "else prong required when switching on type '{}'",
12281 .{operand_ty.fmt(pt)},12470 .{cond_ty.fmt(pt)},
12282 );12471 );
12283 }12472 }
1228412473
...@@ -12299,7 +12488,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12299,7 +12488,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12299 block,12488 block,
12300 &seen_values,12489 &seen_values,
12301 item_ref,12490 item_ref,
12302 operand_ty,12491 cond_ty,
12303 block.src(.{ .switch_case_item = .{12492 block.src(.{ .switch_case_item = .{
12304 .switch_node_offset = src_node_offset,12493 .switch_node_offset = src_node_offset,
12305 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },12494 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
...@@ -12326,7 +12515,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12326,7 +12515,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12326 block,12515 block,
12327 &seen_values,12516 &seen_values,
12328 item_ref,12517 item_ref,
12329 operand_ty,12518 cond_ty,
12330 block.src(.{ .switch_case_item = .{12519 block.src(.{ .switch_case_item = .{
12331 .switch_node_offset = src_node_offset,12520 .switch_node_offset = src_node_offset,
12332 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },12521 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
...@@ -12335,7 +12524,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12335,7 +12524,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12335 ));12524 ));
12336 }12525 }
1233712526
12338 try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);12527 try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
12339 }12528 }
12340 }12529 }
12341 },12530 },
...@@ -12354,16 +12543,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12354,16 +12543,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12354 .comptime_float,12543 .comptime_float,
12355 .float,12544 .float,
12356 => return sema.fail(block, operand_src, "invalid switch operand type '{}'", .{12545 => return sema.fail(block, operand_src, "invalid switch operand type '{}'", .{
12357 operand_ty.fmt(pt),12546 raw_operand_ty.fmt(pt),
12358 }),12547 }),
12359 }12548 }
1236012549
12361 const spa: SwitchProngAnalysis = .{12550 const spa: SwitchProngAnalysis = .{
12362 .sema = sema,12551 .sema = sema,
12363 .parent_block = block,12552 .parent_block = block,
12364 .operand = raw_operand_val,12553 .operand = operand,
12365 .operand_ptr = raw_operand_ptr,
12366 .cond = operand,
12367 .else_error_ty = else_error_ty,12554 .else_error_ty = else_error_ty,
12368 .switch_block_inst = inst,12555 .switch_block_inst = inst,
12369 .tag_capture_inst = tag_capture_inst,12556 .tag_capture_inst = tag_capture_inst,
...@@ -12407,24 +12594,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12407,24 +12594,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12407 defer child_block.instructions.deinit(gpa);12594 defer child_block.instructions.deinit(gpa);
12408 defer merges.deinit(gpa);12595 defer merges.deinit(gpa);
1240912596
12410 if (try sema.resolveDefinedValue(&child_block, src, operand)) |operand_val| {
12411 return resolveSwitchComptime(
12412 sema,
12413 spa,
12414 &child_block,
12415 operand,
12416 operand_val,
12417 operand_ty,
12418 src_node_offset,
12419 special,
12420 case_vals,
12421 scalar_cases_len,
12422 multi_cases_len,
12423 err_set,
12424 empty_enum,
12425 );
12426 }
12427
12428 if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline) {12597 if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline) {
12429 if (empty_enum) {12598 if (empty_enum) {
12430 return .void_value;12599 return .void_value;
...@@ -12432,54 +12601,90 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12432,54 +12601,90 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12432 if (special_prong == .none) {12601 if (special_prong == .none) {
12433 return sema.fail(block, src, "switch must handle all possibilities", .{});12602 return sema.fail(block, src, "switch must handle all possibilities", .{});
12434 }12603 }
12435 if (err_set and try sema.maybeErrorUnwrap(block, special.body, operand, operand_src, false)) {12604 const init_cond = switch (operand) {
12436 return .unreachable_value;12605 .simple => |s| s.cond,
12437 }12606 .loop => |l| l.init_cond,
12438 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and operand_ty.zigTypeTag(zcu) == .@"enum" and12607 };
12439 (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally))12608 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
12609 raw_operand_ty.zigTypeTag(zcu) == .@"enum" and !raw_operand_ty.isNonexhaustiveEnum(zcu))
12440 {12610 {
12441 try sema.zirDbgStmt(block, cond_dbg_node_index);12611 try sema.zirDbgStmt(block, cond_dbg_node_index);
12442 const ok = try block.addUnOp(.is_named_enum_value, operand);12612 const ok = try block.addUnOp(.is_named_enum_value, init_cond);
12443 try sema.addSafetyCheck(block, src, ok, .corrupt_switch);12613 try sema.addSafetyCheck(block, src, ok, .corrupt_switch);
12444 }12614 }
12615 if (err_set and try sema.maybeErrorUnwrap(block, special.body, init_cond, operand_src, false)) {
12616 return .unreachable_value;
12617 }
12618 }
1244512619
12446 return spa.resolveProngComptime(12620 switch (operand) {
12447 &child_block,12621 .loop => {}, // always runtime; evaluation in comptime scope uses `simple`
12448 .special,12622 .simple => |s| {
12449 special.body,12623 if (try sema.resolveDefinedValue(&child_block, src, s.cond)) |cond_val| {
12450 special.capture,12624 return resolveSwitchComptimeLoop(
12451 block.src(.{ .switch_capture = .{12625 sema,
12452 .switch_node_offset = src_node_offset,12626 spa,
12453 .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,12627 &child_block,
12454 } }),12628 if (operand_is_ref)
12455 undefined, // case_vals may be undefined for special prongs12629 sema.typeOf(s.by_ref)
12456 .none,12630 else
12457 false,12631 raw_operand_ty,
12458 merges,12632 cond_ty,
12459 );12633 cond_val,
12634 src_node_offset,
12635 special,
12636 case_vals,
12637 scalar_cases_len,
12638 multi_cases_len,
12639 err_set,
12640 empty_enum,
12641 operand_is_ref,
12642 );
12643 }
12644
12645 if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline and !extra.data.bits.has_continue) {
12646 return spa.resolveProngComptime(
12647 &child_block,
12648 .special,
12649 special.body,
12650 special.capture,
12651 block.src(.{ .switch_capture = .{
12652 .switch_node_offset = src_node_offset,
12653 .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,
12654 } }),
12655 undefined, // case_vals may be undefined for special prongs
12656 .none,
12657 false,
12658 merges,
12659 );
12660 }
12661 },
12460 }12662 }
1246112663
12462 if (child_block.is_comptime) {12664 if (child_block.is_comptime) {
12463 _ = try sema.resolveConstDefinedValue(&child_block, operand_src, operand, .{12665 _ = try sema.resolveConstDefinedValue(&child_block, operand_src, operand.simple.cond, .{
12464 .needed_comptime_reason = "condition in comptime switch must be comptime-known",12666 .needed_comptime_reason = "condition in comptime switch must be comptime-known",
12465 .block_comptime_reason = child_block.comptime_reason,12667 .block_comptime_reason = child_block.comptime_reason,
12466 });12668 });
12467 unreachable;12669 unreachable;
12468 }12670 }
1246912671
12470 _ = try sema.analyzeSwitchRuntimeBlock(12672 const air_switch_ref = try sema.analyzeSwitchRuntimeBlock(
12471 spa,12673 spa,
12472 &child_block,12674 &child_block,
12473 src,12675 src,
12474 operand,12676 switch (operand) {
12475 operand_ty,12677 .simple => |s| s.cond,
12678 .loop => |l| l.init_cond,
12679 },
12680 cond_ty,
12476 operand_src,12681 operand_src,
12477 case_vals,12682 case_vals,
12478 special,12683 special,
12479 scalar_cases_len,12684 scalar_cases_len,
12480 multi_cases_len,12685 multi_cases_len,
12481 union_originally,12686 union_originally,
12482 maybe_union_ty,12687 raw_operand_ty,
12483 err_set,12688 err_set,
12484 src_node_offset,12689 src_node_offset,
12485 special_prong_src,12690 special_prong_src,
...@@ -12492,6 +12697,67 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12492,6 +12697,67 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12492 false,12697 false,
12493 );12698 );
1249412699
12700 for (merges.extra_insts.items, merges.extra_src_locs.items) |placeholder_inst, dispatch_src| {
12701 var replacement_block = block.makeSubBlock();
12702 defer replacement_block.instructions.deinit(gpa);
12703
12704 assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .br);
12705 const new_operand_maybe_ref = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].br.operand;
12706
12707 if (extra.data.bits.any_non_inline_capture) {
12708 _ = try replacement_block.addBinOp(.store, operand.loop.operand_alloc, new_operand_maybe_ref);
12709 }
12710
12711 const new_operand_val = if (operand_is_ref)
12712 try sema.analyzeLoad(&replacement_block, dispatch_src, new_operand_maybe_ref, dispatch_src)
12713 else
12714 new_operand_maybe_ref;
12715
12716 const new_cond = try sema.switchCond(&replacement_block, dispatch_src, new_operand_val);
12717
12718 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
12719 cond_ty.zigTypeTag(zcu) == .@"enum" and !cond_ty.isNonexhaustiveEnum(zcu) and
12720 !try sema.isComptimeKnown(new_cond))
12721 {
12722 const ok = try replacement_block.addUnOp(.is_named_enum_value, new_cond);
12723 try sema.addSafetyCheck(&replacement_block, src, ok, .corrupt_switch);
12724 }
12725
12726 _ = try replacement_block.addInst(.{
12727 .tag = .switch_dispatch,
12728 .data = .{ .br = .{
12729 .block_inst = air_switch_ref.toIndex().?,
12730 .operand = new_cond,
12731 } },
12732 });
12733
12734 if (replacement_block.instructions.items.len == 1) {
12735 // Optimization: we don't need a block!
12736 sema.air_instructions.set(
12737 @intFromEnum(placeholder_inst),
12738 sema.air_instructions.get(@intFromEnum(replacement_block.instructions.items[0])),
12739 );
12740 continue;
12741 }
12742
12743 // Replace placeholder with a block.
12744 // No `br` is needed as the block is a switch dispatch so necessarily `noreturn`.
12745 try sema.air_extra.ensureUnusedCapacity(
12746 gpa,
12747 @typeInfo(Air.Block).@"struct".fields.len + replacement_block.instructions.items.len,
12748 );
12749 sema.air_instructions.set(@intFromEnum(placeholder_inst), .{
12750 .tag = .block,
12751 .data = .{ .ty_pl = .{
12752 .ty = .noreturn_type,
12753 .payload = sema.addExtraAssumeCapacity(Air.Block{
12754 .body_len = @intCast(replacement_block.instructions.items.len),
12755 }),
12756 } },
12757 });
12758 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(replacement_block.instructions.items));
12759 }
12760
12495 return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false);12761 return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false);
12496}12762}
1249712763
...@@ -13123,7 +13389,7 @@ fn analyzeSwitchRuntimeBlock(...@@ -13123,7 +13389,7 @@ fn analyzeSwitchRuntimeBlock(
13123 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_body));13389 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_body));
1312413390
13125 return try child_block.addInst(.{13391 return try child_block.addInst(.{
13126 .tag = .switch_br,13392 .tag = if (spa.operand == .loop) .loop_switch_br else .switch_br,
13127 .data = .{ .pl_op = .{13393 .data = .{ .pl_op = .{
13128 .operand = operand,13394 .operand = operand,
13129 .payload = payload_index,13395 .payload = payload_index,
...@@ -13131,6 +13397,77 @@ fn analyzeSwitchRuntimeBlock(...@@ -13131,6 +13397,77 @@ fn analyzeSwitchRuntimeBlock(
13131 });13397 });
13132}13398}
1313313399
13400fn resolveSwitchComptimeLoop(
13401 sema: *Sema,
13402 init_spa: SwitchProngAnalysis,
13403 child_block: *Block,
13404 maybe_ptr_operand_ty: Type,
13405 cond_ty: Type,
13406 init_cond_val: Value,
13407 switch_node_offset: i32,
13408 special: SpecialProng,
13409 case_vals: std.ArrayListUnmanaged(Air.Inst.Ref),
13410 scalar_cases_len: u32,
13411 multi_cases_len: u32,
13412 err_set: bool,
13413 empty_enum: bool,
13414 operand_is_ref: bool,
13415) CompileError!Air.Inst.Ref {
13416 var spa = init_spa;
13417 var cond_val = init_cond_val;
13418
13419 while (true) {
13420 if (resolveSwitchComptime(
13421 sema,
13422 spa,
13423 child_block,
13424 spa.operand.simple.cond,
13425 cond_val,
13426 cond_ty,
13427 switch_node_offset,
13428 special,
13429 case_vals,
13430 scalar_cases_len,
13431 multi_cases_len,
13432 err_set,
13433 empty_enum,
13434 )) |result| {
13435 return result;
13436 } else |err| switch (err) {
13437 error.ComptimeBreak => {
13438 const break_inst = sema.code.instructions.get(@intFromEnum(sema.comptime_break_inst));
13439 if (break_inst.tag != .switch_continue) return error.ComptimeBreak;
13440 const extra = sema.code.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data;
13441 if (extra.block_inst != spa.switch_block_inst) return error.ComptimeBreak;
13442 // This is a `switch_continue` targeting this block. Change the operand and start over.
13443 const src = child_block.nodeOffset(extra.operand_src_node);
13444 const new_operand_uncoerced = try sema.resolveInst(break_inst.data.@"break".operand);
13445 const new_operand = try sema.coerce(child_block, maybe_ptr_operand_ty, new_operand_uncoerced, src);
13446
13447 try sema.emitBackwardBranch(child_block, src);
13448
13449 const val, const ref = if (operand_is_ref)
13450 .{ try sema.analyzeLoad(child_block, src, new_operand, src), new_operand }
13451 else
13452 .{ new_operand, undefined };
13453
13454 const cond_ref = try sema.switchCond(child_block, src, val);
13455
13456 cond_val = try sema.resolveConstDefinedValue(child_block, src, cond_ref, .{
13457 .needed_comptime_reason = "condition in comptime switch must be comptime-known",
13458 .block_comptime_reason = child_block.comptime_reason,
13459 });
13460 spa.operand = .{ .simple = .{
13461 .by_val = val,
13462 .by_ref = ref,
13463 .cond = cond_ref,
13464 } };
13465 },
13466 else => |e| return e,
13467 }
13468 }
13469}
13470
13134fn resolveSwitchComptime(13471fn resolveSwitchComptime(
13135 sema: *Sema,13472 sema: *Sema,
13136 spa: SwitchProngAnalysis,13473 spa: SwitchProngAnalysis,
...@@ -13148,6 +13485,7 @@ fn resolveSwitchComptime(...@@ -13148,6 +13485,7 @@ fn resolveSwitchComptime(
13148) CompileError!Air.Inst.Ref {13485) CompileError!Air.Inst.Ref {
13149 const merges = &child_block.label.?.merges;13486 const merges = &child_block.label.?.merges;
13150 const resolved_operand_val = try sema.resolveLazyValue(operand_val);13487 const resolved_operand_val = try sema.resolveLazyValue(operand_val);
13488
13151 var extra_index: usize = special.end;13489 var extra_index: usize = special.end;
13152 {13490 {
13153 var scalar_i: usize = 0;13491 var scalar_i: usize = 0;
src/Value.zig+1
...@@ -292,6 +292,7 @@ pub fn getUnsignedIntInner(...@@ -292,6 +292,7 @@ pub fn getUnsignedIntInner(
292 .none => 0,292 .none => 0,
293 else => |payload| Value.fromInterned(payload).getUnsignedIntInner(strat, zcu, tid),293 else => |payload| Value.fromInterned(payload).getUnsignedIntInner(strat, zcu, tid),
294 },294 },
295 .enum_tag => |enum_tag| return Value.fromInterned(enum_tag.int).getUnsignedIntInner(strat, zcu, tid),
295 else => null,296 else => null,
296 },297 },
297 };298 };
src/arch/aarch64/CodeGen.zig+2
...@@ -735,6 +735,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -735,6 +735,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
735 .block => try self.airBlock(inst),735 .block => try self.airBlock(inst),
736 .br => try self.airBr(inst),736 .br => try self.airBr(inst),
737 .repeat => return self.fail("TODO implement `repeat`", .{}),737 .repeat => return self.fail("TODO implement `repeat`", .{}),
738 .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}),
738 .trap => try self.airTrap(),739 .trap => try self.airTrap(),
739 .breakpoint => try self.airBreakpoint(),740 .breakpoint => try self.airBreakpoint(),
740 .ret_addr => try self.airRetAddr(inst),741 .ret_addr => try self.airRetAddr(inst),
...@@ -825,6 +826,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -825,6 +826,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
825 .field_parent_ptr => try self.airFieldParentPtr(inst),826 .field_parent_ptr => try self.airFieldParentPtr(inst),
826827
827 .switch_br => try self.airSwitch(inst),828 .switch_br => try self.airSwitch(inst),
829 .loop_switch_br => return self.fail("TODO implement `loop_switch_br`", .{}),
828 .slice_ptr => try self.airSlicePtr(inst),830 .slice_ptr => try self.airSlicePtr(inst),
829 .slice_len => try self.airSliceLen(inst),831 .slice_len => try self.airSliceLen(inst),
830832
src/arch/arm/CodeGen.zig+2
...@@ -722,6 +722,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -722,6 +722,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
722 .block => try self.airBlock(inst),722 .block => try self.airBlock(inst),
723 .br => try self.airBr(inst),723 .br => try self.airBr(inst),
724 .repeat => return self.fail("TODO implement `repeat`", .{}),724 .repeat => return self.fail("TODO implement `repeat`", .{}),
725 .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}),
725 .trap => try self.airTrap(),726 .trap => try self.airTrap(),
726 .breakpoint => try self.airBreakpoint(),727 .breakpoint => try self.airBreakpoint(),
727 .ret_addr => try self.airRetAddr(inst),728 .ret_addr => try self.airRetAddr(inst),
...@@ -812,6 +813,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -812,6 +813,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
812 .field_parent_ptr => try self.airFieldParentPtr(inst),813 .field_parent_ptr => try self.airFieldParentPtr(inst),
813814
814 .switch_br => try self.airSwitch(inst),815 .switch_br => try self.airSwitch(inst),
816 .loop_switch_br => return self.fail("TODO implement `loop_switch_br`", .{}),
815 .slice_ptr => try self.airSlicePtr(inst),817 .slice_ptr => try self.airSlicePtr(inst),
816 .slice_len => try self.airSliceLen(inst),818 .slice_len => try self.airSliceLen(inst),
817819
src/arch/riscv64/CodeGen.zig+2
...@@ -1580,6 +1580,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {...@@ -1580,6 +1580,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
1580 .block => try func.airBlock(inst),1580 .block => try func.airBlock(inst),
1581 .br => try func.airBr(inst),1581 .br => try func.airBr(inst),
1582 .repeat => return func.fail("TODO implement `repeat`", .{}),1582 .repeat => return func.fail("TODO implement `repeat`", .{}),
1583 .switch_dispatch => return func.fail("TODO implement `switch_dispatch`", .{}),
1583 .trap => try func.airTrap(),1584 .trap => try func.airTrap(),
1584 .breakpoint => try func.airBreakpoint(),1585 .breakpoint => try func.airBreakpoint(),
1585 .ret_addr => try func.airRetAddr(inst),1586 .ret_addr => try func.airRetAddr(inst),
...@@ -1669,6 +1670,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {...@@ -1669,6 +1670,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
1669 .field_parent_ptr => try func.airFieldParentPtr(inst),1670 .field_parent_ptr => try func.airFieldParentPtr(inst),
16701671
1671 .switch_br => try func.airSwitchBr(inst),1672 .switch_br => try func.airSwitchBr(inst),
1673 .loop_switch_br => return func.fail("TODO implement `loop_switch_br`", .{}),
16721674
1673 .ptr_slice_len_ptr => try func.airPtrSliceLenPtr(inst),1675 .ptr_slice_len_ptr => try func.airPtrSliceLenPtr(inst),
1674 .ptr_slice_ptr_ptr => try func.airPtrSlicePtrPtr(inst),1676 .ptr_slice_ptr_ptr => try func.airPtrSlicePtrPtr(inst),
src/arch/sparc64/CodeGen.zig+2
...@@ -577,6 +577,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -577,6 +577,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
577 .block => try self.airBlock(inst),577 .block => try self.airBlock(inst),
578 .br => try self.airBr(inst),578 .br => try self.airBr(inst),
579 .repeat => return self.fail("TODO implement `repeat`", .{}),579 .repeat => return self.fail("TODO implement `repeat`", .{}),
580 .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}),
580 .trap => try self.airTrap(),581 .trap => try self.airTrap(),
581 .breakpoint => try self.airBreakpoint(),582 .breakpoint => try self.airBreakpoint(),
582 .ret_addr => @panic("TODO try self.airRetAddr(inst)"),583 .ret_addr => @panic("TODO try self.airRetAddr(inst)"),
...@@ -667,6 +668,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -667,6 +668,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
667 .field_parent_ptr => @panic("TODO try self.airFieldParentPtr(inst)"),668 .field_parent_ptr => @panic("TODO try self.airFieldParentPtr(inst)"),
668669
669 .switch_br => try self.airSwitch(inst),670 .switch_br => try self.airSwitch(inst),
671 .loop_switch_br => return self.fail("TODO implement `loop_switch_br`", .{}),
670 .slice_ptr => try self.airSlicePtr(inst),672 .slice_ptr => try self.airSlicePtr(inst),
671 .slice_len => try self.airSliceLen(inst),673 .slice_len => try self.airSliceLen(inst),
672674
src/arch/wasm/CodeGen.zig+2
...@@ -1904,6 +1904,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -1904,6 +1904,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
1904 .breakpoint => func.airBreakpoint(inst),1904 .breakpoint => func.airBreakpoint(inst),
1905 .br => func.airBr(inst),1905 .br => func.airBr(inst),
1906 .repeat => return func.fail("TODO implement `repeat`", .{}),1906 .repeat => return func.fail("TODO implement `repeat`", .{}),
1907 .switch_dispatch => return func.fail("TODO implement `switch_dispatch`", .{}),
1907 .int_from_bool => func.airIntFromBool(inst),1908 .int_from_bool => func.airIntFromBool(inst),
1908 .cond_br => func.airCondBr(inst),1909 .cond_br => func.airCondBr(inst),
1909 .intcast => func.airIntcast(inst),1910 .intcast => func.airIntcast(inst),
...@@ -1985,6 +1986,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -1985,6 +1986,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
1985 .field_parent_ptr => func.airFieldParentPtr(inst),1986 .field_parent_ptr => func.airFieldParentPtr(inst),
19861987
1987 .switch_br => func.airSwitchBr(inst),1988 .switch_br => func.airSwitchBr(inst),
1989 .loop_switch_br => return func.fail("TODO implement `loop_switch_br`", .{}),
1988 .trunc => func.airTrunc(inst),1990 .trunc => func.airTrunc(inst),
1989 .unreach => func.airUnreachable(inst),1991 .unreach => func.airUnreachable(inst),
19901992
src/arch/x86_64/CodeGen.zig+2
...@@ -2248,6 +2248,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -2248,6 +2248,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
2248 .block => try self.airBlock(inst),2248 .block => try self.airBlock(inst),
2249 .br => try self.airBr(inst),2249 .br => try self.airBr(inst),
2250 .repeat => return self.fail("TODO implement `repeat`", .{}),2250 .repeat => return self.fail("TODO implement `repeat`", .{}),
2251 .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}),
2251 .trap => try self.airTrap(),2252 .trap => try self.airTrap(),
2252 .breakpoint => try self.airBreakpoint(),2253 .breakpoint => try self.airBreakpoint(),
2253 .ret_addr => try self.airRetAddr(inst),2254 .ret_addr => try self.airRetAddr(inst),
...@@ -2336,6 +2337,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -2336,6 +2337,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
2336 .field_parent_ptr => try self.airFieldParentPtr(inst),2337 .field_parent_ptr => try self.airFieldParentPtr(inst),
23372338
2338 .switch_br => try self.airSwitchBr(inst),2339 .switch_br => try self.airSwitchBr(inst),
2340 .loop_switch_br => return self.fail("TODO implement `loop_switch_br`", .{}),
2339 .slice_ptr => try self.airSlicePtr(inst),2341 .slice_ptr => try self.airSlicePtr(inst),
2340 .slice_len => try self.airSliceLen(inst),2342 .slice_len => try self.airSliceLen(inst),
23412343
src/codegen/c.zig+105-22
...@@ -321,6 +321,9 @@ pub const Function = struct {...@@ -321,6 +321,9 @@ pub const Function = struct {
321 /// by type alignment.321 /// by type alignment.
322 /// The value is whether the alloc needs to be emitted in the header.322 /// The value is whether the alloc needs to be emitted in the header.
323 allocs: std.AutoArrayHashMapUnmanaged(LocalIndex, bool) = .{},323 allocs: std.AutoArrayHashMapUnmanaged(LocalIndex, bool) = .{},
324 /// Maps from `loop_switch_br` instructions to the allocated local used
325 /// for the switch cond. Dispatches should set this local to the new cond.
326 loop_switch_conds: std.AutoHashMapUnmanaged(Air.Inst.Index, LocalIndex) = .{},
324327
325 fn resolveInst(f: *Function, ref: Air.Inst.Ref) !CValue {328 fn resolveInst(f: *Function, ref: Air.Inst.Ref) !CValue {
326 const gop = try f.value_map.getOrPut(ref);329 const gop = try f.value_map.getOrPut(ref);
...@@ -531,6 +534,7 @@ pub const Function = struct {...@@ -531,6 +534,7 @@ pub const Function = struct {
531 f.blocks.deinit(gpa);534 f.blocks.deinit(gpa);
532 f.value_map.deinit();535 f.value_map.deinit();
533 f.lazy_fns.deinit(gpa);536 f.lazy_fns.deinit(gpa);
537 f.loop_switch_conds.deinit(gpa);
534 }538 }
535539
536 fn typeOf(f: *Function, inst: Air.Inst.Ref) Type {540 fn typeOf(f: *Function, inst: Air.Inst.Ref) Type {
...@@ -3376,16 +3380,18 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,...@@ -3376,16 +3380,18 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
3376 => unreachable,3380 => unreachable,
33773381
3378 // Instructions that are known to always be `noreturn` based on their tag.3382 // Instructions that are known to always be `noreturn` based on their tag.
3379 .br => return airBr(f, inst),3383 .br => return airBr(f, inst),
3380 .repeat => return airRepeat(f, inst),3384 .repeat => return airRepeat(f, inst),
3381 .cond_br => return airCondBr(f, inst),3385 .switch_dispatch => return airSwitchDispatch(f, inst),
3382 .switch_br => return airSwitchBr(f, inst),3386 .cond_br => return airCondBr(f, inst),
3383 .loop => return airLoop(f, inst),3387 .switch_br => return airSwitchBr(f, inst, false),
3384 .ret => return airRet(f, inst, false),3388 .loop_switch_br => return airSwitchBr(f, inst, true),
3385 .ret_safe => return airRet(f, inst, false), // TODO3389 .loop => return airLoop(f, inst),
3386 .ret_load => return airRet(f, inst, true),3390 .ret => return airRet(f, inst, false),
3387 .trap => return airTrap(f, f.object.writer()),3391 .ret_safe => return airRet(f, inst, false), // TODO
3388 .unreach => return airUnreach(f),3392 .ret_load => return airRet(f, inst, true),
3393 .trap => return airTrap(f, f.object.writer()),
3394 .unreach => return airUnreach(f),
33893395
3390 // Instructions which may be `noreturn`.3396 // Instructions which may be `noreturn`.
3391 .block => res: {3397 .block => res: {
...@@ -4786,6 +4792,46 @@ fn airRepeat(f: *Function, inst: Air.Inst.Index) !void {...@@ -4786,6 +4792,46 @@ fn airRepeat(f: *Function, inst: Air.Inst.Index) !void {
4786 try writer.print("goto zig_loop_{d};\n", .{@intFromEnum(repeat.loop_inst)});4792 try writer.print("goto zig_loop_{d};\n", .{@intFromEnum(repeat.loop_inst)});
4787}4793}
47884794
4795fn airSwitchDispatch(f: *Function, inst: Air.Inst.Index) !void {
4796 const pt = f.object.dg.pt;
4797 const zcu = pt.zcu;
4798 const br = f.air.instructions.items(.data)[@intFromEnum(inst)].br;
4799 const writer = f.object.writer();
4800
4801 if (try f.air.value(br.operand, pt)) |cond_val| {
4802 // Comptime-known dispatch. Iterate the cases to find the correct
4803 // one, and branch directly to the corresponding case.
4804 const switch_br = f.air.unwrapSwitch(br.block_inst);
4805 var it = switch_br.iterateCases();
4806 const target_case_idx: u32 = target: while (it.next()) |case| {
4807 for (case.items) |item| {
4808 const val = Value.fromInterned(item.toInterned().?);
4809 if (cond_val.compareHetero(.eq, val, zcu)) break :target case.idx;
4810 }
4811 for (case.ranges) |range| {
4812 const low = Value.fromInterned(range[0].toInterned().?);
4813 const high = Value.fromInterned(range[1].toInterned().?);
4814 if (cond_val.compareHetero(.gte, low, zcu) and
4815 cond_val.compareHetero(.lte, high, zcu))
4816 {
4817 break :target case.idx;
4818 }
4819 }
4820 } else switch_br.cases_len;
4821 try writer.print("goto zig_switch_{d}_dispatch_{d};\n", .{ @intFromEnum(br.block_inst), target_case_idx });
4822 return;
4823 }
4824
4825 // Runtime-known dispatch. Set the switch condition, and branch back.
4826 const cond = try f.resolveInst(br.operand);
4827 const cond_local = f.loop_switch_conds.get(br.block_inst).?;
4828 try f.writeCValue(writer, .{ .local = cond_local }, .Other);
4829 try writer.writeAll(" = ");
4830 try f.writeCValue(writer, cond, .Initializer);
4831 try writer.writeAll(";\n");
4832 try writer.print("goto zig_switch_{d}_loop;", .{@intFromEnum(br.block_inst)});
4833}
4834
4789fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue {4835fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue {
4790 const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;4836 const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4791 const inst_ty = f.typeOfIndex(inst);4837 const inst_ty = f.typeOfIndex(inst);
...@@ -5004,15 +5050,34 @@ fn airCondBr(f: *Function, inst: Air.Inst.Index) !void {...@@ -5004,15 +5050,34 @@ fn airCondBr(f: *Function, inst: Air.Inst.Index) !void {
5004 try genBodyInner(f, else_body);5050 try genBodyInner(f, else_body);
5005}5051}
50065052
5007fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void {5053fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void {
5008 const pt = f.object.dg.pt;5054 const pt = f.object.dg.pt;
5009 const zcu = pt.zcu;5055 const zcu = pt.zcu;
5056 const gpa = f.object.dg.gpa;
5010 const switch_br = f.air.unwrapSwitch(inst);5057 const switch_br = f.air.unwrapSwitch(inst);
5011 const condition = try f.resolveInst(switch_br.operand);5058 const init_condition = try f.resolveInst(switch_br.operand);
5012 try reap(f, inst, &.{switch_br.operand});5059 try reap(f, inst, &.{switch_br.operand});
5013 const condition_ty = f.typeOf(switch_br.operand);5060 const condition_ty = f.typeOf(switch_br.operand);
5014 const writer = f.object.writer();5061 const writer = f.object.writer();
50155062
5063 // For dispatches, we will create a local alloc to contain the condition value.
5064 // This may not result in optimal codegen for switch loops, but it minimizes the
5065 // amount of C code we generate, which is probably more desirable here (and is simpler).
5066 const condition = if (is_dispatch_loop) cond: {
5067 const new_local = try f.allocLocal(inst, condition_ty);
5068 try f.writeCValue(writer, new_local, .Other);
5069 try writer.writeAll(" = ");
5070 try f.writeCValue(writer, init_condition, .Initializer);
5071 try writer.writeAll(";\n");
5072 try writer.print("zig_switch_{d}_loop:", .{@intFromEnum(inst)});
5073 try f.loop_switch_conds.put(gpa, inst, new_local.new_local);
5074 break :cond new_local;
5075 } else init_condition;
5076
5077 defer if (is_dispatch_loop) {
5078 assert(f.loop_switch_conds.remove(inst));
5079 };
5080
5016 try writer.writeAll("switch (");5081 try writer.writeAll("switch (");
50175082
5018 const lowered_condition_ty = if (condition_ty.toIntern() == .bool_type)5083 const lowered_condition_ty = if (condition_ty.toIntern() == .bool_type)
...@@ -5030,7 +5095,6 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void {...@@ -5030,7 +5095,6 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void {
5030 try writer.writeAll(") {");5095 try writer.writeAll(") {");
5031 f.object.indent_writer.pushIndent();5096 f.object.indent_writer.pushIndent();
50325097
5033 const gpa = f.object.dg.gpa;
5034 const liveness = try f.liveness.getSwitchBr(gpa, inst, switch_br.cases_len + 1);5098 const liveness = try f.liveness.getSwitchBr(gpa, inst, switch_br.cases_len + 1);
5035 defer gpa.free(liveness.deaths);5099 defer gpa.free(liveness.deaths);
50365100
...@@ -5045,9 +5109,15 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void {...@@ -5045,9 +5109,15 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void {
5045 try f.object.indent_writer.insertNewline();5109 try f.object.indent_writer.insertNewline();
5046 try writer.writeAll("case ");5110 try writer.writeAll("case ");
5047 const item_value = try f.air.value(item, pt);5111 const item_value = try f.air.value(item, pt);
5048 if (item_value.?.getUnsignedInt(zcu)) |item_int| try writer.print("{}\n", .{5112 // If `item_value` is a pointer with a known integer address, print the address
5049 try f.fmtIntLiteral(try pt.intValue(lowered_condition_ty, item_int)),5113 // with no cast to avoid a warning.
5050 }) else {5114 write_val: {
5115 if (condition_ty.isPtrAtRuntime(zcu)) {
5116 if (item_value.?.getUnsignedInt(zcu)) |item_int| {
5117 try writer.print("{}", .{try f.fmtIntLiteral(try pt.intValue(lowered_condition_ty, item_int))});
5118 break :write_val;
5119 }
5120 }
5051 if (condition_ty.isPtrAtRuntime(zcu)) {5121 if (condition_ty.isPtrAtRuntime(zcu)) {
5052 try writer.writeByte('(');5122 try writer.writeByte('(');
5053 try f.renderType(writer, Type.usize);5123 try f.renderType(writer, Type.usize);
...@@ -5057,9 +5127,14 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void {...@@ -5057,9 +5127,14 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void {
5057 }5127 }
5058 try writer.writeByte(':');5128 try writer.writeByte(':');
5059 }5129 }
5060 try writer.writeByte(' ');5130 try writer.writeAll(" {\n");
50615131 f.object.indent_writer.pushIndent();
5062 try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, false);5132 if (is_dispatch_loop) {
5133 try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx });
5134 }
5135 try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true);
5136 f.object.indent_writer.popIndent();
5137 try writer.writeByte('}');
50635138
5064 // The case body must be noreturn so we don't need to insert a break.5139 // The case body must be noreturn so we don't need to insert a break.
5065 }5140 }
...@@ -5095,11 +5170,19 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void {...@@ -5095,11 +5170,19 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void {
5095 try f.object.dg.renderValue(writer, (try f.air.value(range[1], pt)).?, .Other);5170 try f.object.dg.renderValue(writer, (try f.air.value(range[1], pt)).?, .Other);
5096 try writer.writeByte(')');5171 try writer.writeByte(')');
5097 }5172 }
5098 try writer.writeAll(") ");5173 try writer.writeAll(") {\n");
5099 try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, false);5174 f.object.indent_writer.pushIndent();
5175 if (is_dispatch_loop) {
5176 try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx });
5177 }
5178 try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true);
5179 f.object.indent_writer.popIndent();
5180 try writer.writeByte('}');
5100 }5181 }
5101 }5182 }
51025183 if (is_dispatch_loop) {
5184 try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), switch_br.cases_len });
5185 }
5103 if (else_body.len > 0) {5186 if (else_body.len > 0) {
5104 // Note that this must be the last case, so we do not need to use `genBodyResolveState` since5187 // Note that this must be the last case, so we do not need to use `genBodyResolveState` since
5105 // the parent block will do it (because the case body is noreturn).5188 // the parent block will do it (because the case body is noreturn).
src/codegen/llvm.zig+404-84
...@@ -1721,6 +1721,7 @@ pub const Object = struct {...@@ -1721,6 +1721,7 @@ pub const Object = struct {
1721 .func_inst_table = .{},1721 .func_inst_table = .{},
1722 .blocks = .{},1722 .blocks = .{},
1723 .loops = .{},1723 .loops = .{},
1724 .switch_dispatch_info = .{},
1724 .sync_scope = if (owner_mod.single_threaded) .singlethread else .system,1725 .sync_scope = if (owner_mod.single_threaded) .singlethread else .system,
1725 .file = file,1726 .file = file,
1726 .scope = subprogram,1727 .scope = subprogram,
...@@ -4845,6 +4846,10 @@ pub const FuncGen = struct {...@@ -4845,6 +4846,10 @@ pub const FuncGen = struct {
4845 /// Maps `loop` instructions to the bb to branch to to repeat the loop.4846 /// Maps `loop` instructions to the bb to branch to to repeat the loop.
4846 loops: std.AutoHashMapUnmanaged(Air.Inst.Index, Builder.Function.Block.Index),4847 loops: std.AutoHashMapUnmanaged(Air.Inst.Index, Builder.Function.Block.Index),
48474848
4849 /// Maps `loop_switch_br` instructions to the information required to lower
4850 /// dispatches (`switch_dispatch` instructions).
4851 switch_dispatch_info: std.AutoHashMapUnmanaged(Air.Inst.Index, SwitchDispatchInfo),
4852
4848 sync_scope: Builder.SyncScope,4853 sync_scope: Builder.SyncScope,
48494854
4850 const Fuzz = struct {4855 const Fuzz = struct {
...@@ -4857,6 +4862,33 @@ pub const FuncGen = struct {...@@ -4857,6 +4862,33 @@ pub const FuncGen = struct {
4857 }4862 }
4858 };4863 };
48594864
4865 const SwitchDispatchInfo = struct {
4866 /// These are the blocks corresponding to each switch case.
4867 /// The final element corresponds to the `else` case.
4868 /// Slices allocated into `gpa`.
4869 case_blocks: []Builder.Function.Block.Index,
4870 /// This is `.none` if `jmp_table` is set, since we won't use a `switch` instruction to dispatch.
4871 switch_weights: Builder.Function.Instruction.BrCond.Weights,
4872 /// If not `null`, we have manually constructed a jump table to reach the desired block.
4873 /// `table` can be used if the value is between `min` and `max` inclusive.
4874 /// We perform this lowering manually to avoid some questionable behavior from LLVM.
4875 /// See `airSwitchBr` for details.
4876 jmp_table: ?JmpTable,
4877
4878 const JmpTable = struct {
4879 min: Builder.Constant,
4880 max: Builder.Constant,
4881 in_bounds_hint: enum { none, unpredictable, likely, unlikely },
4882 /// Pointer to the jump table itself, to be used with `indirectbr`.
4883 /// The index into the jump table is the dispatch condition minus `min`.
4884 /// The table values are `blockaddress` constants corresponding to blocks in `case_blocks`.
4885 table: Builder.Constant,
4886 /// `true` if `table` conatins a reference to the `else` block.
4887 /// In this case, the `indirectbr` must include the `else` block in its target list.
4888 table_includes_else: bool,
4889 };
4890 };
4891
4860 const BreakList = union {4892 const BreakList = union {
4861 list: std.MultiArrayList(struct {4893 list: std.MultiArrayList(struct {
4862 bb: Builder.Function.Block.Index,4894 bb: Builder.Function.Block.Index,
...@@ -4872,6 +4904,11 @@ pub const FuncGen = struct {...@@ -4872,6 +4904,11 @@ pub const FuncGen = struct {
4872 self.func_inst_table.deinit(gpa);4904 self.func_inst_table.deinit(gpa);
4873 self.blocks.deinit(gpa);4905 self.blocks.deinit(gpa);
4874 self.loops.deinit(gpa);4906 self.loops.deinit(gpa);
4907 var it = self.switch_dispatch_info.valueIterator();
4908 while (it.next()) |info| {
4909 self.gpa.free(info.case_blocks);
4910 }
4911 self.switch_dispatch_info.deinit(gpa);
4875 }4912 }
48764913
4877 fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error {4914 fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error {
...@@ -5182,16 +5219,18 @@ pub const FuncGen = struct {...@@ -5182,16 +5219,18 @@ pub const FuncGen = struct {
5182 .work_group_id => try self.airWorkGroupId(inst),5219 .work_group_id => try self.airWorkGroupId(inst),
51835220
5184 // Instructions that are known to always be `noreturn` based on their tag.5221 // Instructions that are known to always be `noreturn` based on their tag.
5185 .br => return self.airBr(inst),5222 .br => return self.airBr(inst),
5186 .repeat => return self.airRepeat(inst),5223 .repeat => return self.airRepeat(inst),
5187 .cond_br => return self.airCondBr(inst),5224 .switch_dispatch => return self.airSwitchDispatch(inst),
5188 .switch_br => return self.airSwitchBr(inst),5225 .cond_br => return self.airCondBr(inst),
5189 .loop => return self.airLoop(inst),5226 .switch_br => return self.airSwitchBr(inst, false),
5190 .ret => return self.airRet(inst, false),5227 .loop_switch_br => return self.airSwitchBr(inst, true),
5191 .ret_safe => return self.airRet(inst, true),5228 .loop => return self.airLoop(inst),
5192 .ret_load => return self.airRetLoad(inst),5229 .ret => return self.airRet(inst, false),
5193 .trap => return self.airTrap(inst),5230 .ret_safe => return self.airRet(inst, true),
5194 .unreach => return self.airUnreach(inst),5231 .ret_load => return self.airRetLoad(inst),
5232 .trap => return self.airTrap(inst),
5233 .unreach => return self.airUnreach(inst),
51955234
5196 // Instructions which may be `noreturn`.5235 // Instructions which may be `noreturn`.
5197 .block => res: {5236 .block => res: {
...@@ -6093,6 +6132,202 @@ pub const FuncGen = struct {...@@ -6093,6 +6132,202 @@ pub const FuncGen = struct {
6093 _ = try self.wip.br(loop_bb);6132 _ = try self.wip.br(loop_bb);
6094 }6133 }
60956134
6135 fn lowerSwitchDispatch(
6136 self: *FuncGen,
6137 switch_inst: Air.Inst.Index,
6138 cond_ref: Air.Inst.Ref,
6139 dispatch_info: SwitchDispatchInfo,
6140 ) !void {
6141 const o = self.ng.object;
6142 const pt = o.pt;
6143 const zcu = pt.zcu;
6144 const cond_ty = self.typeOf(cond_ref);
6145 const switch_br = self.air.unwrapSwitch(switch_inst);
6146
6147 if (try self.air.value(cond_ref, pt)) |cond_val| {
6148 // Comptime-known dispatch. Iterate the cases to find the correct
6149 // one, and branch to the corresponding element of `case_blocks`.
6150 var it = switch_br.iterateCases();
6151 const target_case_idx = target: while (it.next()) |case| {
6152 for (case.items) |item| {
6153 const val = Value.fromInterned(item.toInterned().?);
6154 if (cond_val.compareHetero(.eq, val, zcu)) break :target case.idx;
6155 }
6156 for (case.ranges) |range| {
6157 const low = Value.fromInterned(range[0].toInterned().?);
6158 const high = Value.fromInterned(range[1].toInterned().?);
6159 if (cond_val.compareHetero(.gte, low, zcu) and
6160 cond_val.compareHetero(.lte, high, zcu))
6161 {
6162 break :target case.idx;
6163 }
6164 }
6165 } else dispatch_info.case_blocks.len - 1;
6166 const target_block = dispatch_info.case_blocks[target_case_idx];
6167 target_block.ptr(&self.wip).incoming += 1;
6168 _ = try self.wip.br(target_block);
6169 return;
6170 }
6171
6172 // Runtime-known dispatch.
6173 const cond = try self.resolveInst(cond_ref);
6174
6175 if (dispatch_info.jmp_table) |jmp_table| {
6176 // We should use the constructed jump table.
6177 // First, check the bounds to branch to the `else` case if needed.
6178 const inbounds = try self.wip.bin(
6179 .@"and",
6180 try self.cmp(.normal, .gte, cond_ty, cond, jmp_table.min.toValue()),
6181 try self.cmp(.normal, .lte, cond_ty, cond, jmp_table.max.toValue()),
6182 "",
6183 );
6184 const jmp_table_block = try self.wip.block(1, "Then");
6185 const else_block = dispatch_info.case_blocks[dispatch_info.case_blocks.len - 1];
6186 else_block.ptr(&self.wip).incoming += 1;
6187 _ = try self.wip.brCond(inbounds, jmp_table_block, else_block, switch (jmp_table.in_bounds_hint) {
6188 .none => .none,
6189 .unpredictable => .unpredictable,
6190 .likely => .then_likely,
6191 .unlikely => .else_likely,
6192 });
6193
6194 self.wip.cursor = .{ .block = jmp_table_block };
6195
6196 // Figure out the list of blocks we might branch to.
6197 // This includes all case blocks, but it might not include the `else` block if
6198 // the table is dense.
6199 const target_blocks_len = dispatch_info.case_blocks.len - @intFromBool(!jmp_table.table_includes_else);
6200 const target_blocks = dispatch_info.case_blocks[0..target_blocks_len];
6201
6202 // Make sure to cast the index to a usize so it's not treated as negative!
6203 const table_index = try self.wip.cast(
6204 .zext,
6205 try self.wip.bin(.@"sub nuw", cond, jmp_table.min.toValue(), ""),
6206 try o.lowerType(Type.usize),
6207 "",
6208 );
6209 const target_ptr_ptr = try self.wip.gep(
6210 .inbounds,
6211 .ptr,
6212 jmp_table.table.toValue(),
6213 &.{table_index},
6214 "",
6215 );
6216 const target_ptr = try self.wip.load(.normal, .ptr, target_ptr_ptr, .default, "");
6217
6218 // Do the branch!
6219 _ = try self.wip.indirectbr(target_ptr, target_blocks);
6220
6221 // Mark all target blocks as having one more incoming branch.
6222 for (target_blocks) |case_block| {
6223 case_block.ptr(&self.wip).incoming += 1;
6224 }
6225
6226 return;
6227 }
6228
6229 // We must lower to an actual LLVM `switch` instruction.
6230 // The switch prongs will correspond to our scalar cases. Ranges will
6231 // be handled by conditional branches in the `else` prong.
6232
6233 const llvm_usize = try o.lowerType(Type.usize);
6234 const cond_int = if (cond.typeOfWip(&self.wip).isPointer(&o.builder))
6235 try self.wip.cast(.ptrtoint, cond, llvm_usize, "")
6236 else
6237 cond;
6238
6239 const llvm_cases_len, const last_range_case = info: {
6240 var llvm_cases_len: u32 = 0;
6241 var last_range_case: ?u32 = null;
6242 var it = switch_br.iterateCases();
6243 while (it.next()) |case| {
6244 if (case.ranges.len > 0) last_range_case = case.idx;
6245 llvm_cases_len += @intCast(case.items.len);
6246 }
6247 break :info .{ llvm_cases_len, last_range_case };
6248 };
6249
6250 // The `else` of the LLVM `switch` is the actual `else` prong only
6251 // if there are no ranges. Otherwise, the `else` will have a
6252 // conditional chain before the "true" `else` prong.
6253 const llvm_else_block = if (last_range_case == null)
6254 dispatch_info.case_blocks[dispatch_info.case_blocks.len - 1]
6255 else
6256 try self.wip.block(0, "RangeTest");
6257
6258 llvm_else_block.ptr(&self.wip).incoming += 1;
6259
6260 var wip_switch = try self.wip.@"switch"(cond_int, llvm_else_block, llvm_cases_len, dispatch_info.switch_weights);
6261 defer wip_switch.finish(&self.wip);
6262
6263 // Construct the actual cases. Set the cursor to the `else` block so
6264 // we can construct ranges at the same time as scalar cases.
6265 self.wip.cursor = .{ .block = llvm_else_block };
6266
6267 var it = switch_br.iterateCases();
6268 while (it.next()) |case| {
6269 const case_block = dispatch_info.case_blocks[case.idx];
6270
6271 for (case.items) |item| {
6272 const llvm_item = (try self.resolveInst(item)).toConst().?;
6273 const llvm_int_item = if (llvm_item.typeOf(&o.builder).isPointer(&o.builder))
6274 try o.builder.castConst(.ptrtoint, llvm_item, llvm_usize)
6275 else
6276 llvm_item;
6277 try wip_switch.addCase(llvm_int_item, case_block, &self.wip);
6278 }
6279 case_block.ptr(&self.wip).incoming += @intCast(case.items.len);
6280
6281 if (case.ranges.len == 0) continue;
6282
6283 // Add a conditional for the ranges, directing to the relevant bb.
6284 // We don't need to consider `cold` branch hints since that information is stored
6285 // in the target bb body, but we do care about likely/unlikely/unpredictable.
6286
6287 const hint = switch_br.getHint(case.idx);
6288
6289 var range_cond: ?Builder.Value = null;
6290 for (case.ranges) |range| {
6291 const llvm_min = try self.resolveInst(range[0]);
6292 const llvm_max = try self.resolveInst(range[1]);
6293 const cond_part = try self.wip.bin(
6294 .@"and",
6295 try self.cmp(.normal, .gte, cond_ty, cond, llvm_min),
6296 try self.cmp(.normal, .lte, cond_ty, cond, llvm_max),
6297 "",
6298 );
6299 if (range_cond) |prev| {
6300 range_cond = try self.wip.bin(.@"or", prev, cond_part, "");
6301 } else range_cond = cond_part;
6302 }
6303
6304 // If the check fails, we either branch to the "true" `else` case,
6305 // or to the next range condition.
6306 const range_else_block = if (case.idx == last_range_case.?)
6307 dispatch_info.case_blocks[dispatch_info.case_blocks.len - 1]
6308 else
6309 try self.wip.block(0, "RangeTest");
6310
6311 _ = try self.wip.brCond(range_cond.?, case_block, range_else_block, switch (hint) {
6312 .none, .cold => .none,
6313 .unpredictable => .unpredictable,
6314 .likely => .then_likely,
6315 .unlikely => .else_likely,
6316 });
6317 case_block.ptr(&self.wip).incoming += 1;
6318 range_else_block.ptr(&self.wip).incoming += 1;
6319
6320 // Construct the next range conditional (if any) in the false branch.
6321 self.wip.cursor = .{ .block = range_else_block };
6322 }
6323 }
6324
6325 fn airSwitchDispatch(self: *FuncGen, inst: Air.Inst.Index) !void {
6326 const br = self.air.instructions.items(.data)[@intFromEnum(inst)].br;
6327 const dispatch_info = self.switch_dispatch_info.get(br.block_inst).?;
6328 return self.lowerSwitchDispatch(br.block_inst, br.operand, dispatch_info);
6329 }
6330
6096 fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !void {6331 fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !void {
6097 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;6332 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6098 const cond = try self.resolveInst(pl_op.operand);6333 const cond = try self.resolveInst(pl_op.operand);
...@@ -6257,36 +6492,123 @@ pub const FuncGen = struct {...@@ -6257,36 +6492,123 @@ pub const FuncGen = struct {
6257 return fg.wip.extractValue(err_union, &.{offset}, "");6492 return fg.wip.extractValue(err_union, &.{offset}, "");
6258 }6493 }
62596494
6260 fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index) !void {6495 fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index, is_dispatch_loop: bool) !void {
6261 const o = self.ng.object;6496 const o = self.ng.object;
6497 const zcu = o.pt.zcu;
62626498
6263 const switch_br = self.air.unwrapSwitch(inst);6499 const switch_br = self.air.unwrapSwitch(inst);
62646500
6265 const cond = try self.resolveInst(switch_br.operand);6501 // For `loop_switch_br`, we need these BBs prepared ahead of time to generate dispatches.
6502 // For `switch_br`, they allow us to sometimes generate better IR by sharing a BB between
6503 // scalar and range cases in the same prong.
6504 // +1 for `else` case. This is not the same as the LLVM `else` prong, as that may first contain
6505 // conditionals to handle ranges.
6506 const case_blocks = try self.gpa.alloc(Builder.Function.Block.Index, switch_br.cases_len + 1);
6507 defer self.gpa.free(case_blocks);
6508 // We set incoming as 0 for now, and increment it as we construct dispatches.
6509 for (case_blocks[0 .. case_blocks.len - 1]) |*b| b.* = try self.wip.block(0, "Case");
6510 case_blocks[case_blocks.len - 1] = try self.wip.block(0, "Default");
6511
6512 // There's a special case here to manually generate a jump table in some cases.
6513 //
6514 // Labeled switch in Zig is intended to follow the "direct threading" pattern. We would ideally use a jump
6515 // table, and each `continue` has its own indirect `jmp`, to allow the branch predictor to more accurately
6516 // use data patterns to predict future dispatches. The problem, however, is that LLVM emits fascinatingly
6517 // bad asm for this. Not only does it not share the jump table -- which we really need it to do to prevent
6518 // destroying the cache -- but it also actually generates slightly different jump tables for each case,
6519 // and *a separate conditional branch beforehand* to handle dispatching back to the case we're currently
6520 // within(!!).
6521 //
6522 // This asm is really, really, not what we want. As such, we will construct the jump table manually where
6523 // appropriate (the values are dense and relatively few), and use it when lowering dispatches.
6524
6525 const jmp_table: ?SwitchDispatchInfo.JmpTable = jmp_table: {
6526 if (!is_dispatch_loop) break :jmp_table null;
6527 // On a 64-bit target, 1024 pointers in our jump table is about 8K of pointers. This seems just
6528 // about acceptable - it won't fill L1d cache on most CPUs.
6529 const max_table_len = 1024;
62666530
6267 // This is not necessarily the actual `else` prong; it first contains conditionals6531 const cond_ty = self.typeOf(switch_br.operand);
6268 // for any range cases. It's just the `else` of the LLVM switch.6532 switch (cond_ty.zigTypeTag(zcu)) {
6269 const llvm_else_block = try self.wip.block(1, "Default");6533 .bool, .pointer => break :jmp_table null,
6534 .@"enum", .int, .error_set => {},
6535 else => unreachable,
6536 }
62706537
6271 const case_blocks = try self.gpa.alloc(Builder.Function.Block.Index, switch_br.cases_len);6538 if (cond_ty.intInfo(zcu).signedness == .signed) break :jmp_table null;
6272 defer self.gpa.free(case_blocks);
6273 // We set incoming as 0 for now, and increment it as we construct the switch.
6274 for (case_blocks) |*b| b.* = try self.wip.block(0, "Case");
62756539
6276 const llvm_usize = try o.lowerType(Type.usize);6540 // Don't worry about the size of the type -- it's irrelevant, because the prong values could be fairly dense.
6277 const cond_int = if (cond.typeOfWip(&self.wip).isPointer(&o.builder))6541 // If they are, then we will construct a jump table.
6278 try self.wip.cast(.ptrtoint, cond, llvm_usize, "")6542 const min, const max = self.switchCaseItemRange(switch_br);
6279 else6543 const min_int = min.getUnsignedInt(zcu) orelse break :jmp_table null;
6280 cond;6544 const max_int = max.getUnsignedInt(zcu) orelse break :jmp_table null;
6545 const table_len = max_int - min_int + 1;
6546 if (table_len > max_table_len) break :jmp_table null;
6547
6548 const table_elems = try self.gpa.alloc(Builder.Constant, @intCast(table_len));
6549 defer self.gpa.free(table_elems);
62816550
6282 const llvm_cases_len = llvm_cases_len: {6551 // Set them all to the `else` branch, then iterate over the AIR switch
6283 var len: u32 = 0;6552 // and replace all values which correspond to other prongs.
6553 @memset(table_elems, try o.builder.blockAddrConst(
6554 self.wip.function,
6555 case_blocks[case_blocks.len - 1],
6556 ));
6557 var item_count: u32 = 0;
6284 var it = switch_br.iterateCases();6558 var it = switch_br.iterateCases();
6285 while (it.next()) |case| len += @intCast(case.items.len);6559 while (it.next()) |case| {
6286 break :llvm_cases_len len;6560 const case_block = case_blocks[case.idx];
6561 const case_block_addr = try o.builder.blockAddrConst(
6562 self.wip.function,
6563 case_block,
6564 );
6565 for (case.items) |item| {
6566 const val = Value.fromInterned(item.toInterned().?);
6567 const table_idx = val.toUnsignedInt(zcu) - min_int;
6568 table_elems[@intCast(table_idx)] = case_block_addr;
6569 item_count += 1;
6570 }
6571 for (case.ranges) |range| {
6572 const low = Value.fromInterned(range[0].toInterned().?);
6573 const high = Value.fromInterned(range[1].toInterned().?);
6574 const low_idx = low.toUnsignedInt(zcu) - min_int;
6575 const high_idx = high.toUnsignedInt(zcu) - min_int;
6576 @memset(table_elems[@intCast(low_idx)..@intCast(high_idx + 1)], case_block_addr);
6577 item_count += @intCast(high_idx + 1 - low_idx);
6578 }
6579 }
6580
6581 const table_llvm_ty = try o.builder.arrayType(table_elems.len, .ptr);
6582 const table_val = try o.builder.arrayConst(table_llvm_ty, table_elems);
6583
6584 const table_variable = try o.builder.addVariable(
6585 try o.builder.strtabStringFmt("__jmptab_{d}", .{@intFromEnum(inst)}),
6586 table_llvm_ty,
6587 .default,
6588 );
6589 try table_variable.setInitializer(table_val, &o.builder);
6590 table_variable.setLinkage(.internal, &o.builder);
6591 table_variable.setUnnamedAddr(.unnamed_addr, &o.builder);
6592
6593 const table_includes_else = item_count != table_len;
6594
6595 break :jmp_table .{
6596 .min = try o.lowerValue(min.toIntern()),
6597 .max = try o.lowerValue(max.toIntern()),
6598 .in_bounds_hint = if (table_includes_else) .none else switch (switch_br.getElseHint()) {
6599 .none, .cold => .none,
6600 .unpredictable => .unpredictable,
6601 .likely => .likely,
6602 .unlikely => .unlikely,
6603 },
6604 .table = table_variable.toConst(&o.builder),
6605 .table_includes_else = table_includes_else,
6606 };
6287 };6607 };
62886608
6289 const weights: Builder.Function.Instruction.BrCond.Weights = weights: {6609 const weights: Builder.Function.Instruction.BrCond.Weights = weights: {
6610 if (jmp_table != null) break :weights .none; // not used
6611
6290 // First pass. If any weights are `.unpredictable`, unpredictable.6612 // First pass. If any weights are `.unpredictable`, unpredictable.
6291 // If all are `.none` or `.cold`, none.6613 // If all are `.none` or `.cold`, none.
6292 var any_likely = false;6614 var any_likely = false;
...@@ -6304,6 +6626,13 @@ pub const FuncGen = struct {...@@ -6304,6 +6626,13 @@ pub const FuncGen = struct {
6304 }6626 }
6305 if (!any_likely) break :weights .none;6627 if (!any_likely) break :weights .none;
63066628
6629 const llvm_cases_len = llvm_cases_len: {
6630 var len: u32 = 0;
6631 var it = switch_br.iterateCases();
6632 while (it.next()) |case| len += @intCast(case.items.len);
6633 break :llvm_cases_len len;
6634 };
6635
6307 var weights = try self.gpa.alloc(Builder.Metadata, llvm_cases_len + 1);6636 var weights = try self.gpa.alloc(Builder.Metadata, llvm_cases_len + 1);
6308 defer self.gpa.free(weights);6637 defer self.gpa.free(weights);
63096638
...@@ -6336,75 +6665,66 @@ pub const FuncGen = struct {...@@ -6336,75 +6665,66 @@ pub const FuncGen = struct {
6336 break :weights @enumFromInt(@intFromEnum(tuple));6665 break :weights @enumFromInt(@intFromEnum(tuple));
6337 };6666 };
63386667
6339 var wip_switch = try self.wip.@"switch"(cond_int, llvm_else_block, llvm_cases_len, weights);6668 const dispatch_info: SwitchDispatchInfo = .{
6340 defer wip_switch.finish(&self.wip);6669 .case_blocks = case_blocks,
6670 .switch_weights = weights,
6671 .jmp_table = jmp_table,
6672 };
6673
6674 if (is_dispatch_loop) {
6675 try self.switch_dispatch_info.putNoClobber(self.gpa, inst, dispatch_info);
6676 }
6677 defer if (is_dispatch_loop) {
6678 assert(self.switch_dispatch_info.remove(inst));
6679 };
6680
6681 // Generate the initial dispatch.
6682 // If this is a simple `switch_br`, this is the only dispatch.
6683 try self.lowerSwitchDispatch(inst, switch_br.operand, dispatch_info);
63416684
6685 // Iterate the cases and generate their bodies.
6342 var it = switch_br.iterateCases();6686 var it = switch_br.iterateCases();
6343 var any_ranges = false;
6344 while (it.next()) |case| {6687 while (it.next()) |case| {
6345 if (case.ranges.len > 0) any_ranges = true;
6346 const case_block = case_blocks[case.idx];6688 const case_block = case_blocks[case.idx];
6347 case_block.ptr(&self.wip).incoming += @intCast(case.items.len);
6348 // Handle scalar items, and generate the block.
6349 // We'll generate conditionals for the ranges later on.
6350 for (case.items) |item| {
6351 const llvm_item = (try self.resolveInst(item)).toConst().?;
6352 const llvm_int_item = if (llvm_item.typeOf(&o.builder).isPointer(&o.builder))
6353 try o.builder.castConst(.ptrtoint, llvm_item, llvm_usize)
6354 else
6355 llvm_item;
6356 try wip_switch.addCase(llvm_int_item, case_block, &self.wip);
6357 }
6358 self.wip.cursor = .{ .block = case_block };6689 self.wip.cursor = .{ .block = case_block };
6359 if (switch_br.getHint(case.idx) == .cold) _ = try self.wip.callIntrinsicAssumeCold();6690 if (switch_br.getHint(case.idx) == .cold) _ = try self.wip.callIntrinsicAssumeCold();
6360 try self.genBodyDebugScope(null, case.body, .poi);6691 try self.genBodyDebugScope(null, case.body, .none);
6361 }6692 }
63626693 self.wip.cursor = .{ .block = case_blocks[case_blocks.len - 1] };
6363 const else_body = it.elseBody();6694 const else_body = it.elseBody();
6364 self.wip.cursor = .{ .block = llvm_else_block };
6365 if (any_ranges) {
6366 const cond_ty = self.typeOf(switch_br.operand);
6367 // Add conditionals for the ranges, directing to the relevant bb.
6368 // We don't need to consider `cold` branch hints since that information is stored
6369 // in the target bb body, but we do care about likely/unlikely/unpredictable.
6370 it = switch_br.iterateCases();
6371 while (it.next()) |case| {
6372 if (case.ranges.len == 0) continue;
6373 const case_block = case_blocks[case.idx];
6374 const hint = switch_br.getHint(case.idx);
6375 case_block.ptr(&self.wip).incoming += 1;
6376 const next_else_block = try self.wip.block(1, "Default");
6377 var range_cond: ?Builder.Value = null;
6378 for (case.ranges) |range| {
6379 const llvm_min = try self.resolveInst(range[0]);
6380 const llvm_max = try self.resolveInst(range[1]);
6381 const cond_part = try self.wip.bin(
6382 .@"and",
6383 try self.cmp(.normal, .gte, cond_ty, cond, llvm_min),
6384 try self.cmp(.normal, .lte, cond_ty, cond, llvm_max),
6385 "",
6386 );
6387 if (range_cond) |prev| {
6388 range_cond = try self.wip.bin(.@"or", prev, cond_part, "");
6389 } else range_cond = cond_part;
6390 }
6391 _ = try self.wip.brCond(range_cond.?, case_block, next_else_block, switch (hint) {
6392 .none, .cold => .none,
6393 .unpredictable => .unpredictable,
6394 .likely => .then_likely,
6395 .unlikely => .else_likely,
6396 });
6397 self.wip.cursor = .{ .block = next_else_block };
6398 }
6399 }
6400 if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold();6695 if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold();
6401 if (else_body.len != 0) {6696 if (else_body.len > 0) {
6402 try self.genBodyDebugScope(null, else_body, .poi);6697 try self.genBodyDebugScope(null, it.elseBody(), .none);
6403 } else {6698 } else {
6404 _ = try self.wip.@"unreachable"();6699 _ = try self.wip.@"unreachable"();
6405 }6700 }
6701 }
64066702
6407 // No need to reset the insert cursor since this instruction is noreturn.6703 fn switchCaseItemRange(self: *FuncGen, switch_br: Air.UnwrappedSwitch) [2]Value {
6704 const zcu = self.ng.object.pt.zcu;
6705 var it = switch_br.iterateCases();
6706 var min: ?Value = null;
6707 var max: ?Value = null;
6708 while (it.next()) |case| {
6709 for (case.items) |item| {
6710 const val = Value.fromInterned(item.toInterned().?);
6711 const low = if (min) |m| val.compareHetero(.lt, m, zcu) else true;
6712 const high = if (max) |m| val.compareHetero(.gt, m, zcu) else true;
6713 if (low) min = val;
6714 if (high) max = val;
6715 }
6716 for (case.ranges) |range| {
6717 const vals: [2]Value = .{
6718 Value.fromInterned(range[0].toInterned().?),
6719 Value.fromInterned(range[1].toInterned().?),
6720 };
6721 const low = if (min) |m| vals[0].compareHetero(.lt, m, zcu) else true;
6722 const high = if (max) |m| vals[1].compareHetero(.gt, m, zcu) else true;
6723 if (low) min = vals[0];
6724 if (high) max = vals[1];
6725 }
6726 }
6727 return .{ min.?, max.? };
6408 }6728 }
64096729
6410 fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !void {6730 fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !void {
src/print_air.zig+2-1
...@@ -296,11 +296,12 @@ const Writer = struct {...@@ -296,11 +296,12 @@ const Writer = struct {
296 .aggregate_init => try w.writeAggregateInit(s, inst),296 .aggregate_init => try w.writeAggregateInit(s, inst),
297 .union_init => try w.writeUnionInit(s, inst),297 .union_init => try w.writeUnionInit(s, inst),
298 .br => try w.writeBr(s, inst),298 .br => try w.writeBr(s, inst),
299 .switch_dispatch => try w.writeBr(s, inst),
299 .repeat => try w.writeRepeat(s, inst),300 .repeat => try w.writeRepeat(s, inst),
300 .cond_br => try w.writeCondBr(s, inst),301 .cond_br => try w.writeCondBr(s, inst),
301 .@"try", .try_cold => try w.writeTry(s, inst),302 .@"try", .try_cold => try w.writeTry(s, inst),
302 .try_ptr, .try_ptr_cold => try w.writeTryPtr(s, inst),303 .try_ptr, .try_ptr_cold => try w.writeTryPtr(s, inst),
303 .switch_br => try w.writeSwitchBr(s, inst),304 .loop_switch_br, .switch_br => try w.writeSwitchBr(s, inst),
304 .cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst),305 .cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst),
305 .fence => try w.writeFence(s, inst),306 .fence => try w.writeFence(s, inst),
306 .atomic_load => try w.writeAtomicLoad(s, inst),307 .atomic_load => try w.writeAtomicLoad(s, inst),
src/print_zir.zig+1
...@@ -302,6 +302,7 @@ const Writer = struct {...@@ -302,6 +302,7 @@ const Writer = struct {
302302
303 .@"break",303 .@"break",
304 .break_inline,304 .break_inline,
305 .switch_continue,
305 => try self.writeBreak(stream, inst),306 => try self.writeBreak(stream, inst),
306307
307 .slice_start => try self.writeSliceStart(stream, inst),308 .slice_start => try self.writeSliceStart(stream, inst),
test/behavior.zig+1
...@@ -88,6 +88,7 @@ test {...@@ -88,6 +88,7 @@ test {
88 _ = @import("behavior/struct_contains_null_ptr_itself.zig");88 _ = @import("behavior/struct_contains_null_ptr_itself.zig");
89 _ = @import("behavior/struct_contains_slice_of_itself.zig");89 _ = @import("behavior/struct_contains_slice_of_itself.zig");
90 _ = @import("behavior/switch.zig");90 _ = @import("behavior/switch.zig");
91 _ = @import("behavior/switch_loop.zig");
91 _ = @import("behavior/switch_prong_err_enum.zig");92 _ = @import("behavior/switch_prong_err_enum.zig");
92 _ = @import("behavior/switch_prong_implicit_cast.zig");93 _ = @import("behavior/switch_prong_implicit_cast.zig");
93 _ = @import("behavior/switch_on_captured_error.zig");94 _ = @import("behavior/switch_on_captured_error.zig");
test/behavior/switch_loop.zig created+205
...@@ -0,0 +1,205 @@
1const builtin = @import("builtin");
2const std = @import("std");
3const expect = std.testing.expect;
4
5test "simple switch loop" {
6 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
7 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
8 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
9 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
10 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
11 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
12
13 const S = struct {
14 fn doTheTest() !void {
15 var start: u32 = undefined;
16 start = 32;
17 const result: u32 = s: switch (start) {
18 0 => 0,
19 1 => 1,
20 2 => 2,
21 3 => 3,
22 else => |x| continue :s x / 2,
23 };
24 try expect(result == 2);
25 }
26 };
27 try S.doTheTest();
28 try comptime S.doTheTest();
29}
30
31test "switch loop with ranges" {
32 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
33 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
34 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
35 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
36 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
37 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
38
39 const S = struct {
40 fn doTheTest() !void {
41 var start: u32 = undefined;
42 start = 32;
43 const result = s: switch (start) {
44 0...3 => |x| x,
45 else => |x| continue :s x / 2,
46 };
47 try expect(result == 2);
48 }
49 };
50 try S.doTheTest();
51 try comptime S.doTheTest();
52}
53
54test "switch loop on enum" {
55 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
56 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
57 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
58 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
59 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
60 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
61
62 const S = struct {
63 const E = enum { a, b, c };
64
65 fn doTheTest() !void {
66 var start: E = undefined;
67 start = .a;
68 const result: u32 = s: switch (start) {
69 .a => continue :s .b,
70 .b => continue :s .c,
71 .c => 123,
72 };
73 try expect(result == 123);
74 }
75 };
76 try S.doTheTest();
77 try comptime S.doTheTest();
78}
79
80test "switch loop on tagged union" {
81 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
82 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
83 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
84 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
85 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
86 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
87
88 const S = struct {
89 const U = union(enum) {
90 a: u32,
91 b: f32,
92 c: f32,
93 };
94
95 fn doTheTest() !void {
96 var start: U = undefined;
97 start = .{ .a = 80 };
98 const result = s: switch (start) {
99 .a => |x| switch (x) {
100 0...49 => continue :s .{ .b = @floatFromInt(x) },
101 50 => continue :s .{ .c = @floatFromInt(x) },
102 else => continue :s .{ .a = x / 2 },
103 },
104 .b => |x| x,
105 .c => return error.TestFailed,
106 };
107 try expect(result == 40.0);
108 }
109 };
110 try S.doTheTest();
111 try comptime S.doTheTest();
112}
113
114test "switch loop dispatching instructions" {
115 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
116 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
117 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
118 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
119 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
120 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
121
122 const S = struct {
123 const Inst = union(enum) {
124 set: u32,
125 add: u32,
126 sub: u32,
127 end,
128 };
129
130 fn doTheTest() !void {
131 var insts: [5]Inst = undefined;
132 @memcpy(&insts, &[5]Inst{
133 .{ .set = 123 },
134 .{ .add = 100 },
135 .{ .sub = 50 },
136 .{ .sub = 10 },
137 .end,
138 });
139 var i: u32 = 0;
140 var cur: u32 = undefined;
141 eval: switch (insts[0]) {
142 .set => |x| {
143 cur = x;
144 i += 1;
145 continue :eval insts[i];
146 },
147 .add => |x| {
148 cur += x;
149 i += 1;
150 continue :eval insts[i];
151 },
152 .sub => |x| {
153 cur -= x;
154 i += 1;
155 continue :eval insts[i];
156 },
157 .end => {},
158 }
159 try expect(cur == 163);
160 }
161 };
162 try S.doTheTest();
163 try comptime S.doTheTest();
164}
165
166test "switch loop with pointer capture" {
167 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
168 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
169 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
170 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
171 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
172 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
173
174 const S = struct {
175 const U = union(enum) {
176 a: u32,
177 b: u32,
178 c: u32,
179 };
180
181 fn doTheTest() !void {
182 var a: U = .{ .a = 100 };
183 var b: U = .{ .b = 200 };
184 var c: U = .{ .c = 300 };
185 inc: switch (a) {
186 .a => |*x| {
187 x.* += 1;
188 continue :inc b;
189 },
190 .b => |*x| {
191 x.* += 10;
192 continue :inc c;
193 },
194 .c => |*x| {
195 x.* += 50;
196 },
197 }
198 try expect(a.a == 101);
199 try expect(b.b == 210);
200 try expect(c.c == 350);
201 }
202 };
203 try S.doTheTest();
204 try comptime S.doTheTest();
205}