authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-09-04 18:31:28-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-09-04 18:31:28-07:00
log3929cac154d71a3e19fd028fc67c1d1d15823ca2
tree3ab01ff8d25313b57bbb485e30a09bc9947448f7
parent7e31804870cac14063b2468f544fc77a4cbb616f
parent289c704b60c3e4b65bc00be55266b3f1c3fc27a3
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #21257 from mlugg/computed-goto-3

compiler: implement labeled switch/continue

28 files changed, 2690 insertions(+), 824 deletions(-)

lib/std/zig/Ast.zig+56-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,25 @@ pub fn taggedUnionEnumTag(tree: Ast, node: Node.Index) full.ContainerDecl {...@@ -1895,6 +1888,25 @@ 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 const main_token = tree.nodes.items(.main_token)[node];
1894 const switch_token: TokenIndex, const label_token: ?TokenIndex = switch (tree.tokens.items(.tag)[main_token]) {
1895 .identifier => .{ main_token + 2, main_token },
1896 .keyword_switch => .{ main_token, null },
1897 else => unreachable,
1898 };
1899 const extra = tree.extraData(data.rhs, Ast.Node.SubRange);
1900 return .{
1901 .ast = .{
1902 .switch_token = switch_token,
1903 .condition = data.lhs,
1904 .cases = tree.extra_data[extra.start..extra.end],
1905 },
1906 .label_token = label_token,
1907 };
1908}
1909
1898pub fn switchCaseOne(tree: Ast, node: Node.Index) full.SwitchCase {1910pub fn switchCaseOne(tree: Ast, node: Node.Index) full.SwitchCase {
1899 const data = &tree.nodes.items(.data)[node];1911 const data = &tree.nodes.items(.data)[node];
1900 const values: *[1]Node.Index = &data.lhs;1912 const values: *[1]Node.Index = &data.lhs;
...@@ -2206,6 +2218,21 @@ fn fullContainerDeclComponents(tree: Ast, info: full.ContainerDecl.Components) f...@@ -2206,6 +2218,21 @@ fn fullContainerDeclComponents(tree: Ast, info: full.ContainerDecl.Components) f
2206 return result;2218 return result;
2207}2219}
22082220
2221fn fullSwitchComponents(tree: Ast, info: full.Switch.Components) full.Switch {
2222 const token_tags = tree.tokens.items(.tag);
2223 const tok_i = info.switch_token -| 1;
2224 var result: full.Switch = .{
2225 .ast = info,
2226 .label_token = null,
2227 };
2228 if (token_tags[tok_i] == .colon and
2229 token_tags[tok_i -| 1] == .identifier)
2230 {
2231 result.label_token = tok_i - 1;
2232 }
2233 return result;
2234}
2235
2209fn fullSwitchCaseComponents(tree: Ast, info: full.SwitchCase.Components, node: Node.Index) full.SwitchCase {2236fn fullSwitchCaseComponents(tree: Ast, info: full.SwitchCase.Components, node: Node.Index) full.SwitchCase {
2210 const token_tags = tree.tokens.items(.tag);2237 const token_tags = tree.tokens.items(.tag);
2211 const node_tags = tree.nodes.items(.tag);2238 const node_tags = tree.nodes.items(.tag);
...@@ -2477,6 +2504,13 @@ pub fn fullContainerDecl(tree: Ast, buffer: *[2]Ast.Node.Index, node: Node.Index...@@ -2477,6 +2504,13 @@ pub fn fullContainerDecl(tree: Ast, buffer: *[2]Ast.Node.Index, node: Node.Index
2477 };2504 };
2478}2505}
24792506
2507pub fn fullSwitch(tree: Ast, node: Node.Index) ?full.Switch {
2508 return switch (tree.nodes.items(.tag)[node]) {
2509 .@"switch", .switch_comma => tree.switchFull(node),
2510 else => null,
2511 };
2512}
2513
2480pub fn fullSwitchCase(tree: Ast, node: Node.Index) ?full.SwitchCase {2514pub fn fullSwitchCase(tree: Ast, node: Node.Index) ?full.SwitchCase {
2481 return switch (tree.nodes.items(.tag)[node]) {2515 return switch (tree.nodes.items(.tag)[node]) {
2482 .switch_case_one, .switch_case_inline_one => tree.switchCaseOne(node),2516 .switch_case_one, .switch_case_inline_one => tree.switchCaseOne(node),
...@@ -2829,6 +2863,17 @@ pub const full = struct {...@@ -2829,6 +2863,17 @@ pub const full = struct {
2829 };2863 };
2830 };2864 };
28312865
2866 pub const Switch = struct {
2867 ast: Components,
2868 label_token: ?TokenIndex,
2869
2870 pub const Components = struct {
2871 switch_token: TokenIndex,
2872 condition: Node.Index,
2873 cases: []const Node.Index,
2874 };
2875 };
2876
2832 pub const SwitchCase = struct {2877 pub const SwitchCase = struct {
2833 inline_token: ?TokenIndex,2878 inline_token: ?TokenIndex,
2834 /// Points to the first token after the `|`. Will either be an identifier or2879 /// Points to the first token after the `|`. Will either be an identifier or
...@@ -3243,6 +3288,7 @@ pub const Node = struct {...@@ -3243,6 +3288,7 @@ pub const Node = struct {
3243 /// main_token is the `(`.3288 /// main_token is the `(`.
3244 async_call_comma,3289 async_call_comma,
3245 /// `switch(lhs) {}`. `SubRange[rhs]`.3290 /// `switch(lhs) {}`. `SubRange[rhs]`.
3291 /// `main_token` is the identifier of a preceding label, if any; otherwise `switch`.
3246 @"switch",3292 @"switch",
3247 /// Same as switch except there is known to be a trailing comma3293 /// Same as switch except there is known to be a trailing comma
3248 /// before the final rbrace3294 /// before the final rbrace
...@@ -3287,7 +3333,8 @@ pub const Node = struct {...@@ -3287,7 +3333,8 @@ pub const Node = struct {
3287 @"suspend",3333 @"suspend",
3288 /// `resume lhs`. rhs is unused.3334 /// `resume lhs`. rhs is unused.
3289 @"resume",3335 @"resume",
3290 /// `continue`. lhs is token index of label if any. rhs is unused.3336 /// `continue :lhs rhs`
3337 /// both lhs and rhs may be omitted.
3291 @"continue",3338 @"continue",
3292 /// `break :lhs rhs`3339 /// `break :lhs rhs`
3293 /// both lhs and rhs may be omitted.3340 /// both lhs and rhs may be omitted.
lib/std/zig/AstGen.zig+109-31
...@@ -857,13 +857,10 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE...@@ -857,13 +857,10 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
857 const if_full = tree.fullIf(node).?;857 const if_full = tree.fullIf(node).?;
858 no_switch_on_err: {858 no_switch_on_err: {
859 const error_token = if_full.error_token orelse break :no_switch_on_err;859 const error_token = if_full.error_token orelse break :no_switch_on_err;
860 switch (node_tags[if_full.ast.else_expr]) {860 const full_switch = tree.fullSwitch(if_full.ast.else_expr) orelse break :no_switch_on_err;
861 .@"switch", .switch_comma => {},861 if (full_switch.label_token != null) break :no_switch_on_err;
862 else => break :no_switch_on_err,862 if (node_tags[full_switch.ast.condition] != .identifier) break :no_switch_on_err;
863 }863 if (!mem.eql(u8, tree.tokenSlice(error_token), tree.tokenSlice(main_tokens[full_switch.ast.condition]))) break :no_switch_on_err;
864 const switch_operand = node_datas[if_full.ast.else_expr].lhs;
865 if (node_tags[switch_operand] != .identifier) break :no_switch_on_err;
866 if (!mem.eql(u8, tree.tokenSlice(error_token), tree.tokenSlice(main_tokens[switch_operand]))) break :no_switch_on_err;
867 return switchExprErrUnion(gz, scope, ri.br(), node, .@"if");864 return switchExprErrUnion(gz, scope, ri.br(), node, .@"if");
868 }865 }
869 return ifExpr(gz, scope, ri.br(), node, if_full);866 return ifExpr(gz, scope, ri.br(), node, if_full);
...@@ -1060,13 +1057,10 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE...@@ -1060,13 +1057,10 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
1060 null;1057 null;
1061 no_switch_on_err: {1058 no_switch_on_err: {
1062 const capture_token = payload_token orelse break :no_switch_on_err;1059 const capture_token = payload_token orelse break :no_switch_on_err;
1063 switch (node_tags[node_datas[node].rhs]) {1060 const full_switch = tree.fullSwitch(node_datas[node].rhs) orelse break :no_switch_on_err;
1064 .@"switch", .switch_comma => {},1061 if (full_switch.label_token != null) break :no_switch_on_err;
1065 else => break :no_switch_on_err,1062 if (node_tags[full_switch.ast.condition] != .identifier) break :no_switch_on_err;
1066 }1063 if (!mem.eql(u8, tree.tokenSlice(capture_token), tree.tokenSlice(main_tokens[full_switch.ast.condition]))) break :no_switch_on_err;
1067 const switch_operand = node_datas[node_datas[node].rhs].lhs;
1068 if (node_tags[switch_operand] != .identifier) break :no_switch_on_err;
1069 if (!mem.eql(u8, tree.tokenSlice(capture_token), tree.tokenSlice(main_tokens[switch_operand]))) break :no_switch_on_err;
1070 return switchExprErrUnion(gz, scope, ri.br(), node, .@"catch");1064 return switchExprErrUnion(gz, scope, ri.br(), node, .@"catch");
1071 }1065 }
1072 switch (ri.rl) {1066 switch (ri.rl) {
...@@ -1155,7 +1149,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE...@@ -1155,7 +1149,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
1155 .error_set_decl => return errorSetDecl(gz, ri, node),1149 .error_set_decl => return errorSetDecl(gz, ri, node),
1156 .array_access => return arrayAccess(gz, scope, ri, node),1150 .array_access => return arrayAccess(gz, scope, ri, node),
1157 .@"comptime" => return comptimeExprAst(gz, scope, ri, node),1151 .@"comptime" => return comptimeExprAst(gz, scope, ri, node),
1158 .@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node),1152 .@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node, tree.fullSwitch(node).?),
11591153
1160 .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node),1154 .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node),
1161 .@"suspend" => return suspendExpr(gz, scope, node),1155 .@"suspend" => return suspendExpr(gz, scope, node),
...@@ -2245,6 +2239,11 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)...@@ -2245,6 +2239,11 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
2245 const tree = astgen.tree;2239 const tree = astgen.tree;
2246 const node_datas = tree.nodes.items(.data);2240 const node_datas = tree.nodes.items(.data);
2247 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 }
22482247
2249 // Look for the label in the scope.2248 // Look for the label in the scope.
2250 var scope = parent_scope;2249 var scope = parent_scope;
...@@ -2269,15 +2268,52 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)...@@ -2269,15 +2268,52 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
2269 if (break_label != 0) blk: {2268 if (break_label != 0) blk: {
2270 if (gen_zir.label) |*label| {2269 if (gen_zir.label) |*label| {
2271 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
2272 label.used = true;2280 label.used = true;
2281 label.used_for_continue = true;
2273 break :blk;2282 break :blk;
2274 }2283 }
2275 }2284 }
2276 // found continue but either it has a different label, or no label2285 // found continue but either it has a different label, or no label
2277 scope = gen_zir.parent;2286 scope = gen_zir.parent;
2278 continue;2287 continue;
2288 } else if (gen_zir.label) |label| {
2289 // This `continue` is unlabeled. If the gz we've found corresponds to a labeled
2290 // `switch`, ignore it and continue to parent scopes.
2291 switch (astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)]) {
2292 .switch_block, .switch_block_ref => {
2293 scope = gen_zir.parent;
2294 continue;
2295 },
2296 else => {},
2297 }
2298 }
2299
2300 if (rhs != 0) {
2301 // We need to figure out the result info to use.
2302 // The type should match
2303 const operand = try reachableExpr(parent_gz, parent_scope, gen_zir.continue_result_info, rhs, node);
2304
2305 try genDefers(parent_gz, scope, parent_scope, .normal_only);
2306
2307 // As our last action before the continue, "pop" the error trace if needed
2308 if (!gen_zir.is_comptime)
2309 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = continue_block }, .always, node);
2310
2311 _ = try parent_gz.addBreakWithSrcNode(.switch_continue, continue_block, operand, rhs);
2312 return Zir.Inst.Ref.unreachable_value;
2279 }2313 }
22802314
2315 try genDefers(parent_gz, scope, parent_scope, .normal_only);
2316
2281 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)2317 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)
2282 .break_inline2318 .break_inline
2283 else2319 else
...@@ -2295,12 +2331,7 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)...@@ -2295,12 +2331,7 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
2295 },2331 },
2296 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,2332 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
2297 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,2333 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
2298 .defer_normal => {2334 .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
2299 const defer_scope = scope.cast(Scope.Defer).?;
2300 scope = defer_scope.parent;
2301 try parent_gz.addDefer(defer_scope.index, defer_scope.len);
2302 },
2303 .defer_error => scope = scope.cast(Scope.Defer).?.parent,
2304 .namespace => break,2335 .namespace => break,
2305 .top => unreachable,2336 .top => unreachable,
2306 }2337 }
...@@ -2894,6 +2925,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As...@@ -2894,6 +2925,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
2894 .panic,2925 .panic,
2895 .trap,2926 .trap,
2896 .check_comptime_control_flow,2927 .check_comptime_control_flow,
2928 .switch_continue,
2897 => {2929 => {
2898 noreturn_src_node = statement;2930 noreturn_src_node = statement;
2899 break :b true;2931 break :b true;
...@@ -7569,7 +7601,8 @@ fn switchExpr(...@@ -7569,7 +7601,8 @@ fn switchExpr(
7569 parent_gz: *GenZir,7601 parent_gz: *GenZir,
7570 scope: *Scope,7602 scope: *Scope,
7571 ri: ResultInfo,7603 ri: ResultInfo,
7572 switch_node: Ast.Node.Index,7604 node: Ast.Node.Index,
7605 switch_full: Ast.full.Switch,
7573) InnerError!Zir.Inst.Ref {7606) InnerError!Zir.Inst.Ref {
7574 const astgen = parent_gz.astgen;7607 const astgen = parent_gz.astgen;
7575 const gpa = astgen.gpa;7608 const gpa = astgen.gpa;
...@@ -7578,14 +7611,13 @@ fn switchExpr(...@@ -7578,14 +7611,13 @@ fn switchExpr(
7578 const node_tags = tree.nodes.items(.tag);7611 const node_tags = tree.nodes.items(.tag);
7579 const main_tokens = tree.nodes.items(.main_token);7612 const main_tokens = tree.nodes.items(.main_token);
7580 const token_tags = tree.tokens.items(.tag);7613 const token_tags = tree.tokens.items(.tag);
7581 const operand_node = node_datas[switch_node].lhs;7614 const operand_node = switch_full.ast.condition;
7582 const extra = tree.extraData(node_datas[switch_node].rhs, Ast.Node.SubRange);7615 const case_nodes = switch_full.ast.cases;
7583 const case_nodes = tree.extra_data[extra.start..extra.end];
75847616
7585 const need_rl = astgen.nodes_need_rl.contains(switch_node);7617 const need_rl = astgen.nodes_need_rl.contains(node);
7586 const block_ri: ResultInfo = if (need_rl) ri else .{7618 const block_ri: ResultInfo = if (need_rl) ri else .{
7587 .rl = switch (ri.rl) {7619 .rl = switch (ri.rl) {
7588 .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, switch_node)).? },7620 .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? },
7589 .inferred_ptr => .none,7621 .inferred_ptr => .none,
7590 else => ri.rl,7622 else => ri.rl,
7591 },7623 },
...@@ -7596,11 +7628,16 @@ fn switchExpr(...@@ -7596,11 +7628,16 @@ fn switchExpr(
7596 const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?;7628 const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?;
7597 const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);7629 const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);
75987630
7631 if (switch_full.label_token) |label_token| {
7632 try astgen.checkLabelRedefinition(scope, label_token);
7633 }
7634
7599 // We perform two passes over the AST. This first pass is to collect information7635 // We perform two passes over the AST. This first pass is to collect information
7600 // for the following variables, make note of the special prong AST node index,7636 // for the following variables, make note of the special prong AST node index,
7601 // and bail out with a compile error if there are multiple special prongs present.7637 // and bail out with a compile error if there are multiple special prongs present.
7602 var any_payload_is_ref = false;7638 var any_payload_is_ref = false;
7603 var any_has_tag_capture = false;7639 var any_has_tag_capture = false;
7640 var any_non_inline_capture = false;
7604 var scalar_cases_len: u32 = 0;7641 var scalar_cases_len: u32 = 0;
7605 var multi_cases_len: u32 = 0;7642 var multi_cases_len: u32 = 0;
7606 var inline_cases_len: u32 = 0;7643 var inline_cases_len: u32 = 0;
...@@ -7618,6 +7655,15 @@ fn switchExpr(...@@ -7618,6 +7655,15 @@ fn switchExpr(
7618 if (token_tags[ident + 1] == .comma) {7655 if (token_tags[ident + 1] == .comma) {
7619 any_has_tag_capture = true;7656 any_has_tag_capture = true;
7620 }7657 }
7658
7659 // If the first capture is ignored, then there is no runtime-known
7660 // capture, as the tag capture must be for an inline prong.
7661 // This check isn't perfect, because for things like enums, the
7662 // first prong *is* comptime-known for inline prongs! But such
7663 // knowledge requires semantic analysis.
7664 if (!mem.eql(u8, tree.tokenSlice(ident), "_")) {
7665 any_non_inline_capture = true;
7666 }
7621 }7667 }
7622 // Check for else/`_` prong.7668 // Check for else/`_` prong.
7623 if (case.ast.values.len == 0) {7669 if (case.ast.values.len == 0) {
...@@ -7637,7 +7683,7 @@ fn switchExpr(...@@ -7637,7 +7683,7 @@ fn switchExpr(
7637 );7683 );
7638 } else if (underscore_src) |some_underscore| {7684 } else if (underscore_src) |some_underscore| {
7639 return astgen.failNodeNotes(7685 return astgen.failNodeNotes(
7640 switch_node,7686 node,
7641 "else and '_' prong in switch expression",7687 "else and '_' prong in switch expression",
7642 .{},7688 .{},
7643 &[_]u32{7689 &[_]u32{
...@@ -7678,7 +7724,7 @@ fn switchExpr(...@@ -7678,7 +7724,7 @@ fn switchExpr(
7678 );7724 );
7679 } else if (else_src) |some_else| {7725 } else if (else_src) |some_else| {
7680 return astgen.failNodeNotes(7726 return astgen.failNodeNotes(
7681 switch_node,7727 node,
7682 "else and '_' prong in switch expression",7728 "else and '_' prong in switch expression",
7683 .{},7729 .{},
7684 &[_]u32{7730 &[_]u32{
...@@ -7727,6 +7773,12 @@ fn switchExpr(...@@ -7727,6 +7773,12 @@ fn switchExpr(
7727 const raw_operand = try expr(parent_gz, scope, operand_ri, operand_node);7773 const raw_operand = try expr(parent_gz, scope, operand_ri, operand_node);
7728 const item_ri: ResultInfo = .{ .rl = .none };7774 const item_ri: ResultInfo = .{ .rl = .none };
77297775
7776 // If this switch is labeled, it may have `continue`s targeting it, and thus we need the operand type
7777 // to provide a result type.
7778 const raw_operand_ty_ref = if (switch_full.label_token != null) t: {
7779 break :t try parent_gz.addUnNode(.typeof, raw_operand, operand_node);
7780 } else undefined;
7781
7730 // This contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti,7782 // This contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti,
7731 // except the first cases_nodes.len slots are a table that indexes payloads later in the array, with7783 // except the first cases_nodes.len slots are a table that indexes payloads later in the array, with
7732 // the special case index coming first, then scalar_case_len indexes, then multi_cases_len indexes7784 // the special case index coming first, then scalar_case_len indexes, then multi_cases_len indexes
...@@ -7748,7 +7800,24 @@ fn switchExpr(...@@ -7748,7 +7800,24 @@ fn switchExpr(
7748 try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc);7800 try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc);
7749 // This gets added to the parent block later, after the item expressions.7801 // This gets added to the parent block later, after the item expressions.
7750 const switch_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_block_ref else .switch_block;7802 const switch_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_block_ref else .switch_block;
7751 const switch_block = try parent_gz.makeBlockInst(switch_tag, switch_node);7803 const switch_block = try parent_gz.makeBlockInst(switch_tag, node);
7804
7805 if (switch_full.label_token) |label_token| {
7806 block_scope.break_block = switch_block.toOptional();
7807 block_scope.continue_block = switch_block.toOptional();
7808 // `break_result_info` already set above
7809 block_scope.continue_result_info = .{
7810 .rl = if (any_payload_is_ref)
7811 .{ .ref_coerced_ty = raw_operand_ty_ref }
7812 else
7813 .{ .coerced_ty = raw_operand_ty_ref },
7814 };
7815
7816 block_scope.label = .{
7817 .token = label_token,
7818 .block_inst = switch_block,
7819 };
7820 }
77527821
7753 // We re-use this same scope for all cases, including the special prong, if any.7822 // We re-use this same scope for all cases, including the special prong, if any.
7754 var case_scope = parent_gz.makeSubBlock(&block_scope.base);7823 var case_scope = parent_gz.makeSubBlock(&block_scope.base);
...@@ -7953,6 +8022,11 @@ fn switchExpr(...@@ -7953,6 +8022,11 @@ fn switchExpr(
7953 appendBodyWithFixupsArrayList(astgen, payloads, case_slice);8022 appendBodyWithFixupsArrayList(astgen, payloads, case_slice);
7954 }8023 }
7955 }8024 }
8025
8026 if (switch_full.label_token) |label_token| if (!block_scope.label.?.used) {
8027 try astgen.appendErrorTok(label_token, "unused switch label", .{});
8028 };
8029
7956 // Now that the item expressions are generated we can add this.8030 // Now that the item expressions are generated we can add this.
7957 try parent_gz.instructions.append(gpa, switch_block);8031 try parent_gz.instructions.append(gpa, switch_block);
79588032
...@@ -7969,6 +8043,8 @@ fn switchExpr(...@@ -7969,6 +8043,8 @@ fn switchExpr(
7969 .has_else = special_prong == .@"else",8043 .has_else = special_prong == .@"else",
7970 .has_under = special_prong == .under,8044 .has_under = special_prong == .under,
7971 .any_has_tag_capture = any_has_tag_capture,8045 .any_has_tag_capture = any_has_tag_capture,
8046 .any_non_inline_capture = any_non_inline_capture,
8047 .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue,
7972 .scalar_cases_len = @intCast(scalar_cases_len),8048 .scalar_cases_len = @intCast(scalar_cases_len),
7973 },8049 },
7974 });8050 });
...@@ -8005,7 +8081,7 @@ fn switchExpr(...@@ -8005,7 +8081,7 @@ fn switchExpr(
8005 }8081 }
80068082
8007 if (need_result_rvalue) {8083 if (need_result_rvalue) {
8008 return rvalue(parent_gz, ri, switch_block.toRef(), switch_node);8084 return rvalue(parent_gz, ri, switch_block.toRef(), node);
8009 } else {8085 } else {
8010 return switch_block.toRef();8086 return switch_block.toRef();
8011 }8087 }
...@@ -11861,6 +11937,7 @@ const GenZir = struct {...@@ -11861,6 +11937,7 @@ const GenZir = struct {
11861 continue_block: Zir.Inst.OptionalIndex = .none,11937 continue_block: Zir.Inst.OptionalIndex = .none,
11862 /// Only valid when setBreakResultInfo is called.11938 /// Only valid when setBreakResultInfo is called.
11863 break_result_info: AstGen.ResultInfo = undefined,11939 break_result_info: AstGen.ResultInfo = undefined,
11940 continue_result_info: AstGen.ResultInfo = undefined,
1186411941
11865 suspend_node: Ast.Node.Index = 0,11942 suspend_node: Ast.Node.Index = 0,
11866 nosuspend_node: Ast.Node.Index = 0,11943 nosuspend_node: Ast.Node.Index = 0,
...@@ -11920,6 +11997,7 @@ const GenZir = struct {...@@ -11920,6 +11997,7 @@ const GenZir = struct {
11920 token: Ast.TokenIndex,11997 token: Ast.TokenIndex,
11921 block_inst: Zir.Inst.Index,11998 block_inst: Zir.Inst.Index,
11922 used: bool = false,11999 used: bool = false,
12000 used_for_continue: bool = false,
11923 };12001 };
1192412002
11925 /// Assumes nothing stacked on `gz`.12003 /// Assumes nothing stacked on `gz`.
lib/std/zig/Parse.zig+23-9
...@@ -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(label_token != 0);
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 {
...@@ -2698,7 +2699,7 @@ fn parsePrimaryTypeExpr(p: *Parse) !Node.Index {...@@ -2698,7 +2699,7 @@ fn parsePrimaryTypeExpr(p: *Parse) !Node.Index {
2698 .builtin => return p.parseBuiltinCall(),2699 .builtin => return p.parseBuiltinCall(),
2699 .keyword_fn => return p.parseFnProto(),2700 .keyword_fn => return p.parseFnProto(),
2700 .keyword_if => return p.parseIf(expectTypeExpr),2701 .keyword_if => return p.parseIf(expectTypeExpr),
2701 .keyword_switch => return p.expectSwitchExpr(),2702 .keyword_switch => return p.expectSwitchExpr(false),
27022703
2703 .keyword_extern,2704 .keyword_extern,
2704 .keyword_packed,2705 .keyword_packed,
...@@ -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(true);
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
3032fn expectSwitchExpr(p: *Parse) !Node.Index {3037fn parseSwitchExpr(p: *Parse, is_labeled: bool) !Node.Index {
3038 const switch_token = p.eatToken(.keyword_switch) orelse return null_node;
3039 return p.expectSwitchSuffix(if (is_labeled) switch_token - 2 else switch_token);
3040}
3041
3042fn expectSwitchExpr(p: *Parse, is_labeled: bool) !Node.Index {
3033 const switch_token = p.assertToken(.keyword_switch);3043 const switch_token = p.assertToken(.keyword_switch);
3044 return p.expectSwitchSuffix(if (is_labeled) switch_token - 2 else switch_token);
3045}
3046
3047fn expectSwitchSuffix(p: *Parse, main_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);
...@@ -3041,7 +3055,7 @@ fn expectSwitchExpr(p: *Parse) !Node.Index {...@@ -3041,7 +3055,7 @@ fn expectSwitchExpr(p: *Parse) !Node.Index {
30413055
3042 return p.addNode(.{3056 return p.addNode(.{
3043 .tag = if (trailing_comma) .switch_comma else .@"switch",3057 .tag = if (trailing_comma) .switch_comma else .@"switch",
3044 .main_token = switch_token,3058 .main_token = main_token,
3045 .data = .{3059 .data = .{
3046 .lhs = expr_node,3060 .lhs = expr_node,
3047 .rhs = try p.addExtra(Node.SubRange{3061 .rhs = try p.addExtra(Node.SubRange{
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,
...@@ -1293,6 +1296,7 @@ pub const Inst = struct {...@@ -1293,6 +1296,7 @@ pub const Inst = struct {
1293 .panic,1296 .panic,
1294 .trap,1297 .trap,
1295 .check_comptime_control_flow,1298 .check_comptime_control_flow,
1299 .switch_continue,
1296 => true,1300 => true,
1297 };1301 };
1298 }1302 }
...@@ -1536,6 +1540,7 @@ pub const Inst = struct {...@@ -1536,6 +1540,7 @@ pub const Inst = struct {
1536 .break_inline,1540 .break_inline,
1537 .condbr,1541 .condbr,
1538 .condbr_inline,1542 .condbr_inline,
1543 .switch_continue,
1539 .compile_error,1544 .compile_error,
1540 .ret_node,1545 .ret_node,
1541 .ret_load,1546 .ret_load,
...@@ -1621,6 +1626,7 @@ pub const Inst = struct {...@@ -1621,6 +1626,7 @@ pub const Inst = struct {
1621 .bool_br_or = .pl_node,1626 .bool_br_or = .pl_node,
1622 .@"break" = .@"break",1627 .@"break" = .@"break",
1623 .break_inline = .@"break",1628 .break_inline = .@"break",
1629 .switch_continue = .@"break",
1624 .check_comptime_control_flow = .un_node,1630 .check_comptime_control_flow = .un_node,
1625 .for_len = .pl_node,1631 .for_len = .pl_node,
1626 .call = .pl_node,1632 .call = .pl_node,
...@@ -2316,6 +2322,7 @@ pub const Inst = struct {...@@ -2316,6 +2322,7 @@ pub const Inst = struct {
2316 },2322 },
2317 @"break": struct {2323 @"break": struct {
2318 operand: Ref,2324 operand: Ref,
2325 /// Index of a `Break` payload.
2319 payload_index: u32,2326 payload_index: u32,
2320 },2327 },
2321 dbg_stmt: LineColumn,2328 dbg_stmt: LineColumn,
...@@ -2973,9 +2980,13 @@ pub const Inst = struct {...@@ -2973,9 +2980,13 @@ pub const Inst = struct {
2973 has_under: bool,2980 has_under: bool,
2974 /// If true, at least one prong has an inline tag capture.2981 /// If true, at least one prong has an inline tag capture.
2975 any_has_tag_capture: bool,2982 any_has_tag_capture: bool,
2983 /// If true, at least one prong has a capture which may not
2984 /// be comptime-known via `inline`.
2985 any_non_inline_capture: bool,
2986 has_continue: bool,
2976 scalar_cases_len: ScalarCasesLen,2987 scalar_cases_len: ScalarCasesLen,
29772988
2978 pub const ScalarCasesLen = u28;2989 pub const ScalarCasesLen = u26;
29792990
2980 pub fn specialProng(bits: Bits) SpecialProng {2991 pub fn specialProng(bits: Bits) SpecialProng {
2981 const has_else: u2 = @intFromBool(bits.has_else);2992 const has_else: u2 = @intFromBool(bits.has_else);
...@@ -3778,6 +3789,7 @@ fn findDeclsInner(...@@ -3778,6 +3789,7 @@ fn findDeclsInner(
3778 .bool_br_or,3789 .bool_br_or,
3779 .@"break",3790 .@"break",
3780 .break_inline,3791 .break_inline,
3792 .switch_continue,
3781 .check_comptime_control_flow,3793 .check_comptime_control_flow,
3782 .builtin_call,3794 .builtin_call,
3783 .cmp_lt,3795 .cmp_lt,
lib/std/zig/render.zig+24-33
...@@ -693,39 +693,27 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {...@@ -693,39 +693,27 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
693 return renderToken(r, datas[node].rhs, space);693 return renderToken(r, datas[node].rhs, space);
694 },694 },
695695
696 .@"break" => {696 .@"break", .@"continue" => {
697 const main_token = main_tokens[node];697 const main_token = main_tokens[node];
698 const label_token = datas[node].lhs;698 const label_token = datas[node].lhs;
699 const target = datas[node].rhs;699 const target = datas[node].rhs;
700 if (label_token == 0 and target == 0) {700 if (label_token == 0 and target == 0) {
701 try renderToken(r, main_token, space); // break keyword701 try renderToken(r, main_token, space); // break/continue
702 } else if (label_token == 0 and target != 0) {702 } else if (label_token == 0 and target != 0) {
703 try renderToken(r, main_token, .space); // break keyword703 try renderToken(r, main_token, .space); // break/continue
704 try renderExpression(r, target, space);704 try renderExpression(r, target, space);
705 } else if (label_token != 0 and target == 0) {705 } else if (label_token != 0 and target == 0) {
706 try renderToken(r, main_token, .space); // break keyword706 try renderToken(r, main_token, .space); // break/continue
707 try renderToken(r, label_token - 1, .none); // colon707 try renderToken(r, label_token - 1, .none); // :
708 try renderIdentifier(r, label_token, space, .eagerly_unquote); // identifier708 try renderIdentifier(r, label_token, space, .eagerly_unquote); // identifier
709 } else if (label_token != 0 and target != 0) {709 } else if (label_token != 0 and target != 0) {
710 try renderToken(r, main_token, .space); // break keyword710 try renderToken(r, main_token, .space); // break/continue
711 try renderToken(r, label_token - 1, .none); // colon711 try renderToken(r, label_token - 1, .none); // :
712 try renderIdentifier(r, label_token, .space, .eagerly_unquote); // identifier712 try renderIdentifier(r, label_token, .space, .eagerly_unquote); // identifier
713 try renderExpression(r, target, space);713 try renderExpression(r, target, space);
714 }714 }
715 },715 },
716716
717 .@"continue" => {
718 const main_token = main_tokens[node];
719 const label = datas[node].lhs;
720 if (label != 0) {
721 try renderToken(r, main_token, .space); // continue
722 try renderToken(r, label - 1, .none); // :
723 return renderIdentifier(r, label, space, .eagerly_unquote); // label
724 } else {
725 return renderToken(r, main_token, space); // continue
726 }
727 },
728
729 .@"return" => {717 .@"return" => {
730 if (datas[node].lhs != 0) {718 if (datas[node].lhs != 0) {
731 try renderToken(r, main_tokens[node], .space);719 try renderToken(r, main_tokens[node], .space);
...@@ -845,26 +833,29 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {...@@ -845,26 +833,29 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
845 .@"switch",833 .@"switch",
846 .switch_comma,834 .switch_comma,
847 => {835 => {
848 const switch_token = main_tokens[node];836 const full = tree.switchFull(node);
849 const condition = datas[node].lhs;
850 const extra = tree.extraData(datas[node].rhs, Ast.Node.SubRange);
851 const cases = tree.extra_data[extra.start..extra.end];
852 const rparen = tree.lastToken(condition) + 1;
853837
854 try renderToken(r, switch_token, .space); // switch keyword838 if (full.label_token) |label_token| {
855 try renderToken(r, switch_token + 1, .none); // lparen839 try renderIdentifier(r, label_token, .none, .eagerly_unquote); // label
856 try renderExpression(r, condition, .none); // condition expression840 try renderToken(r, label_token + 1, .space); // :
857 try renderToken(r, rparen, .space); // rparen841 }
842
843 const rparen = tree.lastToken(full.ast.condition) + 1;
844
845 try renderToken(r, full.ast.switch_token, .space); // switch
846 try renderToken(r, full.ast.switch_token + 1, .none); // (
847 try renderExpression(r, full.ast.condition, .none); // condition expression
848 try renderToken(r, rparen, .space); // )
858849
859 ais.pushIndentNextLine();850 ais.pushIndentNextLine();
860 if (cases.len == 0) {851 if (full.ast.cases.len == 0) {
861 try renderToken(r, rparen + 1, .none); // lbrace852 try renderToken(r, rparen + 1, .none); // {
862 } else {853 } else {
863 try renderToken(r, rparen + 1, .newline); // lbrace854 try renderToken(r, rparen + 1, .newline); // {
864 try renderExpressions(r, cases, .comma);855 try renderExpressions(r, full.ast.cases, .comma);
865 }856 }
866 ais.popIndent();857 ais.popIndent();
867 return renderToken(r, tree.lastToken(node), space); // rbrace858 return renderToken(r, tree.lastToken(node), space); // }
868 },859 },
869860
870 .switch_case_one,861 .switch_case_one,
src/Air.zig+37-7
...@@ -274,13 +274,15 @@ pub const Inst = struct {...@@ -274,13 +274,15 @@ pub const Inst = struct {
274 /// is to encounter a `br` that targets this `block`. If the `block` type is `noreturn`,274 /// is to encounter a `br` that targets this `block`. If the `block` type is `noreturn`,
275 /// then there do not exist any `br` instructions targeting this `block`.275 /// then there do not exist any `br` instructions targeting this `block`.
276 block,276 block,
277 /// A labeled block of code that loops forever. At the end of the body it is implied277 /// A labeled block of code that loops forever. The body must be `noreturn`: loops
278 /// to repeat; no explicit "repeat" instruction terminates loop bodies.278 /// occur through an explicit `repeat` instruction pointing back to this one.
279 /// Result type is always `noreturn`; no instructions in a block follow this one.279 /// Result type is always `noreturn`; no instructions in a block follow this one.
280 /// The body never ends with a `noreturn` instruction, so the "repeat" operation280 /// There is always at least one `repeat` instruction referencing the loop.
281 /// is always statically reachable.
282 /// Uses the `ty_pl` field. Payload is `Block`.281 /// Uses the `ty_pl` field. Payload is `Block`.
283 loop,282 loop,
283 /// Sends control flow back to the beginning of a parent `loop` body.
284 /// Uses the `repeat` field.
285 repeat,
284 /// Return from a block with a result.286 /// Return from a block with a result.
285 /// Result type is always noreturn; no instructions in a block follow this one.287 /// Result type is always noreturn; no instructions in a block follow this one.
286 /// Uses the `br` field.288 /// Uses the `br` field.
...@@ -427,6 +429,14 @@ pub const Inst = struct {...@@ -427,6 +429,14 @@ pub const Inst = struct {
427 /// 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.
428 /// 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`.
429 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,
430 /// Given an operand which is an error union, splits control flow. In440 /// Given an operand which is an error union, splits control flow. In
431 /// 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
432 /// instruction, which is guaranteed to end with a return instruction442 /// instruction, which is guaranteed to end with a return instruction
...@@ -1045,6 +1055,9 @@ pub const Inst = struct {...@@ -1045,6 +1055,9 @@ pub const Inst = struct {
1045 block_inst: Index,1055 block_inst: Index,
1046 operand: Ref,1056 operand: Ref,
1047 },1057 },
1058 repeat: struct {
1059 loop_inst: Index,
1060 },
1048 pl_op: struct {1061 pl_op: struct {
1049 operand: Ref,1062 operand: Ref,
1050 payload: u32,1063 payload: u32,
...@@ -1143,10 +1156,12 @@ pub const SwitchBr = struct {...@@ -1143,10 +1156,12 @@ pub const SwitchBr = struct {
1143 else_body_len: u32,1156 else_body_len: u32,
11441157
1145 /// Trailing:1158 /// Trailing:
1146 /// * item: Inst.Ref // for each `items_len`.1159 /// * item: Inst.Ref // for each `items_len`
1147 /// * instruction index for each `body_len`.1160 /// * { range_start: Inst.Ref, range_end: Inst.Ref } // for each `ranges_len`
1161 /// * body_inst: Inst.Index // for each `body_len`
1148 pub const Case = struct {1162 pub const Case = struct {
1149 items_len: u32,1163 items_len: u32,
1164 ranges_len: u32,
1150 body_len: u32,1165 body_len: u32,
1151 };1166 };
1152};1167};
...@@ -1443,9 +1458,12 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)...@@ -1443,9 +1458,12 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
1443 => return datas[@intFromEnum(inst)].ty_op.ty.toType(),1458 => return datas[@intFromEnum(inst)].ty_op.ty.toType(),
14441459
1445 .loop,1460 .loop,
1461 .repeat,
1446 .br,1462 .br,
1447 .cond_br,1463 .cond_br,
1448 .switch_br,1464 .switch_br,
1465 .loop_switch_br,
1466 .switch_dispatch,
1449 .ret,1467 .ret,
1450 .ret_safe,1468 .ret_safe,
1451 .ret_load,1469 .ret_load,
...@@ -1600,6 +1618,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {...@@ -1600,6 +1618,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
1600 .arg,1618 .arg,
1601 .block,1619 .block,
1602 .loop,1620 .loop,
1621 .repeat,
1603 .br,1622 .br,
1604 .trap,1623 .trap,
1605 .breakpoint,1624 .breakpoint,
...@@ -1609,6 +1628,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {...@@ -1609,6 +1628,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
1609 .call_never_inline,1628 .call_never_inline,
1610 .cond_br,1629 .cond_br,
1611 .switch_br,1630 .switch_br,
1631 .loop_switch_br,
1632 .switch_dispatch,
1612 .@"try",1633 .@"try",
1613 .try_cold,1634 .try_cold,
1614 .try_ptr,1635 .try_ptr,
...@@ -1862,6 +1883,10 @@ pub const UnwrappedSwitch = struct {...@@ -1862,6 +1883,10 @@ pub const UnwrappedSwitch = struct {
1862 var extra_index = extra.end;1883 var extra_index = extra.end;
1863 const items: []const Inst.Ref = @ptrCast(it.air.extra[extra_index..][0..extra.data.items_len]);1884 const items: []const Inst.Ref = @ptrCast(it.air.extra[extra_index..][0..extra.data.items_len]);
1864 extra_index += items.len;1885 extra_index += items.len;
1886 // TODO: ptrcast from []const Inst.Ref to []const [2]Inst.Ref when supported
1887 const ranges_ptr: [*]const [2]Inst.Ref = @ptrCast(it.air.extra[extra_index..]);
1888 const ranges: []const [2]Inst.Ref = ranges_ptr[0..extra.data.ranges_len];
1889 extra_index += ranges.len * 2;
1865 const body: []const Inst.Index = @ptrCast(it.air.extra[extra_index..][0..extra.data.body_len]);1890 const body: []const Inst.Index = @ptrCast(it.air.extra[extra_index..][0..extra.data.body_len]);
1866 extra_index += body.len;1891 extra_index += body.len;
1867 it.extra_index = @intCast(extra_index);1892 it.extra_index = @intCast(extra_index);
...@@ -1869,6 +1894,7 @@ pub const UnwrappedSwitch = struct {...@@ -1869,6 +1894,7 @@ pub const UnwrappedSwitch = struct {
1869 return .{1894 return .{
1870 .idx = idx,1895 .idx = idx,
1871 .items = items,1896 .items = items,
1897 .ranges = ranges,
1872 .body = body,1898 .body = body,
1873 };1899 };
1874 }1900 }
...@@ -1881,6 +1907,7 @@ pub const UnwrappedSwitch = struct {...@@ -1881,6 +1907,7 @@ pub const UnwrappedSwitch = struct {
1881 pub const Case = struct {1907 pub const Case = struct {
1882 idx: u32,1908 idx: u32,
1883 items: []const Inst.Ref,1909 items: []const Inst.Ref,
1910 ranges: []const [2]Inst.Ref,
1884 body: []const Inst.Index,1911 body: []const Inst.Index,
1885 };1912 };
1886 };1913 };
...@@ -1888,7 +1915,10 @@ pub const UnwrappedSwitch = struct {...@@ -1888,7 +1915,10 @@ pub const UnwrappedSwitch = struct {
18881915
1889pub fn unwrapSwitch(air: *const Air, switch_inst: Inst.Index) UnwrappedSwitch {1916pub fn unwrapSwitch(air: *const Air, switch_inst: Inst.Index) UnwrappedSwitch {
1890 const inst = air.instructions.get(@intFromEnum(switch_inst));1917 const inst = air.instructions.get(@intFromEnum(switch_inst));
1891 assert(inst.tag == .switch_br);1918 switch (inst.tag) {
1919 .switch_br, .loop_switch_br => {},
1920 else => unreachable, // assertion failure
1921 }
1892 const pl_op = inst.data.pl_op;1922 const pl_op = inst.data.pl_op;
1893 const extra = air.extraData(SwitchBr, pl_op.payload);1923 const extra = air.extraData(SwitchBr, pl_op.payload);
1894 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+7-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,12 +380,16 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {...@@ -380,12 +380,16 @@ 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();
387 while (it.next()) |case| {387 while (it.next()) |case| {
388 for (case.items) |item| if (!checkRef(item, zcu)) return false;388 for (case.items) |item| if (!checkRef(item, zcu)) return false;
389 for (case.ranges) |range| {
390 if (!checkRef(range[0], zcu)) return false;
391 if (!checkRef(range[1], zcu)) return false;
392 }
389 if (!checkBody(air, case.body, zcu)) return false;393 if (!checkBody(air, case.body, zcu)) return false;
390 }394 }
391 if (!checkBody(air, it.elseBody(), zcu)) return false;395 if (!checkBody(air, it.elseBody(), zcu)) return false;
...@@ -416,6 +420,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {...@@ -416,6 +420,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
416 .dbg_stmt,420 .dbg_stmt,
417 .err_return_trace,421 .err_return_trace,
418 .save_err_return_trace_index,422 .save_err_return_trace_index,
423 .repeat,
419 => {},424 => {},
420 }425 }
421 }426 }
src/Liveness.zig+221-90
...@@ -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,9 +69,10 @@ pub const Block = struct {...@@ -68,9 +69,10 @@ 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 block which the loop body contains a `br` to.74 /// * Every outer block which the loop body contains a `br` to.
75 /// * Every outer loop which the loop body contains a `repeat` to.
74 /// * 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.
75 /// This gives the main analysis pass enough information to determine the full set of77 /// This gives the main analysis pass enough information to determine the full set of
76 /// instructions which need to be alive when a loop repeats. This data is TEMPORARILY stored in78 /// instructions which need to be alive when a loop repeats. This data is TEMPORARILY stored in
...@@ -89,7 +91,9 @@ fn LivenessPassData(comptime pass: LivenessPass) type {...@@ -89,7 +91,9 @@ fn LivenessPassData(comptime pass: LivenessPass) type {
89 return switch (pass) {91 return switch (pass) {
90 .loop_analysis => struct {92 .loop_analysis => struct {
91 /// 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
92 /// body and which we are currently within.94 /// body and which we are currently within. Also includes `loop`s which are the target
95 /// of a `repeat` instruction, and `loop_switch_br`s which are the target of a
96 /// `switch_dispatch` instruction.
93 breaks: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},97 breaks: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{},
9498
95 /// 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.
...@@ -102,7 +106,7 @@ fn LivenessPassData(comptime pass: LivenessPass) type {...@@ -102,7 +106,7 @@ fn LivenessPassData(comptime pass: LivenessPass) type {
102 },106 },
103107
104 .main_analysis => struct {108 .main_analysis => struct {
105 /// Every `block` currently under analysis.109 /// Every `block` and `loop` currently under analysis.
106 block_scopes: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockScope) = .{},110 block_scopes: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockScope) = .{},
107111
108 /// The set of instructions currently alive in the current control112 /// The set of instructions currently alive in the current control
...@@ -114,7 +118,8 @@ fn LivenessPassData(comptime pass: LivenessPass) type {...@@ -114,7 +118,8 @@ fn LivenessPassData(comptime pass: LivenessPass) type {
114 old_extra: std.ArrayListUnmanaged(u32) = .{},118 old_extra: std.ArrayListUnmanaged(u32) = .{},
115119
116 const BlockScope = struct {120 const BlockScope = struct {
117 /// The set of instructions which are alive upon a `br` to this block.121 /// If this is a `block`, these instructions are alive upon a `br` to this block.
122 /// If this is a `loop`, these instructions are alive upon a `repeat` to this block.
118 live_set: std.AutoHashMapUnmanaged(Air.Inst.Index, void),123 live_set: std.AutoHashMapUnmanaged(Air.Inst.Index, void),
119 };124 };
120125
...@@ -326,6 +331,8 @@ pub fn categorizeOperand(...@@ -326,6 +331,8 @@ pub fn categorizeOperand(
326 .ret_ptr,331 .ret_ptr,
327 .trap,332 .trap,
328 .breakpoint,333 .breakpoint,
334 .repeat,
335 .switch_dispatch,
329 .dbg_stmt,336 .dbg_stmt,
330 .unreach,337 .unreach,
331 .ret_addr,338 .ret_addr,
...@@ -658,21 +665,17 @@ pub fn categorizeOperand(...@@ -658,21 +665,17 @@ pub fn categorizeOperand(
658665
659 return .complex;666 return .complex;
660 },667 },
661 .@"try", .try_cold => {668
662 return .complex;669 .@"try",
663 },670 .try_cold,
664 .try_ptr, .try_ptr_cold => {671 .try_ptr,
665 return .complex;672 .try_ptr_cold,
666 },673 .loop,
667 .loop => {674 .cond_br,
668 return .complex;675 .switch_br,
669 },676 .loop_switch_br,
670 .cond_br => {677 => return .complex,
671 return .complex;678
672 },
673 .switch_br => {
674 return .complex;
675 },
676 .wasm_memory_grow => {679 .wasm_memory_grow => {
677 const pl_op = air_datas[@intFromEnum(inst)].pl_op;680 const pl_op = air_datas[@intFromEnum(inst)].pl_op;
678 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);
...@@ -1201,6 +1204,8 @@ fn analyzeInst(...@@ -1201,6 +1204,8 @@ fn analyzeInst(
1201 },1204 },
12021205
1203 .br => return analyzeInstBr(a, pass, data, inst),1206 .br => return analyzeInstBr(a, pass, data, inst),
1207 .repeat => return analyzeInstRepeat(a, pass, data, inst),
1208 .switch_dispatch => return analyzeInstSwitchDispatch(a, pass, data, inst),
12041209
1205 .assembly => {1210 .assembly => {
1206 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);
...@@ -1257,7 +1262,8 @@ fn analyzeInst(...@@ -1257,7 +1262,8 @@ fn analyzeInst(
1257 .@"try", .try_cold => return analyzeInstCondBr(a, pass, data, inst, .@"try"),1262 .@"try", .try_cold => return analyzeInstCondBr(a, pass, data, inst, .@"try"),
1258 .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),
1259 .cond_br => return analyzeInstCondBr(a, pass, data, inst, .cond_br),1264 .cond_br => return analyzeInstCondBr(a, pass, data, inst, .cond_br),
1260 .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),
12611267
1262 .wasm_memory_grow => {1268 .wasm_memory_grow => {
1263 const pl_op = inst_datas[@intFromEnum(inst)].pl_op;1269 const pl_op = inst_datas[@intFromEnum(inst)].pl_op;
...@@ -1380,6 +1386,62 @@ fn analyzeInstBr(...@@ -1380,6 +1386,62 @@ fn analyzeInstBr(
1380 return analyzeOperands(a, pass, data, inst, .{ br.operand, .none, .none });1386 return analyzeOperands(a, pass, data, inst, .{ br.operand, .none, .none });
1381}1387}
13821388
1389fn analyzeInstRepeat(
1390 a: *Analysis,
1391 comptime pass: LivenessPass,
1392 data: *LivenessPassData(pass),
1393 inst: Air.Inst.Index,
1394) !void {
1395 const inst_datas = a.air.instructions.items(.data);
1396 const repeat = inst_datas[@intFromEnum(inst)].repeat;
1397 const gpa = a.gpa;
1398
1399 switch (pass) {
1400 .loop_analysis => {
1401 try data.breaks.put(gpa, repeat.loop_inst, {});
1402 },
1403
1404 .main_analysis => {
1405 const block_scope = data.block_scopes.get(repeat.loop_inst).?; // we should always be repeating an enclosing loop
1406
1407 const new_live_set = try block_scope.live_set.clone(gpa);
1408 data.live_set.deinit(gpa);
1409 data.live_set = new_live_set;
1410 },
1411 }
1412
1413 return analyzeOperands(a, pass, data, inst, .{ .none, .none, .none });
1414}
1415
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
1383fn analyzeInstBlock(1445fn analyzeInstBlock(
1384 a: *Analysis,1446 a: *Analysis,
1385 comptime pass: LivenessPass,1447 comptime pass: LivenessPass,
...@@ -1402,8 +1464,10 @@ fn analyzeInstBlock(...@@ -1402,8 +1464,10 @@ fn analyzeInstBlock(
14021464
1403 .main_analysis => {1465 .main_analysis => {
1404 log.debug("[{}] %{}: block live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });1466 log.debug("[{}] %{}: block live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
1467 // We can move the live set because the body should have a noreturn
1468 // instruction which overrides the set.
1405 try data.block_scopes.put(gpa, inst, .{1469 try data.block_scopes.put(gpa, inst, .{
1406 .live_set = try data.live_set.clone(gpa),1470 .live_set = data.live_set.move(),
1407 });1471 });
1408 defer {1472 defer {
1409 log.debug("[{}] %{}: popped block scope", .{ pass, inst });1473 log.debug("[{}] %{}: popped block scope", .{ pass, inst });
...@@ -1448,6 +1512,102 @@ fn analyzeInstBlock(...@@ -1448,6 +1512,102 @@ fn analyzeInstBlock(
1448 }1512 }
1449}1513}
14501514
1515fn writeLoopInfo(
1516 a: *Analysis,
1517 data: *LivenessPassData(.loop_analysis),
1518 inst: Air.Inst.Index,
1519 old_breaks: std.AutoHashMapUnmanaged(Air.Inst.Index, void),
1520 old_live: std.AutoHashMapUnmanaged(Air.Inst.Index, void),
1521) !void {
1522 const gpa = a.gpa;
1523
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));
1529
1530 const extra_index: u32 = @intCast(a.extra.items.len);
1531
1532 const num_breaks = data.breaks.count();
1533 try a.extra.ensureUnusedCapacity(gpa, 1 + num_breaks);
1534
1535 a.extra.appendAssumeCapacity(num_breaks);
1536
1537 var it = data.breaks.keyIterator();
1538 while (it.next()) |key| {
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) });
1543
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);
1547
1548 a.extra.appendAssumeCapacity(num_live);
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) });
1555
1556 try a.special.put(gpa, inst, extra_index);
1557
1558 // Add back operands which were previously alive
1559 it = old_live.keyIterator();
1560 while (it.next()) |key| {
1561 const alive = key.*;
1562 try data.live_set.put(gpa, alive, {});
1563 }
1564
1565 // And the same for breaks
1566 it = old_breaks.keyIterator();
1567 while (it.next()) |key| {
1568 const block_inst = key.*;
1569 try data.breaks.put(gpa, block_inst, {});
1570 }
1571}
1572
1573/// When analyzing a loop in the main pass, sets up `data.live_set` to be the set
1574/// of operands known to be alive when the loop repeats.
1575fn resolveLoopLiveSet(
1576 a: *Analysis,
1577 data: *LivenessPassData(.main_analysis),
1578 inst: Air.Inst.Index,
1579) !void {
1580 const gpa = a.gpa;
1581
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]);
1585
1586 const num_loop_live = data.old_extra.items[extra_idx + num_breaks + 1];
1587 const loop_live: []const Air.Inst.Index = @ptrCast(data.old_extra.items[extra_idx + num_breaks + 2 ..][0..num_loop_live]);
1588
1589 // This is necessarily not in the same control flow branch, because loops are noreturn
1590 data.live_set.clearRetainingCapacity();
1591
1592 try data.live_set.ensureUnusedCapacity(gpa, @intCast(loop_live.len));
1593 for (loop_live) |alive| data.live_set.putAssumeCapacity(alive, {});
1594
1595 log.debug("[{}] %{}: block live set is {}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) });
1596
1597 for (breaks) |block_inst| {
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).?;
1600
1601 var it = block_scope.live_set.keyIterator();
1602 while (it.next()) |key| {
1603 const alive = key.*;
1604 try data.live_set.put(gpa, alive, {});
1605 }
1606 }
1607
1608 log.debug("[{}] %{}: loop live set is {}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) });
1609}
1610
1451fn analyzeInstLoop(1611fn analyzeInstLoop(
1452 a: *Analysis,1612 a: *Analysis,
1453 comptime pass: LivenessPass,1613 comptime pass: LivenessPass,
...@@ -1471,78 +1631,22 @@ fn analyzeInstLoop(...@@ -1471,78 +1631,22 @@ fn analyzeInstLoop(
14711631
1472 try analyzeBody(a, pass, data, body);1632 try analyzeBody(a, pass, data, body);
14731633
1474 const num_breaks = data.breaks.count();1634 try writeLoopInfo(a, data, inst, old_breaks, old_live);
1475 try a.extra.ensureUnusedCapacity(gpa, 1 + num_breaks);
1476
1477 const extra_index = @as(u32, @intCast(a.extra.items.len));
1478 a.extra.appendAssumeCapacity(num_breaks);
1479
1480 var it = data.breaks.keyIterator();
1481 while (it.next()) |key| {
1482 const block_inst = key.*;
1483 a.extra.appendAssumeCapacity(@intFromEnum(block_inst));
1484 }
1485 log.debug("[{}] %{}: includes breaks to {}", .{ pass, inst, fmtInstSet(&data.breaks) });
1486
1487 // Now we put the live operands from the loop body in too
1488 const num_live = data.live_set.count();
1489 try a.extra.ensureUnusedCapacity(gpa, 1 + num_live);
1490
1491 a.extra.appendAssumeCapacity(num_live);
1492 it = data.live_set.keyIterator();
1493 while (it.next()) |key| {
1494 const alive = key.*;
1495 a.extra.appendAssumeCapacity(@intFromEnum(alive));
1496 }
1497 log.debug("[{}] %{}: maintain liveness of {}", .{ pass, inst, fmtInstSet(&data.live_set) });
1498
1499 try a.special.put(gpa, inst, extra_index);
1500
1501 // Add back operands which were previously alive
1502 it = old_live.keyIterator();
1503 while (it.next()) |key| {
1504 const alive = key.*;
1505 try data.live_set.put(gpa, alive, {});
1506 }
1507
1508 // And the same for breaks
1509 it = old_breaks.keyIterator();
1510 while (it.next()) |key| {
1511 const block_inst = key.*;
1512 try data.breaks.put(gpa, block_inst, {});
1513 }
1514 },1635 },
15151636
1516 .main_analysis => {1637 .main_analysis => {
1517 const extra_idx = a.special.fetchRemove(inst).?.value; // remove because this data does not exist after analysis1638 try resolveLoopLiveSet(a, data, inst);
1518
1519 const num_breaks = data.old_extra.items[extra_idx];
1520 const breaks: []const Air.Inst.Index = @ptrCast(data.old_extra.items[extra_idx + 1 ..][0..num_breaks]);
15211639
1522 const num_loop_live = data.old_extra.items[extra_idx + num_breaks + 1];1640 // Now, `data.live_set` is the operands which must be alive when the loop repeats.
1523 const loop_live: []const Air.Inst.Index = @ptrCast(data.old_extra.items[extra_idx + num_breaks + 2 ..][0..num_loop_live]);1641 // Move them into a block scope for corresponding `repeat` instructions to notice.
15241642 try data.block_scopes.putNoClobber(gpa, inst, .{
1525 // This is necessarily not in the same control flow branch, because loops are noreturn1643 .live_set = data.live_set.move(),
1526 data.live_set.clearRetainingCapacity();1644 });
15271645 defer {
1528 try data.live_set.ensureUnusedCapacity(gpa, @intCast(loop_live.len));1646 log.debug("[{}] %{}: popped loop block scop", .{ pass, inst });
1529 for (loop_live) |alive| {1647 var scope = data.block_scopes.fetchRemove(inst).?.value;
1530 data.live_set.putAssumeCapacity(alive, {});1648 scope.live_set.deinit(gpa);
1531 }
1532
1533 log.debug("[{}] %{}: block live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) });
1534
1535 for (breaks) |block_inst| {
1536 // We might break to this block, so include every operand that the block needs alive
1537 const block_scope = data.block_scopes.get(block_inst).?;
1538
1539 var it = block_scope.live_set.keyIterator();
1540 while (it.next()) |key| {
1541 const alive = key.*;
1542 try data.live_set.put(gpa, alive, {});
1543 }
1544 }1649 }
1545
1546 try analyzeBody(a, pass, data, body);1650 try analyzeBody(a, pass, data, body);
1547 },1651 },
1548 }1652 }
...@@ -1670,6 +1774,7 @@ fn analyzeInstSwitchBr(...@@ -1670,6 +1774,7 @@ fn analyzeInstSwitchBr(
1670 comptime pass: LivenessPass,1774 comptime pass: LivenessPass,
1671 data: *LivenessPassData(pass),1775 data: *LivenessPassData(pass),
1672 inst: Air.Inst.Index,1776 inst: Air.Inst.Index,
1777 is_dispatch_loop: bool,
1673) !void {1778) !void {
1674 const inst_datas = a.air.instructions.items(.data);1779 const inst_datas = a.air.instructions.items(.data);
1675 const pl_op = inst_datas[@intFromEnum(inst)].pl_op;1780 const pl_op = inst_datas[@intFromEnum(inst)].pl_op;
...@@ -1680,6 +1785,17 @@ fn analyzeInstSwitchBr(...@@ -1680,6 +1785,17 @@ fn analyzeInstSwitchBr(
16801785
1681 switch (pass) {1786 switch (pass) {
1682 .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
1683 var it = switch_br.iterateCases();1799 var it = switch_br.iterateCases();
1684 while (it.next()) |case| {1800 while (it.next()) |case| {
1685 try analyzeBody(a, pass, data, case.body);1801 try analyzeBody(a, pass, data, case.body);
...@@ -1688,9 +1804,24 @@ fn analyzeInstSwitchBr(...@@ -1688,9 +1804,24 @@ fn analyzeInstSwitchBr(
1688 const else_body = it.elseBody();1804 const else_body = it.elseBody();
1689 try analyzeBody(a, pass, data, else_body);1805 try analyzeBody(a, pass, data, else_body);
1690 }1806 }
1807
1808 if (is_dispatch_loop) {
1809 try writeLoopInfo(a, data, inst, old_breaks, old_live);
1810 }
1691 },1811 },
16921812
1693 .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 };
1694 // 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
1695 // to understand it, I encourage looking at `analyzeInstCondBr` first.1826 // to understand it, I encourage looking at `analyzeInstCondBr` first.
16961827
src/Liveness/Verify.zig+53-14
...@@ -1,28 +1,38 @@...@@ -1,28 +1,38 @@
1//! Verifies that liveness information is valid.1//! Verifies that Liveness information is valid.
22
3gpa: std.mem.Allocator,3gpa: std.mem.Allocator,
4air: Air,4air: Air,
5liveness: Liveness,5liveness: Liveness,
6live: LiveMap = .{},6live: LiveMap = .{},
7blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, LiveMap) = .{},7blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, LiveMap) = .{},
8loops: std.AutoHashMapUnmanaged(Air.Inst.Index, LiveMap) = .{},
8intern_pool: *const InternPool,9intern_pool: *const InternPool,
910
10pub const Error = error{ LivenessInvalid, OutOfMemory };11pub const Error = error{ LivenessInvalid, OutOfMemory };
1112
12pub fn deinit(self: *Verify) void {13pub fn deinit(self: *Verify) void {
13 self.live.deinit(self.gpa);14 self.live.deinit(self.gpa);
14 var block_it = self.blocks.valueIterator();15 {
15 while (block_it.next()) |block| block.deinit(self.gpa);16 var it = self.blocks.valueIterator();
16 self.blocks.deinit(self.gpa);17 while (it.next()) |block| block.deinit(self.gpa);
18 self.blocks.deinit(self.gpa);
19 }
20 {
21 var it = self.loops.valueIterator();
22 while (it.next()) |block| block.deinit(self.gpa);
23 self.loops.deinit(self.gpa);
24 }
17 self.* = undefined;25 self.* = undefined;
18}26}
1927
20pub fn verify(self: *Verify) Error!void {28pub fn verify(self: *Verify) Error!void {
21 self.live.clearRetainingCapacity();29 self.live.clearRetainingCapacity();
22 self.blocks.clearRetainingCapacity();30 self.blocks.clearRetainingCapacity();
31 self.loops.clearRetainingCapacity();
23 try self.verifyBody(self.air.getMainBody());32 try self.verifyBody(self.air.getMainBody());
24 // We don't care about `self.live` now, because the loop body was noreturn - everything being dead was checked on `ret` etc33 // We don't care about `self.live` now, because the loop body was noreturn - everything being dead was checked on `ret` etc
25 assert(self.blocks.count() == 0);34 assert(self.blocks.count() == 0);
35 assert(self.loops.count() == 0);
26}36}
2737
28const LiveMap = std.AutoHashMapUnmanaged(Air.Inst.Index, void);38const LiveMap = std.AutoHashMapUnmanaged(Air.Inst.Index, void);
...@@ -430,6 +440,23 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -430,6 +440,23 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
430 }440 }
431 try self.verifyInst(inst);441 try self.verifyInst(inst);
432 },442 },
443 .repeat => {
444 const repeat = data[@intFromEnum(inst)].repeat;
445 const expected_live = self.loops.get(repeat.loop_inst) orelse
446 return invalid("%{}: loop %{} not in scope", .{ @intFromEnum(inst), @intFromEnum(repeat.loop_inst) });
447
448 try self.verifyMatchingLiveness(repeat.loop_inst, expected_live);
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 },
433 .block, .dbg_inline_block => |tag| {460 .block, .dbg_inline_block => |tag| {
434 const ty_pl = data[@intFromEnum(inst)].ty_pl;461 const ty_pl = data[@intFromEnum(inst)].ty_pl;
435 const block_ty = ty_pl.ty.toType();462 const block_ty = ty_pl.ty.toType();
...@@ -475,14 +502,17 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -475,14 +502,17 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
475 const extra = self.air.extraData(Air.Block, ty_pl.payload);502 const extra = self.air.extraData(Air.Block, ty_pl.payload);
476 const loop_body: []const Air.Inst.Index = @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len]);503 const loop_body: []const Air.Inst.Index = @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len]);
477504
478 var live = try self.live.clone(self.gpa);505 // The same stuff should be alive after the loop as before it.
479 defer live.deinit(self.gpa);506 const gop = try self.loops.getOrPut(self.gpa, inst);
507 if (gop.found_existing) return invalid("%{}: loop already exists", .{@intFromEnum(inst)});
508 defer {
509 var live = self.loops.fetchRemove(inst).?;
510 live.value.deinit(self.gpa);
511 }
512 gop.value_ptr.* = try self.live.clone(self.gpa);
480513
481 try self.verifyBody(loop_body);514 try self.verifyBody(loop_body);
482515
483 // The same stuff should be alive after the loop as before it
484 try self.verifyMatchingLiveness(inst, live);
485
486 try self.verifyInstOperands(inst, .{ .none, .none, .none });516 try self.verifyInstOperands(inst, .{ .none, .none, .none });
487 },517 },
488 .cond_br => {518 .cond_br => {
...@@ -508,7 +538,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -508,7 +538,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
508538
509 try self.verifyInst(inst);539 try self.verifyInst(inst);
510 },540 },
511 .switch_br => {541 .switch_br, .loop_switch_br => {
512 const switch_br = self.air.unwrapSwitch(inst);542 const switch_br = self.air.unwrapSwitch(inst);
513 const switch_br_liveness = try self.liveness.getSwitchBr(543 const switch_br_liveness = try self.liveness.getSwitchBr(
514 self.gpa,544 self.gpa,
...@@ -519,13 +549,22 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -519,13 +549,22 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
519549
520 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));
521551
522 var live = self.live.move();552 // Excluding the operand (which we just handled), the same stuff should be alive
523 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 }
524563
525 var it = switch_br.iterateCases();564 var it = switch_br.iterateCases();
526 while (it.next()) |case| {565 while (it.next()) |case| {
527 self.live.deinit(self.gpa);566 self.live.deinit(self.gpa);
528 self.live = try live.clone(self.gpa);567 self.live = try self.loops.get(inst).?.clone(self.gpa);
529568
530 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);
531 try self.verifyBody(case.body);570 try self.verifyBody(case.body);
...@@ -534,7 +573,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -534,7 +573,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
534 const else_body = it.elseBody();573 const else_body = it.elseBody();
535 if (else_body.len > 0) {574 if (else_body.len > 0) {
536 self.live.deinit(self.gpa);575 self.live.deinit(self.gpa);
537 self.live = try live.clone(self.gpa);576 self.live = try self.loops.get(inst).?.clone(self.gpa);
538 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);
539 try self.verifyBody(else_body);578 try self.verifyBody(else_body);
540 }579 }
src/Sema.zig+639-370
...@@ -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
...@@ -1563,6 +1580,8 @@ fn analyzeBodyInner(...@@ -1563,6 +1580,8 @@ fn analyzeBodyInner(
1563 // We are definitely called by `zirLoop`, which will treat the1580 // We are definitely called by `zirLoop`, which will treat the
1564 // fact that this body does not terminate `noreturn` as an1581 // fact that this body does not terminate `noreturn` as an
1565 // implicit repeat.1582 // implicit repeat.
1583 // TODO: since AIR has `repeat` now, we could change ZIR to generate
1584 // more optimal code utilizing `repeat` instructions across blocks!
1566 break;1585 break;
1567 }1586 }
1568 },1587 },
...@@ -1573,6 +1592,13 @@ fn analyzeBodyInner(...@@ -1573,6 +1592,13 @@ fn analyzeBodyInner(
1573 i = 0;1592 i = 0;
1574 continue;1593 continue;
1575 },1594 },
1595 .switch_continue => if (block.is_comptime) {
1596 sema.comptime_break_inst = inst;
1597 return error.ComptimeBreak;
1598 } else {
1599 try sema.zirSwitchContinue(block, inst);
1600 break;
1601 },
1576 .loop => blk: {1602 .loop => blk: {
1577 if (!block.is_comptime) break :blk try sema.zirLoop(block, inst);1603 if (!block.is_comptime) break :blk try sema.zirLoop(block, inst);
1578 // Same as `block_inline`. TODO https://github.com/ziglang/zig/issues/82201604 // Same as `block_inline`. TODO https://github.com/ziglang/zig/issues/8220
...@@ -5884,17 +5910,30 @@ fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError...@@ -5884,17 +5910,30 @@ fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError
5884 // Use `analyzeBodyInner` directly to push any comptime control flow up the stack.5910 // Use `analyzeBodyInner` directly to push any comptime control flow up the stack.
5885 try sema.analyzeBodyInner(&loop_block, body);5911 try sema.analyzeBodyInner(&loop_block, body);
58865912
5913 // TODO: since AIR has `repeat` now, we could change ZIR to generate
5914 // more optimal code utilizing `repeat` instructions across blocks!
5915 // For now, if the generated loop body does not terminate `noreturn`,
5916 // then `analyzeBodyInner` is signalling that it ended with `repeat`.
5917
5887 const loop_block_len = loop_block.instructions.items.len;5918 const loop_block_len = loop_block.instructions.items.len;
5888 if (loop_block_len > 0 and sema.typeOf(loop_block.instructions.items[loop_block_len - 1].toRef()).isNoReturn(zcu)) {5919 if (loop_block_len > 0 and sema.typeOf(loop_block.instructions.items[loop_block_len - 1].toRef()).isNoReturn(zcu)) {
5889 // If the loop ended with a noreturn terminator, then there is no way for it to loop,5920 // If the loop ended with a noreturn terminator, then there is no way for it to loop,
5890 // so we can just use the block instead.5921 // so we can just use the block instead.
5891 try child_block.instructions.appendSlice(gpa, loop_block.instructions.items);5922 try child_block.instructions.appendSlice(gpa, loop_block.instructions.items);
5892 } else {5923 } else {
5924 _ = try loop_block.addInst(.{
5925 .tag = .repeat,
5926 .data = .{ .repeat = .{
5927 .loop_inst = loop_inst,
5928 } },
5929 });
5930 // Note that `loop_block_len` is now off by one.
5931
5893 try child_block.instructions.append(gpa, loop_inst);5932 try child_block.instructions.append(gpa, loop_inst);
58945933
5895 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len + loop_block_len);5934 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len + loop_block_len + 1);
5896 sema.air_instructions.items(.data)[@intFromEnum(loop_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(5935 sema.air_instructions.items(.data)[@intFromEnum(loop_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(
5897 Air.Block{ .body_len = @intCast(loop_block_len) },5936 Air.Block{ .body_len = @intCast(loop_block_len + 1) },
5898 );5937 );
5899 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(loop_block.instructions.items));5938 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(loop_block.instructions.items));
5900 }5939 }
...@@ -6589,6 +6628,56 @@ fn zirBreak(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError...@@ -6589,6 +6628,56 @@ fn zirBreak(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError
6589 }6628 }
6590}6629}
65916630
6631fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError!void {
6632 const tracy = trace(@src());
6633 defer tracy.end();
6634
6635 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].@"break";
6636 const extra = sema.code.extraData(Zir.Inst.Break, inst_data.payload_index).data;
6637 assert(extra.operand_src_node != Zir.Inst.Break.no_src_node);
6638 const operand_src = start_block.nodeOffset(extra.operand_src_node);
6639 const uncoerced_operand = try sema.resolveInst(inst_data.operand);
6640 const switch_inst = extra.block_inst;
6641
6642 switch (sema.code.instructions.items(.tag)[@intFromEnum(switch_inst)]) {
6643 .switch_block, .switch_block_ref => {},
6644 else => unreachable, // assertion failure
6645 }
6646
6647 const switch_payload_index = sema.code.instructions.items(.data)[@intFromEnum(switch_inst)].pl_node.payload_index;
6648 const switch_operand_ref = sema.code.extraData(Zir.Inst.SwitchBlock, switch_payload_index).data.operand;
6649 const switch_operand_ty = sema.typeOf(try sema.resolveInst(switch_operand_ref));
6650
6651 const operand = try sema.coerce(start_block, switch_operand_ty, uncoerced_operand, operand_src);
6652
6653 try sema.validateRuntimeValue(start_block, operand_src, operand);
6654
6655 // We want to generate a `switch_dispatch` instruction with the switch condition,
6656 // possibly preceded by a store to the stack alloc containing the raw operand.
6657 // However, to avoid too much special-case state in Sema, this is handled by the
6658 // `switch` lowering logic. As such, we will find the `Block` corresponding to the
6659 // parent `switch_block[_ref]` instruction, create a dummy `br`, and add a merge
6660 // to signal to the switch logic to rewrite this into an appropriate dispatch.
6661
6662 var block = start_block;
6663 while (true) {
6664 if (block.label) |label| {
6665 if (label.zir_block == switch_inst) {
6666 const br_ref = try start_block.addBr(label.merges.block_inst, operand);
6667 try label.merges.extra_insts.append(sema.gpa, br_ref.toIndex().?);
6668 try label.merges.extra_src_locs.append(sema.gpa, operand_src);
6669 block.runtime_index.increment();
6670 if (block.runtime_cond == null and block.runtime_loop == null) {
6671 block.runtime_cond = start_block.runtime_cond orelse start_block.runtime_loop;
6672 block.runtime_loop = start_block.runtime_loop;
6673 }
6674 return;
6675 }
6676 }
6677 block = block.parent.?;
6678 }
6679}
6680
6592fn zirDbgStmt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {6681fn zirDbgStmt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
6593 if (block.is_comptime or block.ownerModule().strip) return;6682 if (block.is_comptime or block.ownerModule().strip) return;
65946683
...@@ -11046,12 +11135,7 @@ const SwitchProngAnalysis = struct {...@@ -11046,12 +11135,7 @@ const SwitchProngAnalysis = struct {
11046 sema: *Sema,11135 sema: *Sema,
11047 /// The block containing the `switch_block` itself.11136 /// The block containing the `switch_block` itself.
11048 parent_block: *Block,11137 parent_block: *Block,
11049 /// The raw switch operand value (*not* the condition). Always defined.11138 operand: Operand,
11050 operand: Air.Inst.Ref,
11051 /// May be `undefined` if no prong has a by-ref capture.
11052 operand_ptr: Air.Inst.Ref,
11053 /// The switch condition value. For unions, `operand` is the union and `cond` is its tag.
11054 cond: Air.Inst.Ref,
11055 /// If this switch is on an error set, this is the type to assign to the11139 /// If this switch is on an error set, this is the type to assign to the
11056 /// `else` prong. If `null`, the prong should be unreachable.11140 /// `else` prong. If `null`, the prong should be unreachable.
11057 else_error_ty: ?Type,11141 else_error_ty: ?Type,
...@@ -11061,6 +11145,34 @@ const SwitchProngAnalysis = struct {...@@ -11061,6 +11145,34 @@ const SwitchProngAnalysis = struct {
11061 /// undefined if no prong has a tag capture.11145 /// undefined if no prong has a tag capture.
11062 tag_capture_inst: Zir.Inst.Index,11146 tag_capture_inst: Zir.Inst.Index,
1106311147
11148 const Operand = union(enum) {
11149 /// This switch will be dispatched only once, with the given operand.
11150 simple: struct {
11151 /// The raw switch operand value. Always defined.
11152 by_val: Air.Inst.Ref,
11153 /// The switch operand *pointer*. Defined only if there is a prong
11154 /// with a by-ref capture.
11155 by_ref: Air.Inst.Ref,
11156 /// The switch condition value. For unions, `operand` is the union
11157 /// and `cond` is its enum tag value.
11158 cond: Air.Inst.Ref,
11159 },
11160 /// This switch may be dispatched multiple times with `continue` syntax.
11161 /// As such, the operand is stored in an alloc if needed.
11162 loop: struct {
11163 /// The `alloc` containing the `switch` operand for the active dispatch.
11164 /// Each prong must load from this `alloc` to get captures.
11165 /// If there are no captures, this may be undefined.
11166 operand_alloc: Air.Inst.Ref,
11167 /// Whether `operand_alloc` contains a by-val operand or a by-ref
11168 /// operand.
11169 operand_is_ref: bool,
11170 /// The switch condition value for the *initial* dispatch. For
11171 /// unions, this is the enum tag value.
11172 init_cond: Air.Inst.Ref,
11173 },
11174 };
11175
11064 /// Resolve a switch prong which is determined at comptime to have no peers.11176 /// Resolve a switch prong which is determined at comptime to have no peers.
11065 /// Uses `resolveBlockBody`. Sets up captures as needed.11177 /// Uses `resolveBlockBody`. Sets up captures as needed.
11066 fn resolveProngComptime(11178 fn resolveProngComptime(
...@@ -11192,7 +11304,15 @@ const SwitchProngAnalysis = struct {...@@ -11192,7 +11304,15 @@ const SwitchProngAnalysis = struct {
11192 const sema = spa.sema;11304 const sema = spa.sema;
11193 const pt = sema.pt;11305 const pt = sema.pt;
11194 const zcu = pt.zcu;11306 const zcu = pt.zcu;
11195 const operand_ty = sema.typeOf(spa.operand);11307 const operand_ty = switch (spa.operand) {
11308 .simple => |s| sema.typeOf(s.by_val),
11309 .loop => |l| ty: {
11310 const alloc_ty = sema.typeOf(l.operand_alloc);
11311 const alloc_child = alloc_ty.childType(zcu);
11312 if (l.operand_is_ref) break :ty alloc_child.childType(zcu);
11313 break :ty alloc_child;
11314 },
11315 };
11196 if (operand_ty.zigTypeTag(zcu) != .@"union") {11316 if (operand_ty.zigTypeTag(zcu) != .@"union") {
11197 const tag_capture_src: LazySrcLoc = .{11317 const tag_capture_src: LazySrcLoc = .{
11198 .base_node_inst = capture_src.base_node_inst,11318 .base_node_inst = capture_src.base_node_inst,
...@@ -11223,10 +11343,24 @@ const SwitchProngAnalysis = struct {...@@ -11223,10 +11343,24 @@ const SwitchProngAnalysis = struct {
11223 const zir_datas = sema.code.instructions.items(.data);11343 const zir_datas = sema.code.instructions.items(.data);
11224 const switch_node_offset = zir_datas[@intFromEnum(spa.switch_block_inst)].pl_node.src_node;11344 const switch_node_offset = zir_datas[@intFromEnum(spa.switch_block_inst)].pl_node.src_node;
1122511345
11226 const operand_ty = sema.typeOf(spa.operand);
11227 const operand_ptr_ty = if (capture_byref) sema.typeOf(spa.operand_ptr) else undefined;
11228 const operand_src = block.src(.{ .node_offset_switch_operand = switch_node_offset });11346 const operand_src = block.src(.{ .node_offset_switch_operand = switch_node_offset });
1122911347
11348 const operand_val, const operand_ptr = switch (spa.operand) {
11349 .simple => |s| .{ s.by_val, s.by_ref },
11350 .loop => |l| op: {
11351 const loaded = try sema.analyzeLoad(block, operand_src, l.operand_alloc, operand_src);
11352 if (l.operand_is_ref) {
11353 const by_val = try sema.analyzeLoad(block, operand_src, loaded, operand_src);
11354 break :op .{ by_val, loaded };
11355 } else {
11356 break :op .{ loaded, undefined };
11357 }
11358 },
11359 };
11360
11361 const operand_ty = sema.typeOf(operand_val);
11362 const operand_ptr_ty = if (capture_byref) sema.typeOf(operand_ptr) else undefined;
11363
11230 if (inline_case_capture != .none) {11364 if (inline_case_capture != .none) {
11231 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inline_case_capture, undefined) catch unreachable;11365 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inline_case_capture, undefined) catch unreachable;
11232 if (operand_ty.zigTypeTag(zcu) == .@"union") {11366 if (operand_ty.zigTypeTag(zcu) == .@"union") {
...@@ -11242,16 +11376,16 @@ const SwitchProngAnalysis = struct {...@@ -11242,16 +11376,16 @@ const SwitchProngAnalysis = struct {
11242 .address_space = operand_ptr_ty.ptrAddressSpace(zcu),11376 .address_space = operand_ptr_ty.ptrAddressSpace(zcu),
11243 },11377 },
11244 });11378 });
11245 if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |union_ptr| {11379 if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |union_ptr| {
11246 return Air.internedToRef((try union_ptr.ptrField(field_index, pt)).toIntern());11380 return Air.internedToRef((try union_ptr.ptrField(field_index, pt)).toIntern());
11247 }11381 }
11248 return block.addStructFieldPtr(spa.operand_ptr, field_index, ptr_field_ty);11382 return block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);
11249 } else {11383 } else {
11250 if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |union_val| {11384 if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |union_val| {
11251 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;11385 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
11252 return Air.internedToRef(tag_and_val.val);11386 return Air.internedToRef(tag_and_val.val);
11253 }11387 }
11254 return block.addStructFieldVal(spa.operand, field_index, field_ty);11388 return block.addStructFieldVal(operand_val, field_index, field_ty);
11255 }11389 }
11256 } else if (capture_byref) {11390 } else if (capture_byref) {
11257 return sema.uavRef(item_val.toIntern());11391 return sema.uavRef(item_val.toIntern());
...@@ -11262,17 +11396,17 @@ const SwitchProngAnalysis = struct {...@@ -11262,17 +11396,17 @@ const SwitchProngAnalysis = struct {
1126211396
11263 if (is_special_prong) {11397 if (is_special_prong) {
11264 if (capture_byref) {11398 if (capture_byref) {
11265 return spa.operand_ptr;11399 return operand_ptr;
11266 }11400 }
1126711401
11268 switch (operand_ty.zigTypeTag(zcu)) {11402 switch (operand_ty.zigTypeTag(zcu)) {
11269 .error_set => if (spa.else_error_ty) |ty| {11403 .error_set => if (spa.else_error_ty) |ty| {
11270 return sema.bitCast(block, ty, spa.operand, operand_src, null);11404 return sema.bitCast(block, ty, operand_val, operand_src, null);
11271 } else {11405 } else {
11272 try sema.analyzeUnreachable(block, operand_src, false);11406 try sema.analyzeUnreachable(block, operand_src, false);
11273 return .unreachable_value;11407 return .unreachable_value;
11274 },11408 },
11275 else => return spa.operand,11409 else => return operand_val,
11276 }11410 }
11277 }11411 }
1127811412
...@@ -11371,19 +11505,19 @@ const SwitchProngAnalysis = struct {...@@ -11371,19 +11505,19 @@ const SwitchProngAnalysis = struct {
11371 };11505 };
11372 };11506 };
1137311507
11374 if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |op_ptr_val| {11508 if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |op_ptr_val| {
11375 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);11509 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
11376 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);11510 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
11377 return Air.internedToRef((try pt.getCoerced(field_ptr_val, capture_ptr_ty)).toIntern());11511 return Air.internedToRef((try pt.getCoerced(field_ptr_val, capture_ptr_ty)).toIntern());
11378 }11512 }
1137911513
11380 try sema.requireRuntimeBlock(block, operand_src, null);11514 try sema.requireRuntimeBlock(block, operand_src, null);
11381 return block.addStructFieldPtr(spa.operand_ptr, first_field_index, capture_ptr_ty);11515 return block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);
11382 }11516 }
1138311517
11384 if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |operand_val| {11518 if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |operand_val_val| {
11385 if (operand_val.isUndef(zcu)) return pt.undefRef(capture_ty);11519 if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);
11386 const union_val = ip.indexToKey(operand_val.toIntern()).un;11520 const union_val = ip.indexToKey(operand_val_val.toIntern()).un;
11387 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);11521 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
11388 const uncoerced = Air.internedToRef(union_val.val);11522 const uncoerced = Air.internedToRef(union_val.val);
11389 return sema.coerce(block, capture_ty, uncoerced, operand_src);11523 return sema.coerce(block, capture_ty, uncoerced, operand_src);
...@@ -11392,7 +11526,7 @@ const SwitchProngAnalysis = struct {...@@ -11392,7 +11526,7 @@ const SwitchProngAnalysis = struct {
11392 try sema.requireRuntimeBlock(block, operand_src, null);11526 try sema.requireRuntimeBlock(block, operand_src, null);
1139311527
11394 if (same_types) {11528 if (same_types) {
11395 return block.addStructFieldVal(spa.operand, first_field_index, capture_ty);11529 return block.addStructFieldVal(operand_val, first_field_index, capture_ty);
11396 }11530 }
1139711531
11398 // We may have to emit a switch block which coerces the operand to the capture type.11532 // We may have to emit a switch block which coerces the operand to the capture type.
...@@ -11406,7 +11540,7 @@ const SwitchProngAnalysis = struct {...@@ -11406,7 +11540,7 @@ const SwitchProngAnalysis = struct {
11406 }11540 }
11407 // All fields are in-memory coercible to the resolved type!11541 // All fields are in-memory coercible to the resolved type!
11408 // Just take the first field and bitcast the result.11542 // Just take the first field and bitcast the result.
11409 const uncoerced = try block.addStructFieldVal(spa.operand, first_field_index, first_field_ty);11543 const uncoerced = try block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
11410 return block.addBitCast(capture_ty, uncoerced);11544 return block.addBitCast(capture_ty, uncoerced);
11411 };11545 };
1141211546
...@@ -11470,13 +11604,18 @@ const SwitchProngAnalysis = struct {...@@ -11470,13 +11604,18 @@ const SwitchProngAnalysis = struct {
1147011604
11471 const field_idx = field_indices[idx];11605 const field_idx = field_indices[idx];
11472 const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_idx]);11606 const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_idx]);
11473 const uncoerced = try coerce_block.addStructFieldVal(spa.operand, field_idx, field_ty);11607 const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);
11474 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);11608 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
11475 _ = try coerce_block.addBr(capture_block_inst, coerced);11609 _ = try coerce_block.addBr(capture_block_inst, coerced);
1147611610
11477 try cases_extra.ensureUnusedCapacity(3 + coerce_block.instructions.items.len);11611 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11478 cases_extra.appendAssumeCapacity(1); // items_len11612 1 + // `item`, no ranges
11479 cases_extra.appendAssumeCapacity(@intCast(coerce_block.instructions.items.len)); // body_len11613 coerce_block.instructions.items.len);
11614 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
11615 .items_len = 1,
11616 .ranges_len = 0,
11617 .body_len = @intCast(coerce_block.instructions.items.len),
11618 }));
11480 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item11619 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
11481 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body11620 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
11482 }11621 }
...@@ -11489,7 +11628,7 @@ const SwitchProngAnalysis = struct {...@@ -11489,7 +11628,7 @@ const SwitchProngAnalysis = struct {
11489 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;11628 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
11490 const first_imc_field_idx = field_indices[first_imc_item_idx];11629 const first_imc_field_idx = field_indices[first_imc_item_idx];
11491 const first_imc_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);11630 const first_imc_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
11492 const uncoerced = try coerce_block.addStructFieldVal(spa.operand, first_imc_field_idx, first_imc_field_ty);11631 const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
11493 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);11632 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
11494 _ = try coerce_block.addBr(capture_block_inst, coerced);11633 _ = try coerce_block.addBr(capture_block_inst, coerced);
1149511634
...@@ -11505,21 +11644,47 @@ const SwitchProngAnalysis = struct {...@@ -11505,21 +11644,47 @@ const SwitchProngAnalysis = struct {
11505 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);11644 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
11506 try sema.air_instructions.append(sema.gpa, .{11645 try sema.air_instructions.append(sema.gpa, .{
11507 .tag = .switch_br,11646 .tag = .switch_br,
11508 .data = .{ .pl_op = .{11647 .data = .{
11509 .operand = spa.cond,11648 .pl_op = .{
11510 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{11649 .operand = undefined, // set by switch below
11511 .cases_len = @intCast(prong_count),11650 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
11512 .else_body_len = @intCast(else_body_len),11651 .cases_len = @intCast(prong_count),
11513 }),11652 .else_body_len = @intCast(else_body_len),
11514 } },11653 }),
11654 },
11655 },
11515 });11656 });
11516 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);11657 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
1151711658
11518 // Set up block body11659 // Set up block body
11519 sema.air_instructions.items(.data)[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{11660 switch (spa.operand) {
11520 .body_len = 1,11661 .simple => |s| {
11521 });11662 const air_datas = sema.air_instructions.items(.data);
11522 sema.air_extra.appendAssumeCapacity(switch_br_inst);11663 air_datas[switch_br_inst].pl_op.operand = s.cond;
11664 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
11665 .body_len = 1,
11666 });
11667 sema.air_extra.appendAssumeCapacity(switch_br_inst);
11668 },
11669 .loop => {
11670 // The block must first extract the tag from the loaded union.
11671 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
11672 try sema.air_instructions.append(sema.gpa, .{
11673 .tag = .get_union_tag,
11674 .data = .{ .ty_op = .{
11675 .ty = Air.internedToRef(union_obj.enum_tag_ty),
11676 .operand = operand_val,
11677 } },
11678 });
11679 const air_datas = sema.air_instructions.items(.data);
11680 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
11681 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
11682 .body_len = 2,
11683 });
11684 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
11685 sema.air_extra.appendAssumeCapacity(switch_br_inst);
11686 },
11687 }
1152311688
11524 return capture_block_inst.toRef();11689 return capture_block_inst.toRef();
11525 },11690 },
...@@ -11536,7 +11701,7 @@ const SwitchProngAnalysis = struct {...@@ -11536,7 +11701,7 @@ const SwitchProngAnalysis = struct {
11536 if (case_vals.len == 1) {11701 if (case_vals.len == 1) {
11537 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable;11702 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable;
11538 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);11703 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
11539 return sema.bitCast(block, item_ty, spa.operand, operand_src, null);11704 return sema.bitCast(block, item_ty, operand_val, operand_src, null);
11540 }11705 }
1154111706
11542 var names: InferredErrorSet.NameMap = .{};11707 var names: InferredErrorSet.NameMap = .{};
...@@ -11546,15 +11711,15 @@ const SwitchProngAnalysis = struct {...@@ -11546,15 +11711,15 @@ const SwitchProngAnalysis = struct {
11546 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});11711 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
11547 }11712 }
11548 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());11713 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
11549 return sema.bitCast(block, error_ty, spa.operand, operand_src, null);11714 return sema.bitCast(block, error_ty, operand_val, operand_src, null);
11550 },11715 },
11551 else => {11716 else => {
11552 // In this case the capture value is just the passed-through value11717 // In this case the capture value is just the passed-through value
11553 // of the switch condition.11718 // of the switch condition.
11554 if (capture_byref) {11719 if (capture_byref) {
11555 return spa.operand_ptr;11720 return operand_ptr;
11556 } else {11721 } else {
11557 return spa.operand;11722 return operand_val;
11558 }11723 }
11559 },11724 },
11560 }11725 }
...@@ -11787,9 +11952,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp...@@ -11787,9 +11952,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
11787 var spa: SwitchProngAnalysis = .{11952 var spa: SwitchProngAnalysis = .{
11788 .sema = sema,11953 .sema = sema,
11789 .parent_block = block,11954 .parent_block = block,
11790 .operand = undefined, // must be set to the unwrapped error code before use11955 .operand = .{
11791 .operand_ptr = .none,11956 .simple = .{
11792 .cond = raw_operand_val,11957 .by_val = undefined, // must be set to the unwrapped error code before use
11958 .by_ref = undefined,
11959 .cond = raw_operand_val,
11960 },
11961 },
11793 .else_error_ty = else_error_ty,11962 .else_error_ty = else_error_ty,
11794 .switch_block_inst = inst,11963 .switch_block_inst = inst,
11795 .tag_capture_inst = undefined,11964 .tag_capture_inst = undefined,
...@@ -11810,13 +11979,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp...@@ -11810,13 +11979,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
11810 .name = operand_val.getErrorName(zcu).unwrap().?,11979 .name = operand_val.getErrorName(zcu).unwrap().?,
11811 },11980 },
11812 }));11981 }));
11813 spa.operand = if (extra.data.bits.payload_is_ref)11982 spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref)
11814 try sema.analyzeErrUnionCodePtr(block, switch_operand_src, raw_operand_val)11983 try sema.analyzeErrUnionCodePtr(block, switch_operand_src, raw_operand_val)
11815 else11984 else
11816 try sema.analyzeErrUnionCode(block, switch_operand_src, raw_operand_val);11985 try sema.analyzeErrUnionCode(block, switch_operand_src, raw_operand_val);
1181711986
11818 if (extra.data.bits.any_uses_err_capture) {11987 if (extra.data.bits.any_uses_err_capture) {
11819 sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand);11988 sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val);
11820 }11989 }
11821 defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));11990 defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));
1182211991
...@@ -11824,7 +11993,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp...@@ -11824,7 +11993,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
11824 sema,11993 sema,
11825 spa,11994 spa,
11826 &child_block,11995 &child_block,
11827 try sema.switchCond(block, switch_operand_src, spa.operand),11996 try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val),
11828 err_val,11997 err_val,
11829 operand_err_set_ty,11998 operand_err_set_ty,
11830 switch_src_node_offset,11999 switch_src_node_offset,
...@@ -11878,20 +12047,20 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp...@@ -11878,20 +12047,20 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
11878 const true_instructions = try sub_block.instructions.toOwnedSlice(gpa);12047 const true_instructions = try sub_block.instructions.toOwnedSlice(gpa);
11879 defer gpa.free(true_instructions);12048 defer gpa.free(true_instructions);
1188012049
11881 spa.operand = if (extra.data.bits.payload_is_ref)12050 spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref)
11882 try sema.analyzeErrUnionCodePtr(&sub_block, switch_operand_src, raw_operand_val)12051 try sema.analyzeErrUnionCodePtr(&sub_block, switch_operand_src, raw_operand_val)
11883 else12052 else
11884 try sema.analyzeErrUnionCode(&sub_block, switch_operand_src, raw_operand_val);12053 try sema.analyzeErrUnionCode(&sub_block, switch_operand_src, raw_operand_val);
1188512054
11886 if (extra.data.bits.any_uses_err_capture) {12055 if (extra.data.bits.any_uses_err_capture) {
11887 sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand);12056 sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val);
11888 }12057 }
11889 defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));12058 defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));
11890 _ = try sema.analyzeSwitchRuntimeBlock(12059 _ = try sema.analyzeSwitchRuntimeBlock(
11891 spa,12060 spa,
11892 &sub_block,12061 &sub_block,
11893 switch_src,12062 switch_src,
11894 try sema.switchCond(block, switch_operand_src, spa.operand),12063 try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val),
11895 operand_err_set_ty,12064 operand_err_set_ty,
11896 switch_operand_src,12065 switch_operand_src,
11897 case_vals,12066 case_vals,
...@@ -11960,17 +12129,63 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11960,17 +12129,63 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11960 const special_prong_src = block.src(.{ .node_offset_switch_special_prong = src_node_offset });12129 const special_prong_src = block.src(.{ .node_offset_switch_special_prong = src_node_offset });
11961 const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);12130 const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
1196212131
11963 const raw_operand_val: Air.Inst.Ref, const raw_operand_ptr: Air.Inst.Ref = blk: {12132 const operand: SwitchProngAnalysis.Operand, const raw_operand_ty: Type = op: {
11964 const maybe_ptr = try sema.resolveInst(extra.data.operand);12133 const maybe_ptr = try sema.resolveInst(extra.data.operand);
11965 if (operand_is_ref) {12134 const val, const ref = if (operand_is_ref)
11966 const val = try sema.analyzeLoad(block, src, maybe_ptr, operand_src);12135 .{ try sema.analyzeLoad(block, src, maybe_ptr, operand_src), maybe_ptr }
11967 break :blk .{ val, maybe_ptr };12136 else
11968 } else {12137 .{ maybe_ptr, undefined };
11969 break :blk .{ maybe_ptr, undefined };12138
12139 const init_cond = try sema.switchCond(block, operand_src, val);
12140
12141 const operand_ty = sema.typeOf(val);
12142
12143 if (extra.data.bits.has_continue and !block.is_comptime) {
12144 // Even if the operand is comptime-known, this `switch` is runtime.
12145 if (try operand_ty.comptimeOnlySema(pt)) {
12146 return sema.failWithOwnedErrorMsg(block, msg: {
12147 const msg = try sema.errMsg(operand_src, "operand of switch loop has comptime-only type '{}'", .{operand_ty.fmt(pt)});
12148 errdefer msg.destroy(gpa);
12149 try sema.errNote(operand_src, msg, "switch loops are evalauted at runtime outside of comptime scopes", .{});
12150 break :msg msg;
12151 });
12152 }
12153 try sema.validateRuntimeValue(block, operand_src, maybe_ptr);
12154 const operand_alloc = if (extra.data.bits.any_non_inline_capture) a: {
12155 const operand_ptr_ty = try pt.singleMutPtrType(sema.typeOf(maybe_ptr));
12156 const operand_alloc = try block.addTy(.alloc, operand_ptr_ty);
12157 _ = try block.addBinOp(.store, operand_alloc, maybe_ptr);
12158 break :a operand_alloc;
12159 } else undefined;
12160 break :op .{
12161 .{ .loop = .{
12162 .operand_alloc = operand_alloc,
12163 .operand_is_ref = operand_is_ref,
12164 .init_cond = init_cond,
12165 } },
12166 operand_ty,
12167 };
11970 }12168 }
12169
12170 // We always use `simple` in the comptime case, because as far as the dispatching logic
12171 // is concerned, it really is dispatching a single prong. `resolveSwitchComptime` will
12172 // be resposible for recursively resolving different prongs as needed.
12173 break :op .{
12174 .{ .simple = .{
12175 .by_val = val,
12176 .by_ref = ref,
12177 .cond = init_cond,
12178 } },
12179 operand_ty,
12180 };
11971 };12181 };
1197212182
11973 const operand = try sema.switchCond(block, operand_src, raw_operand_val);12183 const union_originally = raw_operand_ty.zigTypeTag(zcu) == .@"union";
12184 const err_set = raw_operand_ty.zigTypeTag(zcu) == .error_set;
12185 const cond_ty = switch (raw_operand_ty.zigTypeTag(zcu)) {
12186 .@"union" => raw_operand_ty.unionTagType(zcu).?, // validated by `switchCond` above
12187 else => raw_operand_ty,
12188 };
1197412189
11975 // AstGen guarantees that the instruction immediately preceding12190 // AstGen guarantees that the instruction immediately preceding
11976 // switch_block(_ref) is a dbg_stmt12191 // switch_block(_ref) is a dbg_stmt
...@@ -12020,9 +12235,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12020,9 +12235,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12020 },12235 },
12021 };12236 };
1202212237
12023 const maybe_union_ty = sema.typeOf(raw_operand_val);
12024 const union_originally = maybe_union_ty.zigTypeTag(zcu) == .@"union";
12025
12026 // Duplicate checking variables later also used for `inline else`.12238 // Duplicate checking variables later also used for `inline else`.
12027 var seen_enum_fields: []?LazySrcLoc = &.{};12239 var seen_enum_fields: []?LazySrcLoc = &.{};
12028 var seen_errors = SwitchErrorSet.init(gpa);12240 var seen_errors = SwitchErrorSet.init(gpa);
...@@ -12038,13 +12250,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12038,13 +12250,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
1203812250
12039 var empty_enum = false;12251 var empty_enum = false;
1204012252
12041 const operand_ty = sema.typeOf(operand);
12042 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
12043
12044 var else_error_ty: ?Type = null;12253 var else_error_ty: ?Type = null;
1204512254
12046 // Validate usage of '_' prongs.12255 // Validate usage of '_' prongs.
12047 if (special_prong == .under and (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally)) {12256 if (special_prong == .under and !raw_operand_ty.isNonexhaustiveEnum(zcu)) {
12048 const msg = msg: {12257 const msg = msg: {
12049 const msg = try sema.errMsg(12258 const msg = try sema.errMsg(
12050 src,12259 src,
...@@ -12070,11 +12279,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12070,11 +12279,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12070 }12279 }
1207112280
12072 // Validate for duplicate items, missing else prong, and invalid range.12281 // Validate for duplicate items, missing else prong, and invalid range.
12073 switch (operand_ty.zigTypeTag(zcu)) {12282 switch (cond_ty.zigTypeTag(zcu)) {
12074 .@"union" => unreachable, // handled in `switchCond`12283 .@"union" => unreachable, // handled in `switchCond`
12075 .@"enum" => {12284 .@"enum" => {
12076 seen_enum_fields = try gpa.alloc(?LazySrcLoc, operand_ty.enumFieldCount(zcu));12285 seen_enum_fields = try gpa.alloc(?LazySrcLoc, cond_ty.enumFieldCount(zcu));
12077 empty_enum = seen_enum_fields.len == 0 and !operand_ty.isNonexhaustiveEnum(zcu);12286 empty_enum = seen_enum_fields.len == 0 and !cond_ty.isNonexhaustiveEnum(zcu);
12078 @memset(seen_enum_fields, null);12287 @memset(seen_enum_fields, null);
12079 // `range_set` is used for non-exhaustive enum values that do not correspond to any tags.12288 // `range_set` is used for non-exhaustive enum values that do not correspond to any tags.
1208012289
...@@ -12092,7 +12301,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12092,7 +12301,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12092 seen_enum_fields,12301 seen_enum_fields,
12093 &range_set,12302 &range_set,
12094 item_ref,12303 item_ref,
12095 operand_ty,12304 cond_ty,
12096 block.src(.{ .switch_case_item = .{12305 block.src(.{ .switch_case_item = .{
12097 .switch_node_offset = src_node_offset,12306 .switch_node_offset = src_node_offset,
12098 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },12307 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
...@@ -12120,7 +12329,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12120,7 +12329,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12120 seen_enum_fields,12329 seen_enum_fields,
12121 &range_set,12330 &range_set,
12122 item_ref,12331 item_ref,
12123 operand_ty,12332 cond_ty,
12124 block.src(.{ .switch_case_item = .{12333 block.src(.{ .switch_case_item = .{
12125 .switch_node_offset = src_node_offset,12334 .switch_node_offset = src_node_offset,
12126 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },12335 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
...@@ -12129,7 +12338,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12129,7 +12338,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12129 ));12338 ));
12130 }12339 }
1213112340
12132 try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);12341 try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
12133 }12342 }
12134 }12343 }
12135 const all_tags_handled = for (seen_enum_fields) |seen_src| {12344 const all_tags_handled = for (seen_enum_fields) |seen_src| {
...@@ -12137,7 +12346,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12137,7 +12346,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12137 } else true;12346 } else true;
1213812347
12139 if (special_prong == .@"else") {12348 if (special_prong == .@"else") {
12140 if (all_tags_handled and !operand_ty.isNonexhaustiveEnum(zcu)) return sema.fail(12349 if (all_tags_handled and !cond_ty.isNonexhaustiveEnum(zcu)) return sema.fail(
12141 block,12350 block,
12142 special_prong_src,12351 special_prong_src,
12143 "unreachable else prong; all cases already handled",12352 "unreachable else prong; all cases already handled",
...@@ -12154,9 +12363,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12154,9 +12363,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12154 for (seen_enum_fields, 0..) |seen_src, i| {12363 for (seen_enum_fields, 0..) |seen_src, i| {
12155 if (seen_src != null) continue;12364 if (seen_src != null) continue;
1215612365
12157 const field_name = operand_ty.enumFieldName(i, zcu);12366 const field_name = cond_ty.enumFieldName(i, zcu);
12158 try sema.addFieldErrNote(12367 try sema.addFieldErrNote(
12159 operand_ty,12368 cond_ty,
12160 i,12369 i,
12161 msg,12370 msg,
12162 "unhandled enumeration value: '{}'",12371 "unhandled enumeration value: '{}'",
...@@ -12164,15 +12373,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12164,15 +12373,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12164 );12373 );
12165 }12374 }
12166 try sema.errNote(12375 try sema.errNote(
12167 operand_ty.srcLoc(zcu),12376 cond_ty.srcLoc(zcu),
12168 msg,12377 msg,
12169 "enum '{}' declared here",12378 "enum '{}' declared here",
12170 .{operand_ty.fmt(pt)},12379 .{cond_ty.fmt(pt)},
12171 );12380 );
12172 break :msg msg;12381 break :msg msg;
12173 };12382 };
12174 return sema.failWithOwnedErrorMsg(block, msg);12383 return sema.failWithOwnedErrorMsg(block, msg);
12175 } else if (special_prong == .none and operand_ty.isNonexhaustiveEnum(zcu) and !union_originally) {12384 } else if (special_prong == .none and cond_ty.isNonexhaustiveEnum(zcu) and !union_originally) {
12176 return sema.fail(12385 return sema.fail(
12177 block,12386 block,
12178 src,12387 src,
...@@ -12186,7 +12395,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12186,7 +12395,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12186 block,12395 block,
12187 &seen_errors,12396 &seen_errors,
12188 &case_vals,12397 &case_vals,
12189 operand_ty,12398 cond_ty,
12190 inst_data,12399 inst_data,
12191 scalar_cases_len,12400 scalar_cases_len,
12192 multi_cases_len,12401 multi_cases_len,
...@@ -12207,7 +12416,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12207,7 +12416,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12207 block,12416 block,
12208 &range_set,12417 &range_set,
12209 item_ref,12418 item_ref,
12210 operand_ty,12419 cond_ty,
12211 block.src(.{ .switch_case_item = .{12420 block.src(.{ .switch_case_item = .{
12212 .switch_node_offset = src_node_offset,12421 .switch_node_offset = src_node_offset,
12213 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },12422 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
...@@ -12234,7 +12443,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12234,7 +12443,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12234 block,12443 block,
12235 &range_set,12444 &range_set,
12236 item_ref,12445 item_ref,
12237 operand_ty,12446 cond_ty,
12238 block.src(.{ .switch_case_item = .{12447 block.src(.{ .switch_case_item = .{
12239 .switch_node_offset = src_node_offset,12448 .switch_node_offset = src_node_offset,
12240 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },12449 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
...@@ -12256,7 +12465,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12256,7 +12465,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12256 &range_set,12465 &range_set,
12257 item_first,12466 item_first,
12258 item_last,12467 item_last,
12259 operand_ty,12468 cond_ty,
12260 block.src(.{ .switch_case_item = .{12469 block.src(.{ .switch_case_item = .{
12261 .switch_node_offset = src_node_offset,12470 .switch_node_offset = src_node_offset,
12262 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },12471 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
...@@ -12272,9 +12481,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12272,9 +12481,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12272 }12481 }
1227312482
12274 check_range: {12483 check_range: {
12275 if (operand_ty.zigTypeTag(zcu) == .int) {12484 if (cond_ty.zigTypeTag(zcu) == .int) {
12276 const min_int = try operand_ty.minInt(pt, operand_ty);12485 const min_int = try cond_ty.minInt(pt, cond_ty);
12277 const max_int = try operand_ty.maxInt(pt, operand_ty);12486 const max_int = try cond_ty.maxInt(pt, cond_ty);
12278 if (try range_set.spans(min_int.toIntern(), max_int.toIntern())) {12487 if (try range_set.spans(min_int.toIntern(), max_int.toIntern())) {
12279 if (special_prong == .@"else") {12488 if (special_prong == .@"else") {
12280 return sema.fail(12489 return sema.fail(
...@@ -12347,7 +12556,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12347,7 +12556,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12347 ));12556 ));
12348 }12557 }
1234912558
12350 try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);12559 try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
12351 }12560 }
12352 }12561 }
12353 switch (special_prong) {12562 switch (special_prong) {
...@@ -12379,7 +12588,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12379,7 +12588,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12379 block,12588 block,
12380 src,12589 src,
12381 "else prong required when switching on type '{}'",12590 "else prong required when switching on type '{}'",
12382 .{operand_ty.fmt(pt)},12591 .{cond_ty.fmt(pt)},
12383 );12592 );
12384 }12593 }
1238512594
...@@ -12400,7 +12609,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12400,7 +12609,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12400 block,12609 block,
12401 &seen_values,12610 &seen_values,
12402 item_ref,12611 item_ref,
12403 operand_ty,12612 cond_ty,
12404 block.src(.{ .switch_case_item = .{12613 block.src(.{ .switch_case_item = .{
12405 .switch_node_offset = src_node_offset,12614 .switch_node_offset = src_node_offset,
12406 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },12615 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
...@@ -12427,7 +12636,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12427,7 +12636,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12427 block,12636 block,
12428 &seen_values,12637 &seen_values,
12429 item_ref,12638 item_ref,
12430 operand_ty,12639 cond_ty,
12431 block.src(.{ .switch_case_item = .{12640 block.src(.{ .switch_case_item = .{
12432 .switch_node_offset = src_node_offset,12641 .switch_node_offset = src_node_offset,
12433 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },12642 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
...@@ -12436,7 +12645,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12436,7 +12645,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12436 ));12645 ));
12437 }12646 }
1243812647
12439 try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);12648 try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
12440 }12649 }
12441 }12650 }
12442 },12651 },
...@@ -12455,16 +12664,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12455,16 +12664,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12455 .comptime_float,12664 .comptime_float,
12456 .float,12665 .float,
12457 => return sema.fail(block, operand_src, "invalid switch operand type '{}'", .{12666 => return sema.fail(block, operand_src, "invalid switch operand type '{}'", .{
12458 operand_ty.fmt(pt),12667 raw_operand_ty.fmt(pt),
12459 }),12668 }),
12460 }12669 }
1246112670
12462 const spa: SwitchProngAnalysis = .{12671 const spa: SwitchProngAnalysis = .{
12463 .sema = sema,12672 .sema = sema,
12464 .parent_block = block,12673 .parent_block = block,
12465 .operand = raw_operand_val,12674 .operand = operand,
12466 .operand_ptr = raw_operand_ptr,
12467 .cond = operand,
12468 .else_error_ty = else_error_ty,12675 .else_error_ty = else_error_ty,
12469 .switch_block_inst = inst,12676 .switch_block_inst = inst,
12470 .tag_capture_inst = tag_capture_inst,12677 .tag_capture_inst = tag_capture_inst,
...@@ -12508,24 +12715,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12508,24 +12715,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12508 defer child_block.instructions.deinit(gpa);12715 defer child_block.instructions.deinit(gpa);
12509 defer merges.deinit(gpa);12716 defer merges.deinit(gpa);
1251012717
12511 if (try sema.resolveDefinedValue(&child_block, src, operand)) |operand_val| {
12512 return resolveSwitchComptime(
12513 sema,
12514 spa,
12515 &child_block,
12516 operand,
12517 operand_val,
12518 operand_ty,
12519 src_node_offset,
12520 special,
12521 case_vals,
12522 scalar_cases_len,
12523 multi_cases_len,
12524 err_set,
12525 empty_enum,
12526 );
12527 }
12528
12529 if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline) {12718 if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline) {
12530 if (empty_enum) {12719 if (empty_enum) {
12531 return .void_value;12720 return .void_value;
...@@ -12533,54 +12722,90 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12533,54 +12722,90 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12533 if (special_prong == .none) {12722 if (special_prong == .none) {
12534 return sema.fail(block, src, "switch must handle all possibilities", .{});12723 return sema.fail(block, src, "switch must handle all possibilities", .{});
12535 }12724 }
12536 if (err_set and try sema.maybeErrorUnwrap(block, special.body, operand, operand_src, false)) {12725 const init_cond = switch (operand) {
12537 return .unreachable_value;12726 .simple => |s| s.cond,
12538 }12727 .loop => |l| l.init_cond,
12539 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and operand_ty.zigTypeTag(zcu) == .@"enum" and12728 };
12540 (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally))12729 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
12730 raw_operand_ty.zigTypeTag(zcu) == .@"enum" and !raw_operand_ty.isNonexhaustiveEnum(zcu))
12541 {12731 {
12542 try sema.zirDbgStmt(block, cond_dbg_node_index);12732 try sema.zirDbgStmt(block, cond_dbg_node_index);
12543 const ok = try block.addUnOp(.is_named_enum_value, operand);12733 const ok = try block.addUnOp(.is_named_enum_value, init_cond);
12544 try sema.addSafetyCheck(block, src, ok, .corrupt_switch);12734 try sema.addSafetyCheck(block, src, ok, .corrupt_switch);
12545 }12735 }
12736 if (err_set and try sema.maybeErrorUnwrap(block, special.body, init_cond, operand_src, false)) {
12737 return .unreachable_value;
12738 }
12739 }
1254612740
12547 return spa.resolveProngComptime(12741 switch (operand) {
12548 &child_block,12742 .loop => {}, // always runtime; evaluation in comptime scope uses `simple`
12549 .special,12743 .simple => |s| {
12550 special.body,12744 if (try sema.resolveDefinedValue(&child_block, src, s.cond)) |cond_val| {
12551 special.capture,12745 return resolveSwitchComptimeLoop(
12552 block.src(.{ .switch_capture = .{12746 sema,
12553 .switch_node_offset = src_node_offset,12747 spa,
12554 .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,12748 &child_block,
12555 } }),12749 if (operand_is_ref)
12556 undefined, // case_vals may be undefined for special prongs12750 sema.typeOf(s.by_ref)
12557 .none,12751 else
12558 false,12752 raw_operand_ty,
12559 merges,12753 cond_ty,
12560 );12754 cond_val,
12755 src_node_offset,
12756 special,
12757 case_vals,
12758 scalar_cases_len,
12759 multi_cases_len,
12760 err_set,
12761 empty_enum,
12762 operand_is_ref,
12763 );
12764 }
12765
12766 if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline and !extra.data.bits.has_continue) {
12767 return spa.resolveProngComptime(
12768 &child_block,
12769 .special,
12770 special.body,
12771 special.capture,
12772 block.src(.{ .switch_capture = .{
12773 .switch_node_offset = src_node_offset,
12774 .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,
12775 } }),
12776 undefined, // case_vals may be undefined for special prongs
12777 .none,
12778 false,
12779 merges,
12780 );
12781 }
12782 },
12561 }12783 }
1256212784
12563 if (child_block.is_comptime) {12785 if (child_block.is_comptime) {
12564 _ = try sema.resolveConstDefinedValue(&child_block, operand_src, operand, .{12786 _ = try sema.resolveConstDefinedValue(&child_block, operand_src, operand.simple.cond, .{
12565 .needed_comptime_reason = "condition in comptime switch must be comptime-known",12787 .needed_comptime_reason = "condition in comptime switch must be comptime-known",
12566 .block_comptime_reason = child_block.comptime_reason,12788 .block_comptime_reason = child_block.comptime_reason,
12567 });12789 });
12568 unreachable;12790 unreachable;
12569 }12791 }
1257012792
12571 _ = try sema.analyzeSwitchRuntimeBlock(12793 const air_switch_ref = try sema.analyzeSwitchRuntimeBlock(
12572 spa,12794 spa,
12573 &child_block,12795 &child_block,
12574 src,12796 src,
12575 operand,12797 switch (operand) {
12576 operand_ty,12798 .simple => |s| s.cond,
12799 .loop => |l| l.init_cond,
12800 },
12801 cond_ty,
12577 operand_src,12802 operand_src,
12578 case_vals,12803 case_vals,
12579 special,12804 special,
12580 scalar_cases_len,12805 scalar_cases_len,
12581 multi_cases_len,12806 multi_cases_len,
12582 union_originally,12807 union_originally,
12583 maybe_union_ty,12808 raw_operand_ty,
12584 err_set,12809 err_set,
12585 src_node_offset,12810 src_node_offset,
12586 special_prong_src,12811 special_prong_src,
...@@ -12593,6 +12818,67 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12593,6 +12818,67 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12593 false,12818 false,
12594 );12819 );
1259512820
12821 for (merges.extra_insts.items, merges.extra_src_locs.items) |placeholder_inst, dispatch_src| {
12822 var replacement_block = block.makeSubBlock();
12823 defer replacement_block.instructions.deinit(gpa);
12824
12825 assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .br);
12826 const new_operand_maybe_ref = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].br.operand;
12827
12828 if (extra.data.bits.any_non_inline_capture) {
12829 _ = try replacement_block.addBinOp(.store, operand.loop.operand_alloc, new_operand_maybe_ref);
12830 }
12831
12832 const new_operand_val = if (operand_is_ref)
12833 try sema.analyzeLoad(&replacement_block, dispatch_src, new_operand_maybe_ref, dispatch_src)
12834 else
12835 new_operand_maybe_ref;
12836
12837 const new_cond = try sema.switchCond(&replacement_block, dispatch_src, new_operand_val);
12838
12839 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
12840 cond_ty.zigTypeTag(zcu) == .@"enum" and !cond_ty.isNonexhaustiveEnum(zcu) and
12841 !try sema.isComptimeKnown(new_cond))
12842 {
12843 const ok = try replacement_block.addUnOp(.is_named_enum_value, new_cond);
12844 try sema.addSafetyCheck(&replacement_block, src, ok, .corrupt_switch);
12845 }
12846
12847 _ = try replacement_block.addInst(.{
12848 .tag = .switch_dispatch,
12849 .data = .{ .br = .{
12850 .block_inst = air_switch_ref.toIndex().?,
12851 .operand = new_cond,
12852 } },
12853 });
12854
12855 if (replacement_block.instructions.items.len == 1) {
12856 // Optimization: we don't need a block!
12857 sema.air_instructions.set(
12858 @intFromEnum(placeholder_inst),
12859 sema.air_instructions.get(@intFromEnum(replacement_block.instructions.items[0])),
12860 );
12861 continue;
12862 }
12863
12864 // Replace placeholder with a block.
12865 // No `br` is needed as the block is a switch dispatch so necessarily `noreturn`.
12866 try sema.air_extra.ensureUnusedCapacity(
12867 gpa,
12868 @typeInfo(Air.Block).@"struct".fields.len + replacement_block.instructions.items.len,
12869 );
12870 sema.air_instructions.set(@intFromEnum(placeholder_inst), .{
12871 .tag = .block,
12872 .data = .{ .ty_pl = .{
12873 .ty = .noreturn_type,
12874 .payload = sema.addExtraAssumeCapacity(Air.Block{
12875 .body_len = @intCast(replacement_block.instructions.items.len),
12876 }),
12877 } },
12878 });
12879 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(replacement_block.instructions.items));
12880 }
12881
12596 return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false);12882 return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false);
12597}12883}
1259812884
...@@ -12699,21 +12985,18 @@ fn analyzeSwitchRuntimeBlock(...@@ -12699,21 +12985,18 @@ fn analyzeSwitchRuntimeBlock(
12699 };12985 };
1270012986
12701 try branch_hints.append(gpa, prong_hint);12987 try branch_hints.append(gpa, prong_hint);
12702 try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);12988 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12703 cases_extra.appendAssumeCapacity(1); // items_len12989 1 + // `item`, no ranges
12704 cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));12990 case_block.instructions.items.len);
12991 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12992 .items_len = 1,
12993 .ranges_len = 0,
12994 .body_len = @intCast(case_block.instructions.items.len),
12995 }));
12705 cases_extra.appendAssumeCapacity(@intFromEnum(item));12996 cases_extra.appendAssumeCapacity(@intFromEnum(item));
12706 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));12997 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12707 }12998 }
1270812999
12709 var is_first = true;
12710 var prev_cond_br: Air.Inst.Index = undefined;
12711 var prev_hint: std.builtin.BranchHint = undefined;
12712 var first_else_body: []const Air.Inst.Index = &.{};
12713 defer gpa.free(first_else_body);
12714 var prev_then_body: []const Air.Inst.Index = &.{};
12715 defer gpa.free(prev_then_body);
12716
12717 var cases_len = scalar_cases_len;13000 var cases_len = scalar_cases_len;
12718 var case_val_idx: usize = scalar_cases_len;13001 var case_val_idx: usize = scalar_cases_len;
12719 var multi_i: u32 = 0;13002 var multi_i: u32 = 0;
...@@ -12723,31 +13006,27 @@ fn analyzeSwitchRuntimeBlock(...@@ -12723,31 +13006,27 @@ fn analyzeSwitchRuntimeBlock(
12723 const ranges_len = sema.code.extra[extra_index];13006 const ranges_len = sema.code.extra[extra_index];
12724 extra_index += 1;13007 extra_index += 1;
12725 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);13008 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
12726 extra_index += 1 + items_len;13009 extra_index += 1 + items_len + 2 * ranges_len;
1272713010
12728 const items = case_vals.items[case_val_idx..][0..items_len];13011 const items = case_vals.items[case_val_idx..][0..items_len];
12729 case_val_idx += items_len;13012 case_val_idx += items_len;
13013 // TODO: @ptrCast slice once Sema supports it
13014 const ranges: []const [2]Air.Inst.Ref = @as([*]const [2]Air.Inst.Ref, @ptrCast(case_vals.items[case_val_idx..]))[0..ranges_len];
13015 case_val_idx += ranges_len * 2;
13016
13017 const body = sema.code.bodySlice(extra_index, info.body_len);
13018 extra_index += info.body_len;
1273013019
12731 case_block.instructions.shrinkRetainingCapacity(0);13020 case_block.instructions.shrinkRetainingCapacity(0);
12732 case_block.error_return_trace_index = child_block.error_return_trace_index;13021 case_block.error_return_trace_index = child_block.error_return_trace_index;
1273313022
12734 // Generate all possible cases as scalar prongs.13023 // Generate all possible cases as scalar prongs.
12735 if (info.is_inline) {13024 if (info.is_inline) {
12736 const body_start = extra_index + 2 * ranges_len;
12737 const body = sema.code.bodySlice(body_start, info.body_len);
12738 var emit_bb = false;13025 var emit_bb = false;
1273913026
12740 var range_i: u32 = 0;13027 for (ranges, 0..) |range_items, range_i| {
12741 while (range_i < ranges_len) : (range_i += 1) {13028 var item = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, range_items[0], undefined) catch unreachable;
12742 const range_items = case_vals.items[case_val_idx..][0..2];13029 const item_last = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, range_items[1], undefined) catch unreachable;
12743 extra_index += 2;
12744 case_val_idx += 2;
12745
12746 const item_first_ref = range_items[0];
12747 const item_last_ref = range_items[1];
12748
12749 var item = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item_first_ref, undefined) catch unreachable;
12750 const item_last = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item_last_ref, undefined) catch unreachable;
1275113030
12752 while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({13031 while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({
12753 // Previous validation has resolved any possible lazy values.13032 // Previous validation has resolved any possible lazy values.
...@@ -12785,9 +13064,14 @@ fn analyzeSwitchRuntimeBlock(...@@ -12785,9 +13064,14 @@ fn analyzeSwitchRuntimeBlock(
12785 );13064 );
12786 try branch_hints.append(gpa, prong_hint);13065 try branch_hints.append(gpa, prong_hint);
1278713066
12788 try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);13067 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12789 cases_extra.appendAssumeCapacity(1); // items_len13068 1 + // `item`, no ranges
12790 cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));13069 case_block.instructions.items.len);
13070 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
13071 .items_len = 1,
13072 .ranges_len = 0,
13073 .body_len = @intCast(case_block.instructions.items.len),
13074 }));
12791 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));13075 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
12792 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));13076 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
1279313077
...@@ -12834,134 +13118,39 @@ fn analyzeSwitchRuntimeBlock(...@@ -12834,134 +13118,39 @@ fn analyzeSwitchRuntimeBlock(
12834 };13118 };
12835 try branch_hints.append(gpa, prong_hint);13119 try branch_hints.append(gpa, prong_hint);
1283613120
12837 try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);13121 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12838 cases_extra.appendAssumeCapacity(1); // items_len13122 1 + // `item`, no ranges
12839 cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));13123 case_block.instructions.items.len);
13124 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
13125 .items_len = 1,
13126 .ranges_len = 0,
13127 .body_len = @intCast(case_block.instructions.items.len),
13128 }));
12840 cases_extra.appendAssumeCapacity(@intFromEnum(item));13129 cases_extra.appendAssumeCapacity(@intFromEnum(item));
12841 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));13130 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12842 }13131 }
1284313132
12844 extra_index += info.body_len;
12845 continue;13133 continue;
12846 }13134 }
1284713135
12848 var any_ok: Air.Inst.Ref = .none;13136 cases_len += 1;
1284913137
12850 // If there are any ranges, we have to put all the items into the13138 const analyze_body = if (union_originally)
12851 // else prong. Otherwise, we can take advantage of multiple items
12852 // mapping to the same body.
12853 if (ranges_len == 0) {
12854 cases_len += 1;
12855
12856 const analyze_body = if (union_originally)
12857 for (items) |item| {
12858 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
12859 const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
12860 if (field_ty.zigTypeTag(zcu) != .noreturn) break true;
12861 } else false
12862 else
12863 true;
12864
12865 const body = sema.code.bodySlice(extra_index, info.body_len);
12866 extra_index += info.body_len;
12867 const prong_hint: std.builtin.BranchHint = if (err_set and
12868 try sema.maybeErrorUnwrap(&case_block, body, operand, operand_src, allow_err_code_unwrap))
12869 h: {
12870 // nothing to do here. weight against error branch
12871 break :h .unlikely;
12872 } else if (analyze_body) h: {
12873 break :h try spa.analyzeProngRuntime(
12874 &case_block,
12875 .normal,
12876 body,
12877 info.capture,
12878 child_block.src(.{ .switch_capture = .{
12879 .switch_node_offset = switch_node_offset,
12880 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12881 } }),
12882 items,
12883 .none,
12884 false,
12885 );
12886 } else h: {
12887 _ = try case_block.addNoOp(.unreach);
12888 break :h .none;
12889 };
12890
12891 try branch_hints.append(gpa, prong_hint);
12892 try cases_extra.ensureUnusedCapacity(gpa, 2 + items.len +
12893 case_block.instructions.items.len);
12894
12895 cases_extra.appendAssumeCapacity(@intCast(items.len));
12896 cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));
12897
12898 for (items) |item| {
12899 cases_extra.appendAssumeCapacity(@intFromEnum(item));
12900 }
12901
12902 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12903 } else {
12904 for (items) |item| {13139 for (items) |item| {
12905 const cmp_ok = try case_block.addBinOp(if (case_block.float_mode == .optimized) .cmp_eq_optimized else .cmp_eq, operand, item);13140 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
12906 if (any_ok != .none) {13141 const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
12907 any_ok = try case_block.addBinOp(.bool_or, any_ok, cmp_ok);13142 if (field_ty.zigTypeTag(zcu) != .noreturn) break true;
12908 } else {13143 } else false
12909 any_ok = cmp_ok;13144 else
12910 }13145 true;
12911 }
12912
12913 var range_i: usize = 0;
12914 while (range_i < ranges_len) : (range_i += 1) {
12915 const range_items = case_vals.items[case_val_idx..][0..2];
12916 extra_index += 2;
12917 case_val_idx += 2;
12918
12919 const item_first = range_items[0];
12920 const item_last = range_items[1];
12921
12922 // operand >= first and operand <= last
12923 const range_first_ok = try case_block.addBinOp(
12924 if (case_block.float_mode == .optimized) .cmp_gte_optimized else .cmp_gte,
12925 operand,
12926 item_first,
12927 );
12928 const range_last_ok = try case_block.addBinOp(
12929 if (case_block.float_mode == .optimized) .cmp_lte_optimized else .cmp_lte,
12930 operand,
12931 item_last,
12932 );
12933 const range_ok = try case_block.addBinOp(
12934 .bool_and,
12935 range_first_ok,
12936 range_last_ok,
12937 );
12938 if (any_ok != .none) {
12939 any_ok = try case_block.addBinOp(.bool_or, any_ok, range_ok);
12940 } else {
12941 any_ok = range_ok;
12942 }
12943 }
12944
12945 const new_cond_br = try case_block.addInstAsIndex(.{ .tag = .cond_br, .data = .{
12946 .pl_op = .{
12947 .operand = any_ok,
12948 .payload = undefined,
12949 },
12950 } });
12951 var cond_body = try case_block.instructions.toOwnedSlice(gpa);
12952 defer gpa.free(cond_body);
12953
12954 case_block.instructions.shrinkRetainingCapacity(0);
12955 case_block.error_return_trace_index = child_block.error_return_trace_index;
1295613146
12957 const body = sema.code.bodySlice(extra_index, info.body_len);13147 const prong_hint: std.builtin.BranchHint = if (err_set and
12958 extra_index += info.body_len;13148 try sema.maybeErrorUnwrap(&case_block, body, operand, operand_src, allow_err_code_unwrap))
12959 const prong_hint: std.builtin.BranchHint = if (err_set and13149 h: {
12960 try sema.maybeErrorUnwrap(&case_block, body, operand, operand_src, allow_err_code_unwrap))13150 // nothing to do here. weight against error branch
12961 h: {13151 break :h .unlikely;
12962 // nothing to do here. weight against error branch13152 } else if (analyze_body) h: {
12963 break :h .unlikely;13153 break :h try spa.analyzeProngRuntime(
12964 } else try spa.analyzeProngRuntime(
12965 &case_block,13154 &case_block,
12966 .normal,13155 .normal,
12967 body,13156 body,
...@@ -12974,40 +13163,36 @@ fn analyzeSwitchRuntimeBlock(...@@ -12974,40 +13163,36 @@ fn analyzeSwitchRuntimeBlock(
12974 .none,13163 .none,
12975 false,13164 false,
12976 );13165 );
13166 } else h: {
13167 _ = try case_block.addNoOp(.unreach);
13168 break :h .none;
13169 };
1297713170
12978 if (is_first) {13171 try branch_hints.append(gpa, prong_hint);
12979 is_first = false;
12980 first_else_body = cond_body;
12981 cond_body = &.{};
12982 } else {
12983 try sema.air_extra.ensureUnusedCapacity(
12984 gpa,
12985 @typeInfo(Air.CondBr).@"struct".fields.len + prev_then_body.len + cond_body.len,
12986 );
1298713172
12988 sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{13173 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12989 .then_body_len = @intCast(prev_then_body.len),13174 items.len + 2 * ranges_len +
12990 .else_body_len = @intCast(cond_body.len),13175 case_block.instructions.items.len);
12991 .branch_hints = .{13176 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12992 .true = prev_hint,13177 .items_len = @intCast(items.len),
12993 .false = .none,13178 .ranges_len = @intCast(ranges_len),
12994 // Code coverage is desired for error handling.13179 .body_len = @intCast(case_block.instructions.items.len),
12995 .then_cov = .poi,13180 }));
12996 .else_cov = .poi,13181
12997 },13182 for (items) |item| {
12998 });13183 cases_extra.appendAssumeCapacity(@intFromEnum(item));
12999 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body));
13000 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cond_body));
13001 }
13002 gpa.free(prev_then_body);
13003 prev_then_body = try case_block.instructions.toOwnedSlice(gpa);
13004 prev_cond_br = new_cond_br;
13005 prev_hint = prong_hint;
13006 }13184 }
13185 for (ranges) |range| {
13186 cases_extra.appendSliceAssumeCapacity(&.{
13187 @intFromEnum(range[0]),
13188 @intFromEnum(range[1]),
13189 });
13190 }
13191
13192 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
13007 }13193 }
1300813194
13009 var final_else_body: []const Air.Inst.Index = &.{};13195 const else_body: []const Air.Inst.Index = if (special.body.len != 0 or case_block.wantSafety()) else_body: {
13010 if (special.body.len != 0 or !is_first or case_block.wantSafety()) {
13011 var emit_bb = false;13196 var emit_bb = false;
13012 if (special.is_inline) switch (operand_ty.zigTypeTag(zcu)) {13197 if (special.is_inline) switch (operand_ty.zigTypeTag(zcu)) {
13013 .@"enum" => {13198 .@"enum" => {
...@@ -13054,9 +13239,14 @@ fn analyzeSwitchRuntimeBlock(...@@ -13054,9 +13239,14 @@ fn analyzeSwitchRuntimeBlock(
13054 };13239 };
13055 try branch_hints.append(gpa, prong_hint);13240 try branch_hints.append(gpa, prong_hint);
1305613241
13057 try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);13242 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
13058 cases_extra.appendAssumeCapacity(1); // items_len13243 1 + // `item`, no ranges
13059 cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));13244 case_block.instructions.items.len);
13245 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
13246 .items_len = 1,
13247 .ranges_len = 0,
13248 .body_len = @intCast(case_block.instructions.items.len),
13249 }));
13060 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));13250 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
13061 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));13251 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
13062 }13252 }
...@@ -13100,9 +13290,14 @@ fn analyzeSwitchRuntimeBlock(...@@ -13100,9 +13290,14 @@ fn analyzeSwitchRuntimeBlock(
13100 );13290 );
13101 try branch_hints.append(gpa, prong_hint);13291 try branch_hints.append(gpa, prong_hint);
1310213292
13103 try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);13293 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
13104 cases_extra.appendAssumeCapacity(1); // items_len13294 1 + // `item`, no ranges
13105 cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));13295 case_block.instructions.items.len);
13296 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
13297 .items_len = 1,
13298 .ranges_len = 0,
13299 .body_len = @intCast(case_block.instructions.items.len),
13300 }));
13106 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));13301 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
13107 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));13302 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
13108 }13303 }
...@@ -13135,9 +13330,14 @@ fn analyzeSwitchRuntimeBlock(...@@ -13135,9 +13330,14 @@ fn analyzeSwitchRuntimeBlock(
13135 );13330 );
13136 try branch_hints.append(gpa, prong_hint);13331 try branch_hints.append(gpa, prong_hint);
1313713332
13138 try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);13333 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
13139 cases_extra.appendAssumeCapacity(1); // items_len13334 1 + // `item`, no ranges
13140 cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));13335 case_block.instructions.items.len);
13336 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
13337 .items_len = 1,
13338 .ranges_len = 0,
13339 .body_len = @intCast(case_block.instructions.items.len),
13340 }));
13141 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));13341 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
13142 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));13342 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
13143 }13343 }
...@@ -13167,9 +13367,14 @@ fn analyzeSwitchRuntimeBlock(...@@ -13167,9 +13367,14 @@ fn analyzeSwitchRuntimeBlock(
13167 );13367 );
13168 try branch_hints.append(gpa, prong_hint);13368 try branch_hints.append(gpa, prong_hint);
1316913369
13170 try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);13370 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
13171 cases_extra.appendAssumeCapacity(1); // items_len13371 1 + // `item`, no ranges
13172 cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));13372 case_block.instructions.items.len);
13373 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
13374 .items_len = 1,
13375 .ranges_len = 0,
13376 .body_len = @intCast(case_block.instructions.items.len),
13377 }));
13173 cases_extra.appendAssumeCapacity(@intFromEnum(Air.Inst.Ref.bool_true));13378 cases_extra.appendAssumeCapacity(@intFromEnum(Air.Inst.Ref.bool_true));
13174 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));13379 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
13175 }13380 }
...@@ -13197,9 +13402,14 @@ fn analyzeSwitchRuntimeBlock(...@@ -13197,9 +13402,14 @@ fn analyzeSwitchRuntimeBlock(
13197 );13402 );
13198 try branch_hints.append(gpa, prong_hint);13403 try branch_hints.append(gpa, prong_hint);
1319913404
13200 try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);13405 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
13201 cases_extra.appendAssumeCapacity(1); // items_len13406 1 + // `item`, no ranges
13202 cases_extra.appendAssumeCapacity(@intCast(case_block.instructions.items.len));13407 case_block.instructions.items.len);
13408 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
13409 .items_len = 1,
13410 .ranges_len = 0,
13411 .body_len = @intCast(case_block.instructions.items.len),
13412 }));
13203 cases_extra.appendAssumeCapacity(@intFromEnum(Air.Inst.Ref.bool_false));13413 cases_extra.appendAssumeCapacity(@intFromEnum(Air.Inst.Ref.bool_false));
13204 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));13414 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
13205 }13415 }
...@@ -13263,41 +13473,22 @@ fn analyzeSwitchRuntimeBlock(...@@ -13263,41 +13473,22 @@ fn analyzeSwitchRuntimeBlock(
13263 break :h .cold;13473 break :h .cold;
13264 };13474 };
1326513475
13266 if (is_first) {13476 try branch_hints.append(gpa, else_hint);
13267 try branch_hints.append(gpa, else_hint);13477 break :else_body case_block.instructions.items;
13268 final_else_body = case_block.instructions.items;13478 } else else_body: {
13269 } else {
13270 try branch_hints.append(gpa, .none); // we have the range conditionals first
13271 try sema.air_extra.ensureUnusedCapacity(gpa, prev_then_body.len +
13272 @typeInfo(Air.CondBr).@"struct".fields.len + case_block.instructions.items.len);
13273
13274 sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{
13275 .then_body_len = @intCast(prev_then_body.len),
13276 .else_body_len = @intCast(case_block.instructions.items.len),
13277 .branch_hints = .{
13278 .true = prev_hint,
13279 .false = else_hint,
13280 .then_cov = .poi,
13281 .else_cov = .poi,
13282 },
13283 });
13284 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body));
13285 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
13286 final_else_body = first_else_body;
13287 }
13288 } else {
13289 try branch_hints.append(gpa, .none);13479 try branch_hints.append(gpa, .none);
13290 }13480 break :else_body &.{};
13481 };
1329113482
13292 assert(branch_hints.items.len == cases_len + 1);13483 assert(branch_hints.items.len == cases_len + 1);
1329313484
13294 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +13485 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +
13295 cases_extra.items.len + final_else_body.len +13486 cases_extra.items.len + else_body.len +
13296 (std.math.divCeil(usize, branch_hints.items.len, 10) catch unreachable)); // branch hints13487 (std.math.divCeil(usize, branch_hints.items.len, 10) catch unreachable)); // branch hints
1329713488
13298 const payload_index = sema.addExtraAssumeCapacity(Air.SwitchBr{13489 const payload_index = sema.addExtraAssumeCapacity(Air.SwitchBr{
13299 .cases_len = @intCast(cases_len),13490 .cases_len = @intCast(cases_len),
13300 .else_body_len = @intCast(final_else_body.len),13491 .else_body_len = @intCast(else_body.len),
13301 });13492 });
1330213493
13303 {13494 {
...@@ -13316,10 +13507,10 @@ fn analyzeSwitchRuntimeBlock(...@@ -13316,10 +13507,10 @@ fn analyzeSwitchRuntimeBlock(
13316 }13507 }
13317 }13508 }
13318 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cases_extra.items));13509 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cases_extra.items));
13319 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(final_else_body));13510 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_body));
1332013511
13321 return try child_block.addInst(.{13512 return try child_block.addInst(.{
13322 .tag = .switch_br,13513 .tag = if (spa.operand == .loop) .loop_switch_br else .switch_br,
13323 .data = .{ .pl_op = .{13514 .data = .{ .pl_op = .{
13324 .operand = operand,13515 .operand = operand,
13325 .payload = payload_index,13516 .payload = payload_index,
...@@ -13327,6 +13518,77 @@ fn analyzeSwitchRuntimeBlock(...@@ -13327,6 +13518,77 @@ fn analyzeSwitchRuntimeBlock(
13327 });13518 });
13328}13519}
1332913520
13521fn resolveSwitchComptimeLoop(
13522 sema: *Sema,
13523 init_spa: SwitchProngAnalysis,
13524 child_block: *Block,
13525 maybe_ptr_operand_ty: Type,
13526 cond_ty: Type,
13527 init_cond_val: Value,
13528 switch_node_offset: i32,
13529 special: SpecialProng,
13530 case_vals: std.ArrayListUnmanaged(Air.Inst.Ref),
13531 scalar_cases_len: u32,
13532 multi_cases_len: u32,
13533 err_set: bool,
13534 empty_enum: bool,
13535 operand_is_ref: bool,
13536) CompileError!Air.Inst.Ref {
13537 var spa = init_spa;
13538 var cond_val = init_cond_val;
13539
13540 while (true) {
13541 if (resolveSwitchComptime(
13542 sema,
13543 spa,
13544 child_block,
13545 spa.operand.simple.cond,
13546 cond_val,
13547 cond_ty,
13548 switch_node_offset,
13549 special,
13550 case_vals,
13551 scalar_cases_len,
13552 multi_cases_len,
13553 err_set,
13554 empty_enum,
13555 )) |result| {
13556 return result;
13557 } else |err| switch (err) {
13558 error.ComptimeBreak => {
13559 const break_inst = sema.code.instructions.get(@intFromEnum(sema.comptime_break_inst));
13560 if (break_inst.tag != .switch_continue) return error.ComptimeBreak;
13561 const extra = sema.code.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data;
13562 if (extra.block_inst != spa.switch_block_inst) return error.ComptimeBreak;
13563 // This is a `switch_continue` targeting this block. Change the operand and start over.
13564 const src = child_block.nodeOffset(extra.operand_src_node);
13565 const new_operand_uncoerced = try sema.resolveInst(break_inst.data.@"break".operand);
13566 const new_operand = try sema.coerce(child_block, maybe_ptr_operand_ty, new_operand_uncoerced, src);
13567
13568 try sema.emitBackwardBranch(child_block, src);
13569
13570 const val, const ref = if (operand_is_ref)
13571 .{ try sema.analyzeLoad(child_block, src, new_operand, src), new_operand }
13572 else
13573 .{ new_operand, undefined };
13574
13575 const cond_ref = try sema.switchCond(child_block, src, val);
13576
13577 cond_val = try sema.resolveConstDefinedValue(child_block, src, cond_ref, .{
13578 .needed_comptime_reason = "condition in comptime switch must be comptime-known",
13579 .block_comptime_reason = child_block.comptime_reason,
13580 });
13581 spa.operand = .{ .simple = .{
13582 .by_val = val,
13583 .by_ref = ref,
13584 .cond = cond_ref,
13585 } };
13586 },
13587 else => |e| return e,
13588 }
13589 }
13590}
13591
13330fn resolveSwitchComptime(13592fn resolveSwitchComptime(
13331 sema: *Sema,13593 sema: *Sema,
13332 spa: SwitchProngAnalysis,13594 spa: SwitchProngAnalysis,
...@@ -13344,6 +13606,7 @@ fn resolveSwitchComptime(...@@ -13344,6 +13606,7 @@ fn resolveSwitchComptime(
13344) CompileError!Air.Inst.Ref {13606) CompileError!Air.Inst.Ref {
13345 const merges = &child_block.label.?.merges;13607 const merges = &child_block.label.?.merges;
13346 const resolved_operand_val = try sema.resolveLazyValue(operand_val);13608 const resolved_operand_val = try sema.resolveLazyValue(operand_val);
13609
13347 var extra_index: usize = special.end;13610 var extra_index: usize = special.end;
13348 {13611 {
13349 var scalar_i: usize = 0;13612 var scalar_i: usize = 0;
...@@ -37507,15 +37770,21 @@ pub fn addExtra(sema: *Sema, extra: anytype) Allocator.Error!u32 {...@@ -37507,15 +37770,21 @@ pub fn addExtra(sema: *Sema, extra: anytype) Allocator.Error!u32 {
37507}37770}
3750837771
37509pub fn addExtraAssumeCapacity(sema: *Sema, extra: anytype) u32 {37772pub fn addExtraAssumeCapacity(sema: *Sema, extra: anytype) u32 {
37510 const fields = std.meta.fields(@TypeOf(extra));
37511 const result: u32 = @intCast(sema.air_extra.items.len);37773 const result: u32 = @intCast(sema.air_extra.items.len);
37512 inline for (fields) |field| {37774 sema.air_extra.appendSliceAssumeCapacity(&payloadToExtraItems(extra));
37513 sema.air_extra.appendAssumeCapacity(switch (field.type) {37775 return result;
37514 u32 => @field(extra, field.name),37776}
37515 i32, Air.CondBr.BranchHints => @bitCast(@field(extra, field.name)),37777
37516 Air.Inst.Ref, InternPool.Index => @intFromEnum(@field(extra, field.name)),37778fn payloadToExtraItems(data: anytype) [@typeInfo(@TypeOf(data)).@"struct".fields.len]u32 {
37779 const fields = @typeInfo(@TypeOf(data)).@"struct".fields;
37780 var result: [fields.len]u32 = undefined;
37781 inline for (&result, fields) |*val, field| {
37782 val.* = switch (field.type) {
37783 u32 => @field(data, field.name),
37784 i32, Air.CondBr.BranchHints => @bitCast(@field(data, field.name)),
37785 Air.Inst.Ref, InternPool.Index => @intFromEnum(@field(data, field.name)),
37517 else => @compileError("bad field type: " ++ @typeName(field.type)),37786 else => @compileError("bad field type: " ++ @typeName(field.type)),
37518 });37787 };
37519 }37788 }
37520 return result;37789 return result;
37521}37790}
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+5
...@@ -734,6 +734,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -734,6 +734,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
734 .bitcast => try self.airBitCast(inst),734 .bitcast => try self.airBitCast(inst),
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`", .{}),
738 .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}),
737 .trap => try self.airTrap(),739 .trap => try self.airTrap(),
738 .breakpoint => try self.airBreakpoint(),740 .breakpoint => try self.airBreakpoint(),
739 .ret_addr => try self.airRetAddr(inst),741 .ret_addr => try self.airRetAddr(inst),
...@@ -824,6 +826,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -824,6 +826,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
824 .field_parent_ptr => try self.airFieldParentPtr(inst),826 .field_parent_ptr => try self.airFieldParentPtr(inst),
825827
826 .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`", .{}),
827 .slice_ptr => try self.airSlicePtr(inst),830 .slice_ptr => try self.airSlicePtr(inst),
828 .slice_len => try self.airSliceLen(inst),831 .slice_len => try self.airSliceLen(inst),
829832
...@@ -5105,6 +5108,8 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {...@@ -5105,6 +5108,8 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
51055108
5106 var it = switch_br.iterateCases();5109 var it = switch_br.iterateCases();
5107 while (it.next()) |case| {5110 while (it.next()) |case| {
5111 if (case.ranges.len > 0) return self.fail("TODO: switch with ranges", .{});
5112
5108 // For every item, we compare it to condition and branch into5113 // For every item, we compare it to condition and branch into
5109 // the prong if they are equal. After we compared to all5114 // the prong if they are equal. After we compared to all
5110 // items, we branch into the next prong (or if no other prongs5115 // items, we branch into the next prong (or if no other prongs
src/arch/arm/CodeGen.zig+4
...@@ -721,6 +721,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -721,6 +721,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
721 .bitcast => try self.airBitCast(inst),721 .bitcast => try self.airBitCast(inst),
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`", .{}),
725 .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}),
724 .trap => try self.airTrap(),726 .trap => try self.airTrap(),
725 .breakpoint => try self.airBreakpoint(),727 .breakpoint => try self.airBreakpoint(),
726 .ret_addr => try self.airRetAddr(inst),728 .ret_addr => try self.airRetAddr(inst),
...@@ -811,6 +813,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -811,6 +813,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
811 .field_parent_ptr => try self.airFieldParentPtr(inst),813 .field_parent_ptr => try self.airFieldParentPtr(inst),
812814
813 .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`", .{}),
814 .slice_ptr => try self.airSlicePtr(inst),817 .slice_ptr => try self.airSlicePtr(inst),
815 .slice_len => try self.airSliceLen(inst),818 .slice_len => try self.airSliceLen(inst),
816819
...@@ -5053,6 +5056,7 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {...@@ -5053,6 +5056,7 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
50535056
5054 var it = switch_br.iterateCases();5057 var it = switch_br.iterateCases();
5055 while (it.next()) |case| {5058 while (it.next()) |case| {
5059 if (case.ranges.len > 0) return self.fail("TODO: switch with ranges", .{});
5056 // For every item, we compare it to condition and branch into5060 // For every item, we compare it to condition and branch into
5057 // the prong if they are equal. After we compared to all5061 // the prong if they are equal. After we compared to all
5058 // items, we branch into the next prong (or if no other prongs5062 // items, we branch into the next prong (or if no other prongs
src/arch/riscv64/CodeGen.zig+181-30
...@@ -108,6 +108,13 @@ frame_allocs: std.MultiArrayList(FrameAlloc) = .{},...@@ -108,6 +108,13 @@ frame_allocs: std.MultiArrayList(FrameAlloc) = .{},
108free_frame_indices: std.AutoArrayHashMapUnmanaged(FrameIndex, void) = .{},108free_frame_indices: std.AutoArrayHashMapUnmanaged(FrameIndex, void) = .{},
109frame_locs: std.MultiArrayList(Mir.FrameLoc) = .{},109frame_locs: std.MultiArrayList(Mir.FrameLoc) = .{},
110110
111loops: std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
112 /// The state to restore before branching.
113 state: State,
114 /// The branch target.
115 jmp_target: Mir.Inst.Index,
116}) = .{},
117
111/// Debug field, used to find bugs in the compiler.118/// Debug field, used to find bugs in the compiler.
112air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init,119air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init,
113120
...@@ -225,11 +232,12 @@ const MCValue = union(enum) {...@@ -225,11 +232,12 @@ const MCValue = union(enum) {
225 .register,232 .register,
226 .register_pair,233 .register_pair,
227 .register_offset,234 .register_offset,
228 .load_frame,
229 .load_symbol,235 .load_symbol,
230 .load_tlv,236 .load_tlv,
231 .indirect,237 .indirect,
232 => true,238 => true,
239
240 .load_frame => |frame_addr| !frame_addr.index.isNamed(),
233 };241 };
234 }242 }
235243
...@@ -797,6 +805,7 @@ pub fn generate(...@@ -797,6 +805,7 @@ pub fn generate(
797 function.frame_allocs.deinit(gpa);805 function.frame_allocs.deinit(gpa);
798 function.free_frame_indices.deinit(gpa);806 function.free_frame_indices.deinit(gpa);
799 function.frame_locs.deinit(gpa);807 function.frame_locs.deinit(gpa);
808 function.loops.deinit(gpa);
800 var block_it = function.blocks.valueIterator();809 var block_it = function.blocks.valueIterator();
801 while (block_it.next()) |block| block.deinit(gpa);810 while (block_it.next()) |block| block.deinit(gpa);
802 function.blocks.deinit(gpa);811 function.blocks.deinit(gpa);
...@@ -1579,6 +1588,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {...@@ -1579,6 +1588,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
1579 .bitcast => try func.airBitCast(inst),1588 .bitcast => try func.airBitCast(inst),
1580 .block => try func.airBlock(inst),1589 .block => try func.airBlock(inst),
1581 .br => try func.airBr(inst),1590 .br => try func.airBr(inst),
1591 .repeat => try func.airRepeat(inst),
1592 .switch_dispatch => try func.airSwitchDispatch(inst),
1582 .trap => try func.airTrap(),1593 .trap => try func.airTrap(),
1583 .breakpoint => try func.airBreakpoint(),1594 .breakpoint => try func.airBreakpoint(),
1584 .ret_addr => try func.airRetAddr(inst),1595 .ret_addr => try func.airRetAddr(inst),
...@@ -1668,6 +1679,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {...@@ -1668,6 +1679,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
1668 .field_parent_ptr => try func.airFieldParentPtr(inst),1679 .field_parent_ptr => try func.airFieldParentPtr(inst),
16691680
1670 .switch_br => try func.airSwitchBr(inst),1681 .switch_br => try func.airSwitchBr(inst),
1682 .loop_switch_br => try func.airLoopSwitchBr(inst),
16711683
1672 .ptr_slice_len_ptr => try func.airPtrSliceLenPtr(inst),1684 .ptr_slice_len_ptr => try func.airPtrSliceLenPtr(inst),
1673 .ptr_slice_ptr_ptr => try func.airPtrSlicePtrPtr(inst),1685 .ptr_slice_ptr_ptr => try func.airPtrSlicePtrPtr(inst),
...@@ -5638,15 +5650,13 @@ fn airLoop(func: *Func, inst: Air.Inst.Index) !void {...@@ -5638,15 +5650,13 @@ fn airLoop(func: *Func, inst: Air.Inst.Index) !void {
5638 func.scope_generation += 1;5650 func.scope_generation += 1;
5639 const state = try func.saveState();5651 const state = try func.saveState();
56405652
5641 const jmp_target: Mir.Inst.Index = @intCast(func.mir_instructions.len);5653 try func.loops.putNoClobber(func.gpa, inst, .{
5642 try func.genBody(body);5654 .state = state,
5643 try func.restoreState(state, &.{}, .{5655 .jmp_target = @intCast(func.mir_instructions.len),
5644 .emit_instructions = true,
5645 .update_tracking = false,
5646 .resurrect = false,
5647 .close_scope = true,
5648 });5656 });
5649 _ = try func.jump(jmp_target);5657 defer assert(func.loops.remove(inst));
5658
5659 try func.genBody(body);
56505660
5651 func.finishAirBookkeeping();5661 func.finishAirBookkeeping();
5652}5662}
...@@ -5701,12 +5711,7 @@ fn lowerBlock(func: *Func, inst: Air.Inst.Index, body: []const Air.Inst.Index) !...@@ -5701,12 +5711,7 @@ fn lowerBlock(func: *Func, inst: Air.Inst.Index, body: []const Air.Inst.Index) !
57015711
5702fn airSwitchBr(func: *Func, inst: Air.Inst.Index) !void {5712fn airSwitchBr(func: *Func, inst: Air.Inst.Index) !void {
5703 const switch_br = func.air.unwrapSwitch(inst);5713 const switch_br = func.air.unwrapSwitch(inst);
5704
5705 const liveness = try func.liveness.getSwitchBr(func.gpa, inst, switch_br.cases_len + 1);
5706 defer func.gpa.free(liveness.deaths);
5707
5708 const condition = try func.resolveInst(switch_br.operand);5714 const condition = try func.resolveInst(switch_br.operand);
5709 const condition_ty = func.typeOf(switch_br.operand);
57105715
5711 // If the condition dies here in this switch instruction, process5716 // If the condition dies here in this switch instruction, process
5712 // that death now instead of later as this has an effect on5717 // that death now instead of later as this has an effect on
...@@ -5715,15 +5720,31 @@ fn airSwitchBr(func: *Func, inst: Air.Inst.Index) !void {...@@ -5715,15 +5720,31 @@ fn airSwitchBr(func: *Func, inst: Air.Inst.Index) !void {
5715 if (switch_br.operand.toIndex()) |op_inst| try func.processDeath(op_inst);5720 if (switch_br.operand.toIndex()) |op_inst| try func.processDeath(op_inst);
5716 }5721 }
57175722
5723 try func.lowerSwitchBr(inst, switch_br, condition);
5724
5725 // We already took care of pl_op.operand earlier, so there's nothing left to do
5726 func.finishAirBookkeeping();
5727}
5728
5729fn lowerSwitchBr(
5730 func: *Func,
5731 inst: Air.Inst.Index,
5732 switch_br: Air.UnwrappedSwitch,
5733 condition: MCValue,
5734) !void {
5735 const condition_ty = func.typeOf(switch_br.operand);
5736 const liveness = try func.liveness.getSwitchBr(func.gpa, inst, switch_br.cases_len + 1);
5737 defer func.gpa.free(liveness.deaths);
5738
5718 func.scope_generation += 1;5739 func.scope_generation += 1;
5719 const state = try func.saveState();5740 const state = try func.saveState();
57205741
5721 var it = switch_br.iterateCases();5742 var it = switch_br.iterateCases();
5722 while (it.next()) |case| {5743 while (it.next()) |case| {
5723 var relocs = try func.gpa.alloc(Mir.Inst.Index, case.items.len);5744 var relocs = try func.gpa.alloc(Mir.Inst.Index, case.items.len + case.ranges.len);
5724 defer func.gpa.free(relocs);5745 defer func.gpa.free(relocs);
57255746
5726 for (case.items, relocs, 0..) |item, *reloc, i| {5747 for (case.items, relocs[0..case.items.len]) |item, *reloc| {
5727 const item_mcv = try func.resolveInst(item);5748 const item_mcv = try func.resolveInst(item);
57285749
5729 const cond_lock = switch (condition) {5750 const cond_lock = switch (condition) {
...@@ -5744,22 +5765,52 @@ fn airSwitchBr(func: *Func, inst: Air.Inst.Index) !void {...@@ -5744,22 +5765,52 @@ fn airSwitchBr(func: *Func, inst: Air.Inst.Index) !void {
5744 cmp_reg,5765 cmp_reg,
5745 );5766 );
57465767
5747 if (!(i < relocs.len - 1)) {
5748 _ = try func.addInst(.{
5749 .tag = .pseudo_not,
5750 .data = .{ .rr = .{
5751 .rd = cmp_reg,
5752 .rs = cmp_reg,
5753 } },
5754 });
5755 }
5756
5757 reloc.* = try func.condBr(condition_ty, .{ .register = cmp_reg });5768 reloc.* = try func.condBr(condition_ty, .{ .register = cmp_reg });
5758 }5769 }
57595770
5771 for (case.ranges, relocs[case.items.len..]) |range, *reloc| {
5772 const min_mcv = try func.resolveInst(range[0]);
5773 const max_mcv = try func.resolveInst(range[1]);
5774 const cond_lock = switch (condition) {
5775 .register => func.register_manager.lockRegAssumeUnused(condition.register),
5776 else => null,
5777 };
5778 defer if (cond_lock) |lock| func.register_manager.unlockReg(lock);
5779
5780 const temp_cmp_reg, const temp_cmp_lock = try func.allocReg(.int);
5781 defer func.register_manager.unlockReg(temp_cmp_lock);
5782
5783 // is `condition` less than `min`? is "true", we've failed
5784 try func.genBinOp(
5785 .cmp_gte,
5786 condition,
5787 condition_ty,
5788 min_mcv,
5789 condition_ty,
5790 temp_cmp_reg,
5791 );
5792
5793 // if the compare was true, we will jump to the fail case and fall through
5794 // to the next checks
5795 const lt_fail_reloc = try func.condBr(condition_ty, .{ .register = temp_cmp_reg });
5796 try func.genBinOp(
5797 .cmp_gt,
5798 condition,
5799 condition_ty,
5800 max_mcv,
5801 condition_ty,
5802 temp_cmp_reg,
5803 );
5804
5805 reloc.* = try func.condBr(condition_ty, .{ .register = temp_cmp_reg });
5806 func.performReloc(lt_fail_reloc);
5807 }
5808
5809 const skip_case_reloc = try func.jump(undefined);
5810
5760 for (liveness.deaths[case.idx]) |operand| try func.processDeath(operand);5811 for (liveness.deaths[case.idx]) |operand| try func.processDeath(operand);
57615812
5762 for (relocs[0 .. relocs.len - 1]) |reloc| func.performReloc(reloc);5813 for (relocs) |reloc| func.performReloc(reloc);
5763 try func.genBody(case.body);5814 try func.genBody(case.body);
5764 try func.restoreState(state, &.{}, .{5815 try func.restoreState(state, &.{}, .{
5765 .emit_instructions = false,5816 .emit_instructions = false,
...@@ -5768,7 +5819,7 @@ fn airSwitchBr(func: *Func, inst: Air.Inst.Index) !void {...@@ -5768,7 +5819,7 @@ fn airSwitchBr(func: *Func, inst: Air.Inst.Index) !void {
5768 .close_scope = true,5819 .close_scope = true,
5769 });5820 });
57705821
5771 func.performReloc(relocs[relocs.len - 1]);5822 func.performReloc(skip_case_reloc);
5772 }5823 }
57735824
5774 if (switch_br.else_body_len > 0) {5825 if (switch_br.else_body_len > 0) {
...@@ -5785,8 +5836,92 @@ fn airSwitchBr(func: *Func, inst: Air.Inst.Index) !void {...@@ -5785,8 +5836,92 @@ fn airSwitchBr(func: *Func, inst: Air.Inst.Index) !void {
5785 .close_scope = true,5836 .close_scope = true,
5786 });5837 });
5787 }5838 }
5839}
5840
5841fn airLoopSwitchBr(func: *Func, inst: Air.Inst.Index) !void {
5842 const switch_br = func.air.unwrapSwitch(inst);
5843 const condition = try func.resolveInst(switch_br.operand);
5844
5845 const mat_cond = if (condition.isMutable() and
5846 func.reuseOperand(inst, switch_br.operand, 0, condition))
5847 condition
5848 else mat_cond: {
5849 const ty = func.typeOf(switch_br.operand);
5850 const mat_cond = try func.allocRegOrMem(ty, inst, true);
5851 try func.genCopy(ty, mat_cond, condition);
5852 break :mat_cond mat_cond;
5853 };
5854 func.inst_tracking.putAssumeCapacityNoClobber(inst, InstTracking.init(mat_cond));
5855
5856 // If the condition dies here in this switch instruction, process
5857 // that death now instead of later as this has an effect on
5858 // whether it needs to be spilled in the branches
5859 if (func.liveness.operandDies(inst, 0)) {
5860 if (switch_br.operand.toIndex()) |op_inst| try func.processDeath(op_inst);
5861 }
5862
5863 func.scope_generation += 1;
5864 const state = try func.saveState();
5865
5866 try func.loops.putNoClobber(func.gpa, inst, .{
5867 .state = state,
5868 .jmp_target = @intCast(func.mir_instructions.len),
5869 });
5870 defer assert(func.loops.remove(inst));
5871
5872 // Stop tracking block result without forgetting tracking info
5873 try func.freeValue(mat_cond);
5874
5875 try func.lowerSwitchBr(inst, switch_br, mat_cond);
5876
5877 try func.processDeath(inst);
5878 func.finishAirBookkeeping();
5879}
5880
5881fn airSwitchDispatch(func: *Func, inst: Air.Inst.Index) !void {
5882 const br = func.air.instructions.items(.data)[@intFromEnum(inst)].br;
5883
5884 const block_ty = func.typeOfIndex(br.block_inst);
5885 const block_tracking = func.inst_tracking.getPtr(br.block_inst).?;
5886 const loop_data = func.loops.getPtr(br.block_inst).?;
5887 done: {
5888 try func.getValue(block_tracking.short, null);
5889 const src_mcv = try func.resolveInst(br.operand);
5890
5891 if (func.reuseOperandAdvanced(inst, br.operand, 0, src_mcv, br.block_inst)) {
5892 try func.getValue(block_tracking.short, br.block_inst);
5893 // .long = .none to avoid merging operand and block result stack frames.
5894 const current_tracking: InstTracking = .{ .long = .none, .short = src_mcv };
5895 try current_tracking.materializeUnsafe(func, br.block_inst, block_tracking.*);
5896 for (current_tracking.getRegs()) |src_reg| func.register_manager.freeReg(src_reg);
5897 break :done;
5898 }
5899
5900 try func.getValue(block_tracking.short, br.block_inst);
5901 const dst_mcv = block_tracking.short;
5902 try func.genCopy(block_ty, dst_mcv, try func.resolveInst(br.operand));
5903 break :done;
5904 }
5905
5906 // Process operand death so that it is properly accounted for in the State below.
5907 if (func.liveness.operandDies(inst, 0)) {
5908 if (br.operand.toIndex()) |op_inst| try func.processDeath(op_inst);
5909 }
5910
5911 try func.restoreState(loop_data.state, &.{}, .{
5912 .emit_instructions = true,
5913 .update_tracking = false,
5914 .resurrect = false,
5915 .close_scope = false,
5916 });
5917
5918 // Emit a jump with a relocation. It will be patched up after the block ends.
5919 // Leave the jump offset undefined
5920 _ = try func.jump(loop_data.jmp_target);
5921
5922 // Stop tracking block result without forgetting tracking info
5923 try func.freeValue(block_tracking.short);
57885924
5789 // We already took care of pl_op.operand earlier, so there's nothing left to do
5790 func.finishAirBookkeeping();5925 func.finishAirBookkeeping();
5791}5926}
57925927
...@@ -5865,6 +6000,19 @@ fn airBr(func: *Func, inst: Air.Inst.Index) !void {...@@ -5865,6 +6000,19 @@ fn airBr(func: *Func, inst: Air.Inst.Index) !void {
5865 func.finishAirBookkeeping();6000 func.finishAirBookkeeping();
5866}6001}
58676002
6003fn airRepeat(func: *Func, inst: Air.Inst.Index) !void {
6004 const loop_inst = func.air.instructions.items(.data)[@intFromEnum(inst)].repeat.loop_inst;
6005 const repeat_info = func.loops.get(loop_inst).?;
6006 try func.restoreState(repeat_info.state, &.{}, .{
6007 .emit_instructions = true,
6008 .update_tracking = false,
6009 .resurrect = false,
6010 .close_scope = true,
6011 });
6012 _ = try func.jump(repeat_info.jmp_target);
6013 func.finishAirBookkeeping();
6014}
6015
5868fn airBoolOp(func: *Func, inst: Air.Inst.Index) !void {6016fn airBoolOp(func: *Func, inst: Air.Inst.Index) !void {
5869 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;6017 const bin_op = func.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
5870 const tag: Air.Inst.Tag = func.air.instructions.items(.tag)[@intFromEnum(inst)];6018 const tag: Air.Inst.Tag = func.air.instructions.items(.tag)[@intFromEnum(inst)];
...@@ -8285,7 +8433,10 @@ fn typeOf(func: *Func, inst: Air.Inst.Ref) Type {...@@ -8285,7 +8433,10 @@ fn typeOf(func: *Func, inst: Air.Inst.Ref) Type {
82858433
8286fn typeOfIndex(func: *Func, inst: Air.Inst.Index) Type {8434fn typeOfIndex(func: *Func, inst: Air.Inst.Index) Type {
8287 const zcu = func.pt.zcu;8435 const zcu = func.pt.zcu;
8288 return func.air.typeOfIndex(inst, &zcu.intern_pool);8436 return switch (func.air.instructions.items(.tag)[@intFromEnum(inst)]) {
8437 .loop_switch_br => func.typeOf(func.air.unwrapSwitch(inst).operand),
8438 else => func.air.typeOfIndex(inst, &zcu.intern_pool),
8439 };
8289}8440}
82908441
8291fn hasFeature(func: *Func, feature: Target.riscv.Feature) bool {8442fn hasFeature(func: *Func, feature: Target.riscv.Feature) bool {
src/arch/sparc64/CodeGen.zig+3
...@@ -576,6 +576,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -576,6 +576,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
576 .bitcast => try self.airBitCast(inst),576 .bitcast => try self.airBitCast(inst),
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`", .{}),
580 .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}),
579 .trap => try self.airTrap(),581 .trap => try self.airTrap(),
580 .breakpoint => try self.airBreakpoint(),582 .breakpoint => try self.airBreakpoint(),
581 .ret_addr => @panic("TODO try self.airRetAddr(inst)"),583 .ret_addr => @panic("TODO try self.airRetAddr(inst)"),
...@@ -666,6 +668,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -666,6 +668,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
666 .field_parent_ptr => @panic("TODO try self.airFieldParentPtr(inst)"),668 .field_parent_ptr => @panic("TODO try self.airFieldParentPtr(inst)"),
667669
668 .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`", .{}),
669 .slice_ptr => try self.airSlicePtr(inst),672 .slice_ptr => try self.airSlicePtr(inst),
670 .slice_len => try self.airSliceLen(inst),673 .slice_len => try self.airSliceLen(inst),
671674
src/arch/wasm/CodeGen.zig+74-36
...@@ -662,6 +662,8 @@ blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {...@@ -662,6 +662,8 @@ blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {
662 label: u32,662 label: u32,
663 value: WValue,663 value: WValue,
664}) = .{},664}) = .{},
665/// Maps `loop` instructions to their label. `br` to here repeats the loop.
666loops: std.AutoHashMapUnmanaged(Air.Inst.Index, u32) = .{},
665/// `bytes` contains the wasm bytecode belonging to the 'code' section.667/// `bytes` contains the wasm bytecode belonging to the 'code' section.
666code: *ArrayList(u8),668code: *ArrayList(u8),
667/// The index the next local generated will have669/// The index the next local generated will have
...@@ -751,6 +753,7 @@ pub fn deinit(func: *CodeGen) void {...@@ -751,6 +753,7 @@ pub fn deinit(func: *CodeGen) void {
751 }753 }
752 func.branches.deinit(func.gpa);754 func.branches.deinit(func.gpa);
753 func.blocks.deinit(func.gpa);755 func.blocks.deinit(func.gpa);
756 func.loops.deinit(func.gpa);
754 func.locals.deinit(func.gpa);757 func.locals.deinit(func.gpa);
755 func.simd_immediates.deinit(func.gpa);758 func.simd_immediates.deinit(func.gpa);
756 func.mir_instructions.deinit(func.gpa);759 func.mir_instructions.deinit(func.gpa);
...@@ -1903,6 +1906,8 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -1903,6 +1906,8 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
1903 .trap => func.airTrap(inst),1906 .trap => func.airTrap(inst),
1904 .breakpoint => func.airBreakpoint(inst),1907 .breakpoint => func.airBreakpoint(inst),
1905 .br => func.airBr(inst),1908 .br => func.airBr(inst),
1909 .repeat => func.airRepeat(inst),
1910 .switch_dispatch => return func.fail("TODO implement `switch_dispatch`", .{}),
1906 .int_from_bool => func.airIntFromBool(inst),1911 .int_from_bool => func.airIntFromBool(inst),
1907 .cond_br => func.airCondBr(inst),1912 .cond_br => func.airCondBr(inst),
1908 .intcast => func.airIntcast(inst),1913 .intcast => func.airIntcast(inst),
...@@ -1984,6 +1989,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -1984,6 +1989,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
1984 .field_parent_ptr => func.airFieldParentPtr(inst),1989 .field_parent_ptr => func.airFieldParentPtr(inst),
19851990
1986 .switch_br => func.airSwitchBr(inst),1991 .switch_br => func.airSwitchBr(inst),
1992 .loop_switch_br => return func.fail("TODO implement `loop_switch_br`", .{}),
1987 .trunc => func.airTrunc(inst),1993 .trunc => func.airTrunc(inst),
1988 .unreach => func.airUnreachable(inst),1994 .unreach => func.airUnreachable(inst),
19891995
...@@ -3534,10 +3540,11 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3534,10 +3540,11 @@ fn airLoop(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3534 // result type of loop is always 'noreturn', meaning we can always3540 // result type of loop is always 'noreturn', meaning we can always
3535 // emit the wasm type 'block_empty'.3541 // emit the wasm type 'block_empty'.
3536 try func.startBlock(.loop, wasm.block_empty);3542 try func.startBlock(.loop, wasm.block_empty);
3537 try func.genBody(body);
35383543
3539 // breaking to the index of a loop block will continue the loop instead3544 try func.loops.putNoClobber(func.gpa, inst, func.block_depth);
3540 try func.addLabel(.br, 0);3545 defer assert(func.loops.remove(inst));
3546
3547 try func.genBody(body);
3541 try func.endBlock();3548 try func.endBlock();
35423549
3543 return func.finishAir(inst, .none, &.{});3550 return func.finishAir(inst, .none, &.{});
...@@ -3734,6 +3741,16 @@ fn airBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3734,6 +3741,16 @@ fn airBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3734 return func.finishAir(inst, .none, &.{br.operand});3741 return func.finishAir(inst, .none, &.{br.operand});
3735}3742}
37363743
3744fn airRepeat(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3745 const repeat = func.air.instructions.items(.data)[@intFromEnum(inst)].repeat;
3746 const loop_label = func.loops.get(repeat.loop_inst).?;
3747
3748 const idx: u32 = func.block_depth - loop_label;
3749 try func.addLabel(.br, idx);
3750
3751 return func.finishAir(inst, .none, &.{});
3752}
3753
3737fn airNot(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {3754fn airNot(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3738 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;3755 const ty_op = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
37393756
...@@ -4050,7 +4067,10 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -4050,7 +4067,10 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
4050 defer func.gpa.free(liveness.deaths);4067 defer func.gpa.free(liveness.deaths);
40514068
4052 // a list that maps each value with its value and body based on the order inside the list.4069 // a list that maps each value with its value and body based on the order inside the list.
4053 const CaseValue = struct { integer: i32, value: Value };4070 const CaseValue = union(enum) {
4071 singular: struct { integer: i32, value: Value },
4072 range: struct { min: i32, min_value: Value, max: i32, max_value: Value },
4073 };
4054 var case_list = try std.ArrayList(struct {4074 var case_list = try std.ArrayList(struct {
4055 values: []const CaseValue,4075 values: []const CaseValue,
4056 body: []const Air.Inst.Index,4076 body: []const Air.Inst.Index,
...@@ -4061,10 +4081,9 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -4061,10 +4081,9 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
40614081
4062 var lowest_maybe: ?i32 = null;4082 var lowest_maybe: ?i32 = null;
4063 var highest_maybe: ?i32 = null;4083 var highest_maybe: ?i32 = null;
4064
4065 var it = switch_br.iterateCases();4084 var it = switch_br.iterateCases();
4066 while (it.next()) |case| {4085 while (it.next()) |case| {
4067 const values = try func.gpa.alloc(CaseValue, case.items.len);4086 const values = try func.gpa.alloc(CaseValue, case.items.len + case.ranges.len);
4068 errdefer func.gpa.free(values);4087 errdefer func.gpa.free(values);
40694088
4070 for (case.items, 0..) |ref, i| {4089 for (case.items, 0..) |ref, i| {
...@@ -4076,7 +4095,30 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -4076,7 +4095,30 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
4076 if (highest_maybe == null or int_val > highest_maybe.?) {4095 if (highest_maybe == null or int_val > highest_maybe.?) {
4077 highest_maybe = int_val;4096 highest_maybe = int_val;
4078 }4097 }
4079 values[i] = .{ .integer = int_val, .value = item_val };4098 values[i] = .{ .singular = .{ .integer = int_val, .value = item_val } };
4099 }
4100
4101 for (case.ranges, 0..) |range, i| {
4102 const min_val = (try func.air.value(range[0], pt)).?;
4103 const int_min_val = func.valueAsI32(min_val);
4104
4105 if (lowest_maybe == null or int_min_val < lowest_maybe.?) {
4106 lowest_maybe = int_min_val;
4107 }
4108
4109 const max_val = (try func.air.value(range[1], pt)).?;
4110 const int_max_val = func.valueAsI32(max_val);
4111
4112 if (highest_maybe == null or int_max_val > highest_maybe.?) {
4113 highest_maybe = int_max_val;
4114 }
4115
4116 values[i + case.items.len] = .{ .range = .{
4117 .min = int_min_val,
4118 .min_value = min_val,
4119 .max = int_max_val,
4120 .max_value = max_val,
4121 } };
4080 }4122 }
40814123
4082 case_list.appendAssumeCapacity(.{ .values = values, .body = case.body });4124 case_list.appendAssumeCapacity(.{ .values = values, .body = case.body });
...@@ -4129,7 +4171,12 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -4129,7 +4171,12 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
4129 const idx = blk: {4171 const idx = blk: {
4130 for (case_list.items, 0..) |case, idx| {4172 for (case_list.items, 0..) |case, idx| {
4131 for (case.values) |case_value| {4173 for (case.values) |case_value| {
4132 if (case_value.integer == value) break :blk @as(u32, @intCast(idx));4174 switch (case_value) {
4175 .singular => |val| if (val.integer == value) break :blk @as(u32, @intCast(idx)),
4176 .range => |range_val| if (value >= range_val.min and value <= range_val.max) {
4177 break :blk @as(u32, @intCast(idx));
4178 },
4179 }
4133 }4180 }
4134 }4181 }
4135 // error sets are almost always sparse so we use the default case4182 // error sets are almost always sparse so we use the default case
...@@ -4145,43 +4192,34 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -4145,43 +4192,34 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
4145 try func.endBlock();4192 try func.endBlock();
4146 }4193 }
41474194
4148 const signedness: std.builtin.Signedness = blk: {
4149 // by default we tell the operand type is unsigned (i.e. bools and enum values)
4150 if (target_ty.zigTypeTag(zcu) != .int) break :blk .unsigned;
4151
4152 // incase of an actual integer, we emit the correct signedness
4153 break :blk target_ty.intInfo(zcu).signedness;
4154 };
4155
4156 try func.branches.ensureUnusedCapacity(func.gpa, case_list.items.len + @intFromBool(has_else_body));4195 try func.branches.ensureUnusedCapacity(func.gpa, case_list.items.len + @intFromBool(has_else_body));
4157 for (case_list.items, 0..) |case, index| {4196 for (case_list.items, 0..) |case, index| {
4158 // when sparse, we use if/else-chain, so emit conditional checks4197 // when sparse, we use if/else-chain, so emit conditional checks
4159 if (is_sparse) {4198 if (is_sparse) {
4160 // for single value prong we can emit a simple if4199 // for single value prong we can emit a simple condition
4161 if (case.values.len == 1) {4200 if (case.values.len == 1 and case.values[0] == .singular) {
4162 try func.emitWValue(target);4201 const val = try func.lowerConstant(case.values[0].singular.value, target_ty);
4163 const val = try func.lowerConstant(case.values[0].value, target_ty);4202 // not equal, because we want to jump out of this block if it does not match the condition.
4164 try func.emitWValue(val);4203 _ = try func.cmp(target, val, target_ty, .neq);
4165 const opcode = buildOpcode(.{
4166 .valtype1 = typeToValtype(target_ty, pt, func.target.*),
4167 .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition.
4168 .signedness = signedness,
4169 });
4170 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
4171 try func.addLabel(.br_if, 0);4204 try func.addLabel(.br_if, 0);
4172 } else {4205 } else {
4173 // in multi-value prongs we must check if any prongs match the target value.4206 // in multi-value prongs we must check if any prongs match the target value.
4174 try func.startBlock(.block, blocktype);4207 try func.startBlock(.block, blocktype);
4175 for (case.values) |value| {4208 for (case.values) |value| {
4176 try func.emitWValue(target);4209 switch (value) {
4177 const val = try func.lowerConstant(value.value, target_ty);4210 .singular => |single_val| {
4178 try func.emitWValue(val);4211 const val = try func.lowerConstant(single_val.value, target_ty);
4179 const opcode = buildOpcode(.{4212 _ = try func.cmp(target, val, target_ty, .eq);
4180 .valtype1 = typeToValtype(target_ty, pt, func.target.*),4213 },
4181 .op = .eq,4214 .range => |range| {
4182 .signedness = signedness,4215 const min_val = try func.lowerConstant(range.min_value, target_ty);
4183 });4216 const max_val = try func.lowerConstant(range.max_value, target_ty);
4184 try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));4217
4218 const gte = try func.cmp(target, min_val, target_ty, .gte);
4219 const lte = try func.cmp(target, max_val, target_ty, .lte);
4220 _ = try func.binOp(gte, lte, Type.bool, .@"and");
4221 },
4222 }
4185 try func.addLabel(.br_if, 0);4223 try func.addLabel(.br_if, 0);
4186 }4224 }
4187 // value did not match any of the prong values4225 // value did not match any of the prong values
src/arch/x86_64/CodeGen.zig+241-34
...@@ -105,6 +105,13 @@ frame_allocs: std.MultiArrayList(FrameAlloc) = .{},...@@ -105,6 +105,13 @@ frame_allocs: std.MultiArrayList(FrameAlloc) = .{},
105free_frame_indices: std.AutoArrayHashMapUnmanaged(FrameIndex, void) = .{},105free_frame_indices: std.AutoArrayHashMapUnmanaged(FrameIndex, void) = .{},
106frame_locs: std.MultiArrayList(Mir.FrameLoc) = .{},106frame_locs: std.MultiArrayList(Mir.FrameLoc) = .{},
107107
108loops: std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
109 /// The state to restore before branching.
110 state: State,
111 /// The branch target.
112 jmp_target: Mir.Inst.Index,
113}) = .{},
114
108/// Debug field, used to find bugs in the compiler.115/// Debug field, used to find bugs in the compiler.
109air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init,116air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init,
110117
...@@ -212,6 +219,38 @@ pub const MCValue = union(enum) {...@@ -212,6 +219,38 @@ pub const MCValue = union(enum) {
212 reserved_frame: FrameIndex,219 reserved_frame: FrameIndex,
213 air_ref: Air.Inst.Ref,220 air_ref: Air.Inst.Ref,
214221
222 fn isModifiable(mcv: MCValue) bool {
223 return switch (mcv) {
224 .none,
225 .unreach,
226 .dead,
227 .undef,
228 .immediate,
229 .register_offset,
230 .eflags,
231 .register_overflow,
232 .lea_symbol,
233 .lea_direct,
234 .lea_got,
235 .lea_tlv,
236 .lea_frame,
237 .elementwise_regs_then_frame,
238 .reserved_frame,
239 .air_ref,
240 => false,
241 .register,
242 .register_pair,
243 .memory,
244 .load_symbol,
245 .load_got,
246 .load_direct,
247 .load_tlv,
248 .indirect,
249 => true,
250 .load_frame => |frame_addr| !frame_addr.index.isNamed(),
251 };
252 }
253
215 fn isMemory(mcv: MCValue) bool {254 fn isMemory(mcv: MCValue) bool {
216 return switch (mcv) {255 return switch (mcv) {
217 .memory, .indirect, .load_frame => true,256 .memory, .indirect, .load_frame => true,
...@@ -815,6 +854,7 @@ pub fn generate(...@@ -815,6 +854,7 @@ pub fn generate(
815 function.frame_allocs.deinit(gpa);854 function.frame_allocs.deinit(gpa);
816 function.free_frame_indices.deinit(gpa);855 function.free_frame_indices.deinit(gpa);
817 function.frame_locs.deinit(gpa);856 function.frame_locs.deinit(gpa);
857 function.loops.deinit(gpa);
818 var block_it = function.blocks.valueIterator();858 var block_it = function.blocks.valueIterator();
819 while (block_it.next()) |block| block.deinit(gpa);859 while (block_it.next()) |block| block.deinit(gpa);
820 function.blocks.deinit(gpa);860 function.blocks.deinit(gpa);
...@@ -2148,18 +2188,20 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -2148,18 +2188,20 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
2148 const air_tags = self.air.instructions.items(.tag);2188 const air_tags = self.air.instructions.items(.tag);
21492189
2150 self.arg_index = 0;2190 self.arg_index = 0;
2151 for (body) |inst| {2191 for (body) |inst| switch (air_tags[@intFromEnum(inst)]) {
2152 wip_mir_log.debug("{}", .{self.fmtAir(inst)});2192 .arg => {
2153 verbose_tracking_log.debug("{}", .{self.fmtTracking()});2193 wip_mir_log.debug("{}", .{self.fmtAir(inst)});
2194 verbose_tracking_log.debug("{}", .{self.fmtTracking()});
21542195
2155 const old_air_bookkeeping = self.air_bookkeeping;2196 const old_air_bookkeeping = self.air_bookkeeping;
2156 try self.inst_tracking.ensureUnusedCapacity(self.gpa, 1);2197 try self.inst_tracking.ensureUnusedCapacity(self.gpa, 1);
2157 switch (air_tags[@intFromEnum(inst)]) {2198
2158 .arg => try self.airArg(inst),2199 try self.airArg(inst);
2159 else => break,2200
2160 }2201 self.checkInvariantsAfterAirInst(inst, old_air_bookkeeping);
2161 self.checkInvariantsAfterAirInst(inst, old_air_bookkeeping);2202 },
2162 }2203 else => break,
2204 };
21632205
2164 if (self.arg_index == 0) try self.airDbgVarArgs();2206 if (self.arg_index == 0) try self.airDbgVarArgs();
2165 self.arg_index = 0;2207 self.arg_index = 0;
...@@ -2247,6 +2289,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -2247,6 +2289,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
2247 .bitcast => try self.airBitCast(inst),2289 .bitcast => try self.airBitCast(inst),
2248 .block => try self.airBlock(inst),2290 .block => try self.airBlock(inst),
2249 .br => try self.airBr(inst),2291 .br => try self.airBr(inst),
2292 .repeat => try self.airRepeat(inst),
2293 .switch_dispatch => try self.airSwitchDispatch(inst),
2250 .trap => try self.airTrap(),2294 .trap => try self.airTrap(),
2251 .breakpoint => try self.airBreakpoint(),2295 .breakpoint => try self.airBreakpoint(),
2252 .ret_addr => try self.airRetAddr(inst),2296 .ret_addr => try self.airRetAddr(inst),
...@@ -2335,6 +2379,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -2335,6 +2379,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
2335 .field_parent_ptr => try self.airFieldParentPtr(inst),2379 .field_parent_ptr => try self.airFieldParentPtr(inst),
23362380
2337 .switch_br => try self.airSwitchBr(inst),2381 .switch_br => try self.airSwitchBr(inst),
2382 .loop_switch_br => try self.airLoopSwitchBr(inst),
2338 .slice_ptr => try self.airSlicePtr(inst),2383 .slice_ptr => try self.airSlicePtr(inst),
2339 .slice_len => try self.airSliceLen(inst),2384 .slice_len => try self.airSliceLen(inst),
23402385
...@@ -13626,16 +13671,13 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {...@@ -13626,16 +13671,13 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
13626 self.scope_generation += 1;13671 self.scope_generation += 1;
13627 const state = try self.saveState();13672 const state = try self.saveState();
1362813673
13629 const jmp_target: Mir.Inst.Index = @intCast(self.mir_instructions.len);13674 try self.loops.putNoClobber(self.gpa, inst, .{
13630 try self.genBody(body);13675 .state = state,
13631 try self.restoreState(state, &.{}, .{13676 .jmp_target = @intCast(self.mir_instructions.len),
13632 .emit_instructions = true,
13633 .update_tracking = false,
13634 .resurrect = false,
13635 .close_scope = true,
13636 });13677 });
13637 _ = try self.asmJmpReloc(jmp_target);13678 defer assert(self.loops.remove(inst));
1363813679
13680 try self.genBody(body);
13639 self.finishAirBookkeeping();13681 self.finishAirBookkeeping();
13640}13682}
1364113683
...@@ -13676,30 +13718,28 @@ fn lowerBlock(self: *Self, inst: Air.Inst.Index, body: []const Air.Inst.Index) !...@@ -13676,30 +13718,28 @@ fn lowerBlock(self: *Self, inst: Air.Inst.Index, body: []const Air.Inst.Index) !
13676 self.finishAirBookkeeping();13718 self.finishAirBookkeeping();
13677}13719}
1367813720
13679fn airSwitchBr(self: *Self, inst: Air.Inst.Index) !void {13721fn lowerSwitchBr(self: *Self, inst: Air.Inst.Index, switch_br: Air.UnwrappedSwitch, condition: MCValue) !void {
13680 const switch_br = self.air.unwrapSwitch(inst);13722 const zcu = self.pt.zcu;
13681 const condition = try self.resolveInst(switch_br.operand);
13682 const condition_ty = self.typeOf(switch_br.operand);13723 const condition_ty = self.typeOf(switch_br.operand);
13683 const liveness = try self.liveness.getSwitchBr(self.gpa, inst, switch_br.cases_len + 1);13724 const liveness = try self.liveness.getSwitchBr(self.gpa, inst, switch_br.cases_len + 1);
13684 defer self.gpa.free(liveness.deaths);13725 defer self.gpa.free(liveness.deaths);
1368513726
13686 // If the condition dies here in this switch instruction, process13727 const signedness = switch (condition_ty.zigTypeTag(zcu)) {
13687 // that death now instead of later as this has an effect on13728 .bool, .pointer => .unsigned,
13688 // whether it needs to be spilled in the branches13729 .int, .@"enum", .error_set => condition_ty.intInfo(zcu).signedness,
13689 if (self.liveness.operandDies(inst, 0)) {13730 else => unreachable,
13690 if (switch_br.operand.toIndex()) |op_inst| try self.processDeath(op_inst);13731 };
13691 }
1369213732
13693 self.scope_generation += 1;13733 self.scope_generation += 1;
13694 const state = try self.saveState();13734 const state = try self.saveState();
1369513735
13696 var it = switch_br.iterateCases();13736 var it = switch_br.iterateCases();
13697 while (it.next()) |case| {13737 while (it.next()) |case| {
13698 var relocs = try self.gpa.alloc(Mir.Inst.Index, case.items.len);13738 var relocs = try self.gpa.alloc(Mir.Inst.Index, case.items.len + case.ranges.len);
13699 defer self.gpa.free(relocs);13739 defer self.gpa.free(relocs);
1370013740
13701 try self.spillEflagsIfOccupied();13741 try self.spillEflagsIfOccupied();
13702 for (case.items, relocs, 0..) |item, *reloc, i| {13742 for (case.items, relocs[0..case.items.len]) |item, *reloc| {
13703 const item_mcv = try self.resolveInst(item);13743 const item_mcv = try self.resolveInst(item);
13704 const cc: Condition = switch (condition) {13744 const cc: Condition = switch (condition) {
13705 .eflags => |cc| switch (item_mcv.immediate) {13745 .eflags => |cc| switch (item_mcv.immediate) {
...@@ -13712,12 +13752,62 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) !void {...@@ -13712,12 +13752,62 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) !void {
13712 break :cc .e;13752 break :cc .e;
13713 },13753 },
13714 };13754 };
13715 reloc.* = try self.asmJccReloc(if (i < relocs.len - 1) cc else cc.negate(), undefined);13755 reloc.* = try self.asmJccReloc(cc, undefined);
13756 }
13757
13758 for (case.ranges, relocs[case.items.len..]) |range, *reloc| {
13759 const min_mcv = try self.resolveInst(range[0]);
13760 const max_mcv = try self.resolveInst(range[1]);
13761 // `null` means always false.
13762 const lt_min: ?Condition = switch (condition) {
13763 .eflags => |cc| switch (min_mcv.immediate) {
13764 0 => null, // condition never <0
13765 1 => cc.negate(),
13766 else => unreachable,
13767 },
13768 else => cc: {
13769 try self.genBinOpMir(.{ ._, .cmp }, condition_ty, condition, min_mcv);
13770 break :cc switch (signedness) {
13771 .unsigned => .b,
13772 .signed => .l,
13773 };
13774 },
13775 };
13776 const lt_min_reloc = if (lt_min) |cc| r: {
13777 break :r try self.asmJccReloc(cc, undefined);
13778 } else null;
13779 // `null` means always true.
13780 const lte_max: ?Condition = switch (condition) {
13781 .eflags => |cc| switch (max_mcv.immediate) {
13782 0 => cc.negate(),
13783 1 => null, // condition always >=1
13784 else => unreachable,
13785 },
13786 else => cc: {
13787 try self.genBinOpMir(.{ ._, .cmp }, condition_ty, condition, max_mcv);
13788 break :cc switch (signedness) {
13789 .unsigned => .be,
13790 .signed => .le,
13791 };
13792 },
13793 };
13794 // "Success" case is in `reloc`....
13795 if (lte_max) |cc| {
13796 reloc.* = try self.asmJccReloc(cc, undefined);
13797 } else {
13798 reloc.* = try self.asmJmpReloc(undefined);
13799 }
13800 // ...and "fail" case falls through to next checks.
13801 if (lt_min_reloc) |r| self.performReloc(r);
13716 }13802 }
1371713803
13804 // The jump to skip this case if the conditions all failed.
13805 const skip_case_reloc = try self.asmJmpReloc(undefined);
13806
13718 for (liveness.deaths[case.idx]) |operand| try self.processDeath(operand);13807 for (liveness.deaths[case.idx]) |operand| try self.processDeath(operand);
1371913808
13720 for (relocs[0 .. relocs.len - 1]) |reloc| self.performReloc(reloc);13809 // Relocate all success cases to the body we're about to generate.
13810 for (relocs) |reloc| self.performReloc(reloc);
13721 try self.genBody(case.body);13811 try self.genBody(case.body);
13722 try self.restoreState(state, &.{}, .{13812 try self.restoreState(state, &.{}, .{
13723 .emit_instructions = false,13813 .emit_instructions = false,
...@@ -13726,7 +13816,8 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) !void {...@@ -13726,7 +13816,8 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) !void {
13726 .close_scope = true,13816 .close_scope = true,
13727 });13817 });
1372813818
13729 self.performReloc(relocs[relocs.len - 1]);13819 // Relocate the "skip" branch to fall through to the next case.
13820 self.performReloc(skip_case_reloc);
13730 }13821 }
1373113822
13732 if (switch_br.else_body_len > 0) {13823 if (switch_br.else_body_len > 0) {
...@@ -13743,11 +13834,111 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) !void {...@@ -13743,11 +13834,111 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) !void {
13743 .close_scope = true,13834 .close_scope = true,
13744 });13835 });
13745 }13836 }
13837}
13838
13839fn airSwitchBr(self: *Self, inst: Air.Inst.Index) !void {
13840 const switch_br = self.air.unwrapSwitch(inst);
13841 const condition = try self.resolveInst(switch_br.operand);
13842
13843 // If the condition dies here in this switch instruction, process
13844 // that death now instead of later as this has an effect on
13845 // whether it needs to be spilled in the branches
13846 if (self.liveness.operandDies(inst, 0)) {
13847 if (switch_br.operand.toIndex()) |op_inst| try self.processDeath(op_inst);
13848 }
13849
13850 try self.lowerSwitchBr(inst, switch_br, condition);
1374613851
13747 // We already took care of pl_op.operand earlier, so there's nothing left to do13852 // We already took care of pl_op.operand earlier, so there's nothing left to do
13748 self.finishAirBookkeeping();13853 self.finishAirBookkeeping();
13749}13854}
1375013855
13856fn airLoopSwitchBr(self: *Self, inst: Air.Inst.Index) !void {
13857 const switch_br = self.air.unwrapSwitch(inst);
13858 const condition = try self.resolveInst(switch_br.operand);
13859
13860 const mat_cond = if (condition.isModifiable() and
13861 self.reuseOperand(inst, switch_br.operand, 0, condition))
13862 condition
13863 else mat_cond: {
13864 const mat_cond = try self.allocRegOrMem(inst, true);
13865 try self.genCopy(self.typeOf(switch_br.operand), mat_cond, condition, .{});
13866 break :mat_cond mat_cond;
13867 };
13868 self.inst_tracking.putAssumeCapacityNoClobber(inst, InstTracking.init(mat_cond));
13869
13870 // If the condition dies here in this switch instruction, process
13871 // that death now instead of later as this has an effect on
13872 // whether it needs to be spilled in the branches
13873 if (self.liveness.operandDies(inst, 0)) {
13874 if (switch_br.operand.toIndex()) |op_inst| try self.processDeath(op_inst);
13875 }
13876
13877 self.scope_generation += 1;
13878 const state = try self.saveState();
13879
13880 try self.loops.putNoClobber(self.gpa, inst, .{
13881 .state = state,
13882 .jmp_target = @intCast(self.mir_instructions.len),
13883 });
13884 defer assert(self.loops.remove(inst));
13885
13886 // Stop tracking block result without forgetting tracking info
13887 try self.freeValue(mat_cond);
13888
13889 try self.lowerSwitchBr(inst, switch_br, mat_cond);
13890
13891 try self.processDeath(inst);
13892 self.finishAirBookkeeping();
13893}
13894
13895fn airSwitchDispatch(self: *Self, inst: Air.Inst.Index) !void {
13896 const br = self.air.instructions.items(.data)[@intFromEnum(inst)].br;
13897
13898 const block_ty = self.typeOfIndex(br.block_inst);
13899 const block_tracking = self.inst_tracking.getPtr(br.block_inst).?;
13900 const loop_data = self.loops.getPtr(br.block_inst).?;
13901 done: {
13902 try self.getValue(block_tracking.short, null);
13903 const src_mcv = try self.resolveInst(br.operand);
13904
13905 if (self.reuseOperandAdvanced(inst, br.operand, 0, src_mcv, br.block_inst)) {
13906 try self.getValue(block_tracking.short, br.block_inst);
13907 // .long = .none to avoid merging operand and block result stack frames.
13908 const current_tracking: InstTracking = .{ .long = .none, .short = src_mcv };
13909 try current_tracking.materializeUnsafe(self, br.block_inst, block_tracking.*);
13910 for (current_tracking.getRegs()) |src_reg| self.register_manager.freeReg(src_reg);
13911 break :done;
13912 }
13913
13914 try self.getValue(block_tracking.short, br.block_inst);
13915 const dst_mcv = block_tracking.short;
13916 try self.genCopy(block_ty, dst_mcv, try self.resolveInst(br.operand), .{});
13917 break :done;
13918 }
13919
13920 // Process operand death so that it is properly accounted for in the State below.
13921 if (self.liveness.operandDies(inst, 0)) {
13922 if (br.operand.toIndex()) |op_inst| try self.processDeath(op_inst);
13923 }
13924
13925 try self.restoreState(loop_data.state, &.{}, .{
13926 .emit_instructions = true,
13927 .update_tracking = false,
13928 .resurrect = false,
13929 .close_scope = false,
13930 });
13931
13932 // Emit a jump with a relocation. It will be patched up after the block ends.
13933 // Leave the jump offset undefined
13934 _ = try self.asmJmpReloc(loop_data.jmp_target);
13935
13936 // Stop tracking block result without forgetting tracking info
13937 try self.freeValue(block_tracking.short);
13938
13939 self.finishAirBookkeeping();
13940}
13941
13751fn performReloc(self: *Self, reloc: Mir.Inst.Index) void {13942fn performReloc(self: *Self, reloc: Mir.Inst.Index) void {
13752 const next_inst: u32 = @intCast(self.mir_instructions.len);13943 const next_inst: u32 = @intCast(self.mir_instructions.len);
13753 switch (self.mir_instructions.items(.tag)[reloc]) {13944 switch (self.mir_instructions.items(.tag)[reloc]) {
...@@ -13822,6 +14013,19 @@ fn airBr(self: *Self, inst: Air.Inst.Index) !void {...@@ -13822,6 +14013,19 @@ fn airBr(self: *Self, inst: Air.Inst.Index) !void {
13822 self.finishAirBookkeeping();14013 self.finishAirBookkeeping();
13823}14014}
1382414015
14016fn airRepeat(self: *Self, inst: Air.Inst.Index) !void {
14017 const loop_inst = self.air.instructions.items(.data)[@intFromEnum(inst)].repeat.loop_inst;
14018 const repeat_info = self.loops.get(loop_inst).?;
14019 try self.restoreState(repeat_info.state, &.{}, .{
14020 .emit_instructions = true,
14021 .update_tracking = false,
14022 .resurrect = false,
14023 .close_scope = true,
14024 });
14025 _ = try self.asmJmpReloc(repeat_info.jmp_target);
14026 self.finishAirBookkeeping();
14027}
14028
13825fn airAsm(self: *Self, inst: Air.Inst.Index) !void {14029fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
13826 const pt = self.pt;14030 const pt = self.pt;
13827 const zcu = pt.zcu;14031 const zcu = pt.zcu;
...@@ -19498,7 +19702,10 @@ fn typeOf(self: *Self, inst: Air.Inst.Ref) Type {...@@ -19498,7 +19702,10 @@ fn typeOf(self: *Self, inst: Air.Inst.Ref) Type {
19498fn typeOfIndex(self: *Self, inst: Air.Inst.Index) Type {19702fn typeOfIndex(self: *Self, inst: Air.Inst.Index) Type {
19499 const pt = self.pt;19703 const pt = self.pt;
19500 const zcu = pt.zcu;19704 const zcu = pt.zcu;
19501 return self.air.typeOfIndex(inst, &zcu.intern_pool);19705 return switch (self.air.instructions.items(.tag)[@intFromEnum(inst)]) {
19706 .loop_switch_br => self.typeOf(self.air.unwrapSwitch(inst).operand),
19707 else => self.air.typeOfIndex(inst, &zcu.intern_pool),
19708 };
19502}19709}
1950319710
19504fn intCompilerRtAbiName(int_bits: u32) u8 {19711fn intCompilerRtAbiName(int_bits: u32) u8 {
src/codegen/c.zig+184-60
...@@ -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 {
...@@ -3137,11 +3141,9 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,...@@ -3137,11 +3141,9 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
31373141
3138 .arg => try airArg(f, inst),3142 .arg => try airArg(f, inst),
31393143
3140 .trap => try airTrap(f, f.object.writer()),
3141 .breakpoint => try airBreakpoint(f.object.writer()),3144 .breakpoint => try airBreakpoint(f.object.writer()),
3142 .ret_addr => try airRetAddr(f, inst),3145 .ret_addr => try airRetAddr(f, inst),
3143 .frame_addr => try airFrameAddress(f, inst),3146 .frame_addr => try airFrameAddress(f, inst),
3144 .unreach => try airUnreach(f),
3145 .fence => try airFence(f, inst),3147 .fence => try airFence(f, inst),
31463148
3147 .ptr_add => try airPtrAddSub(f, inst, '+'),3149 .ptr_add => try airPtrAddSub(f, inst, '+'),
...@@ -3248,21 +3250,13 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,...@@ -3248,21 +3250,13 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
3248 .alloc => try airAlloc(f, inst),3250 .alloc => try airAlloc(f, inst),
3249 .ret_ptr => try airRetPtr(f, inst),3251 .ret_ptr => try airRetPtr(f, inst),
3250 .assembly => try airAsm(f, inst),3252 .assembly => try airAsm(f, inst),
3251 .block => try airBlock(f, inst),
3252 .bitcast => try airBitcast(f, inst),3253 .bitcast => try airBitcast(f, inst),
3253 .intcast => try airIntCast(f, inst),3254 .intcast => try airIntCast(f, inst),
3254 .trunc => try airTrunc(f, inst),3255 .trunc => try airTrunc(f, inst),
3255 .int_from_bool => try airIntFromBool(f, inst),3256 .int_from_bool => try airIntFromBool(f, inst),
3256 .load => try airLoad(f, inst),3257 .load => try airLoad(f, inst),
3257 .ret => try airRet(f, inst, false),
3258 .ret_safe => try airRet(f, inst, false), // TODO
3259 .ret_load => try airRet(f, inst, true),
3260 .store => try airStore(f, inst, false),3258 .store => try airStore(f, inst, false),
3261 .store_safe => try airStore(f, inst, true),3259 .store_safe => try airStore(f, inst, true),
3262 .loop => try airLoop(f, inst),
3263 .cond_br => try airCondBr(f, inst),
3264 .br => try airBr(f, inst),
3265 .switch_br => try airSwitchBr(f, inst),
3266 .struct_field_ptr => try airStructFieldPtr(f, inst),3260 .struct_field_ptr => try airStructFieldPtr(f, inst),
3267 .array_to_slice => try airArrayToSlice(f, inst),3261 .array_to_slice => try airArrayToSlice(f, inst),
3268 .cmpxchg_weak => try airCmpxchg(f, inst, "weak"),3262 .cmpxchg_weak => try airCmpxchg(f, inst, "weak"),
...@@ -3296,14 +3290,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,...@@ -3296,14 +3290,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
3296 .try_ptr_cold => try airTryPtr(f, inst),3290 .try_ptr_cold => try airTryPtr(f, inst),
32973291
3298 .dbg_stmt => try airDbgStmt(f, inst),3292 .dbg_stmt => try airDbgStmt(f, inst),
3299 .dbg_inline_block => try airDbgInlineBlock(f, inst),
3300 .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => try airDbgVar(f, inst),3293 .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => try airDbgVar(f, inst),
33013294
3302 .call => try airCall(f, inst, .auto),
3303 .call_always_tail => .none,
3304 .call_never_tail => try airCall(f, inst, .never_tail),
3305 .call_never_inline => try airCall(f, inst, .never_inline),
3306
3307 .float_from_int,3295 .float_from_int,
3308 .int_from_float,3296 .int_from_float,
3309 .fptrunc,3297 .fptrunc,
...@@ -3390,6 +3378,41 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,...@@ -3390,6 +3378,41 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
3390 .work_group_size,3378 .work_group_size,
3391 .work_group_id,3379 .work_group_id,
3392 => unreachable,3380 => unreachable,
3381
3382 // Instructions that are known to always be `noreturn` based on their tag.
3383 .br => return airBr(f, inst),
3384 .repeat => return airRepeat(f, inst),
3385 .switch_dispatch => return airSwitchDispatch(f, inst),
3386 .cond_br => return airCondBr(f, inst),
3387 .switch_br => return airSwitchBr(f, inst, false),
3388 .loop_switch_br => return airSwitchBr(f, inst, true),
3389 .loop => return airLoop(f, inst),
3390 .ret => return airRet(f, inst, false),
3391 .ret_safe => return airRet(f, inst, false), // TODO
3392 .ret_load => return airRet(f, inst, true),
3393 .trap => return airTrap(f, f.object.writer()),
3394 .unreach => return airUnreach(f),
3395
3396 // Instructions which may be `noreturn`.
3397 .block => res: {
3398 const res = try airBlock(f, inst);
3399 if (f.typeOfIndex(inst).isNoReturn(zcu)) return;
3400 break :res res;
3401 },
3402 .dbg_inline_block => res: {
3403 const res = try airDbgInlineBlock(f, inst);
3404 if (f.typeOfIndex(inst).isNoReturn(zcu)) return;
3405 break :res res;
3406 },
3407 // TODO: calls should be in this category! The AIR we emit for them is a bit weird.
3408 // The instruction has type `noreturn`, but there are instructions (and maybe a safety
3409 // check) following nonetheless. The `unreachable` or safety check should be emitted by
3410 // backends instead.
3411 .call => try airCall(f, inst, .auto),
3412 .call_always_tail => .none,
3413 .call_never_tail => try airCall(f, inst, .never_tail),
3414 .call_never_inline => try airCall(f, inst, .never_inline),
3415
3393 // zig fmt: on3416 // zig fmt: on
3394 };3417 };
3395 if (result_value == .new_local) {3418 if (result_value == .new_local) {
...@@ -3401,6 +3424,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,...@@ -3401,6 +3424,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
3401 else => result_value,3424 else => result_value,
3402 });3425 });
3403 }3426 }
3427 unreachable;
3404}3428}
34053429
3406fn airSliceField(f: *Function, inst: Air.Inst.Index, is_ptr: bool, field_name: []const u8) !CValue {3430fn airSliceField(f: *Function, inst: Air.Inst.Index, is_ptr: bool, field_name: []const u8) !CValue {
...@@ -3718,7 +3742,7 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -3718,7 +3742,7 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue {
3718 return local;3742 return local;
3719}3743}
37203744
3721fn airRet(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValue {3745fn airRet(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !void {
3722 const pt = f.object.dg.pt;3746 const pt = f.object.dg.pt;
3723 const zcu = pt.zcu;3747 const zcu = pt.zcu;
3724 const un_op = f.air.instructions.items(.data)[@intFromEnum(inst)].un_op;3748 const un_op = f.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
...@@ -3769,7 +3793,6 @@ fn airRet(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValue {...@@ -3769,7 +3793,6 @@ fn airRet(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValue {
3769 // Not even allowed to return void in a naked function.3793 // Not even allowed to return void in a naked function.
3770 if (!f.object.dg.is_naked_fn) try writer.writeAll("return;\n");3794 if (!f.object.dg.is_naked_fn) try writer.writeAll("return;\n");
3771 }3795 }
3772 return .none;
3773}3796}
37743797
3775fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue {3798fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue {
...@@ -4741,7 +4764,7 @@ fn lowerTry(...@@ -4741,7 +4764,7 @@ fn lowerTry(
4741 return local;4764 return local;
4742}4765}
47434766
4744fn airBr(f: *Function, inst: Air.Inst.Index) !CValue {4767fn airBr(f: *Function, inst: Air.Inst.Index) !void {
4745 const branch = f.air.instructions.items(.data)[@intFromEnum(inst)].br;4768 const branch = f.air.instructions.items(.data)[@intFromEnum(inst)].br;
4746 const block = f.blocks.get(branch.block_inst).?;4769 const block = f.blocks.get(branch.block_inst).?;
4747 const result = block.result;4770 const result = block.result;
...@@ -4761,7 +4784,52 @@ fn airBr(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -4761,7 +4784,52 @@ fn airBr(f: *Function, inst: Air.Inst.Index) !CValue {
4761 }4784 }
47624785
4763 try writer.print("goto zig_block_{d};\n", .{block.block_id});4786 try writer.print("goto zig_block_{d};\n", .{block.block_id});
4764 return .none;4787}
4788
4789fn airRepeat(f: *Function, inst: Air.Inst.Index) !void {
4790 const repeat = f.air.instructions.items(.data)[@intFromEnum(inst)].repeat;
4791 const writer = f.object.writer();
4792 try writer.print("goto zig_loop_{d};\n", .{@intFromEnum(repeat.loop_inst)});
4793}
4794
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)});
4765}4833}
47664834
4767fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue {4835fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue {
...@@ -4889,12 +4957,10 @@ fn bitcast(f: *Function, dest_ty: Type, operand: CValue, operand_ty: Type) !CVal...@@ -4889,12 +4957,10 @@ fn bitcast(f: *Function, dest_ty: Type, operand: CValue, operand_ty: Type) !CVal
4889 return local;4957 return local;
4890}4958}
48914959
4892fn airTrap(f: *Function, writer: anytype) !CValue {4960fn airTrap(f: *Function, writer: anytype) !void {
4893 // Not even allowed to call trap in a naked function.4961 // Not even allowed to call trap in a naked function.
4894 if (f.object.dg.is_naked_fn) return .none;4962 if (f.object.dg.is_naked_fn) return;
4895
4896 try writer.writeAll("zig_trap();\n");4963 try writer.writeAll("zig_trap();\n");
4897 return .none;
4898}4964}
48994965
4900fn airBreakpoint(writer: anytype) !CValue {4966fn airBreakpoint(writer: anytype) !CValue {
...@@ -4933,28 +4999,27 @@ fn airFence(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -4933,28 +4999,27 @@ fn airFence(f: *Function, inst: Air.Inst.Index) !CValue {
4933 return .none;4999 return .none;
4934}5000}
49355001
4936fn airUnreach(f: *Function) !CValue {5002fn airUnreach(f: *Function) !void {
4937 // Not even allowed to call unreachable in a naked function.5003 // Not even allowed to call unreachable in a naked function.
4938 if (f.object.dg.is_naked_fn) return .none;5004 if (f.object.dg.is_naked_fn) return;
4939
4940 try f.object.writer().writeAll("zig_unreachable();\n");5005 try f.object.writer().writeAll("zig_unreachable();\n");
4941 return .none;
4942}5006}
49435007
4944fn airLoop(f: *Function, inst: Air.Inst.Index) !CValue {5008fn airLoop(f: *Function, inst: Air.Inst.Index) !void {
4945 const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;5009 const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4946 const loop = f.air.extraData(Air.Block, ty_pl.payload);5010 const loop = f.air.extraData(Air.Block, ty_pl.payload);
4947 const body: []const Air.Inst.Index = @ptrCast(f.air.extra[loop.end..][0..loop.data.body_len]);5011 const body: []const Air.Inst.Index = @ptrCast(f.air.extra[loop.end..][0..loop.data.body_len]);
4948 const writer = f.object.writer();5012 const writer = f.object.writer();
49495013
4950 try writer.writeAll("for (;;) ");5014 // `repeat` instructions matching this loop will branch to
4951 try genBody(f, body); // no need to restore state, we're noreturn5015 // this label. Since we need a label for arbitrary `repeat`
4952 try writer.writeByte('\n');5016 // anyway, there's actually no need to use a "real" looping
49535017 // construct at all!
4954 return .none;5018 try writer.print("zig_loop_{d}:\n", .{@intFromEnum(inst)});
5019 try genBodyInner(f, body); // no need to restore state, we're noreturn
4955}5020}
49565021
4957fn airCondBr(f: *Function, inst: Air.Inst.Index) !CValue {5022fn airCondBr(f: *Function, inst: Air.Inst.Index) !void {
4958 const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;5023 const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
4959 const cond = try f.resolveInst(pl_op.operand);5024 const cond = try f.resolveInst(pl_op.operand);
4960 try reap(f, inst, &.{pl_op.operand});5025 try reap(f, inst, &.{pl_op.operand});
...@@ -4983,19 +5048,33 @@ fn airCondBr(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -4983,19 +5048,33 @@ fn airCondBr(f: *Function, inst: Air.Inst.Index) !CValue {
4983 // instance) `br` to a block (label).5048 // instance) `br` to a block (label).
49845049
4985 try genBodyInner(f, else_body);5050 try genBodyInner(f, else_body);
4986
4987 return .none;
4988}5051}
49895052
4990fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue {5053fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void {
4991 const pt = f.object.dg.pt;5054 const pt = f.object.dg.pt;
4992 const zcu = pt.zcu;5055 const zcu = pt.zcu;
5056 const gpa = f.object.dg.gpa;
4993 const switch_br = f.air.unwrapSwitch(inst);5057 const switch_br = f.air.unwrapSwitch(inst);
4994 const condition = try f.resolveInst(switch_br.operand);5058 const init_condition = try f.resolveInst(switch_br.operand);
4995 try reap(f, inst, &.{switch_br.operand});5059 try reap(f, inst, &.{switch_br.operand});
4996 const condition_ty = f.typeOf(switch_br.operand);5060 const condition_ty = f.typeOf(switch_br.operand);
4997 const writer = f.object.writer();5061 const writer = f.object.writer();
49985062
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.copyCValue(try f.ctypeFromType(condition_ty, .complete), new_local, init_condition);
5069 try writer.print("zig_switch_{d}_loop:\n", .{@intFromEnum(inst)});
5070 try f.loop_switch_conds.put(gpa, inst, new_local.new_local);
5071 break :cond new_local;
5072 } else init_condition;
5073
5074 defer if (is_dispatch_loop) {
5075 assert(f.loop_switch_conds.remove(inst));
5076 };
5077
4999 try writer.writeAll("switch (");5078 try writer.writeAll("switch (");
50005079
5001 const lowered_condition_ty = if (condition_ty.toIntern() == .bool_type)5080 const lowered_condition_ty = if (condition_ty.toIntern() == .bool_type)
...@@ -5013,23 +5092,29 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -5013,23 +5092,29 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue {
5013 try writer.writeAll(") {");5092 try writer.writeAll(") {");
5014 f.object.indent_writer.pushIndent();5093 f.object.indent_writer.pushIndent();
50155094
5016 const gpa = f.object.dg.gpa;
5017 const liveness = try f.liveness.getSwitchBr(gpa, inst, switch_br.cases_len + 1);5095 const liveness = try f.liveness.getSwitchBr(gpa, inst, switch_br.cases_len + 1);
5018 defer gpa.free(liveness.deaths);5096 defer gpa.free(liveness.deaths);
50195097
5020 // On the final iteration we do not need to fix any state. This is because, like in the `else`5098 var any_range_cases = false;
5021 // branch of a `cond_br`, our parent has to do it for this entire body anyway.
5022 const last_case_i = switch_br.cases_len - @intFromBool(switch_br.else_body_len == 0);
5023
5024 var it = switch_br.iterateCases();5099 var it = switch_br.iterateCases();
5025 while (it.next()) |case| {5100 while (it.next()) |case| {
5101 if (case.ranges.len > 0) {
5102 any_range_cases = true;
5103 continue;
5104 }
5026 for (case.items) |item| {5105 for (case.items) |item| {
5027 try f.object.indent_writer.insertNewline();5106 try f.object.indent_writer.insertNewline();
5028 try writer.writeAll("case ");5107 try writer.writeAll("case ");
5029 const item_value = try f.air.value(item, pt);5108 const item_value = try f.air.value(item, pt);
5030 if (item_value.?.getUnsignedInt(zcu)) |item_int| try writer.print("{}\n", .{5109 // If `item_value` is a pointer with a known integer address, print the address
5031 try f.fmtIntLiteral(try pt.intValue(lowered_condition_ty, item_int)),5110 // with no cast to avoid a warning.
5032 }) else {5111 write_val: {
5112 if (condition_ty.isPtrAtRuntime(zcu)) {
5113 if (item_value.?.getUnsignedInt(zcu)) |item_int| {
5114 try writer.print("{}", .{try f.fmtIntLiteral(try pt.intValue(lowered_condition_ty, item_int))});
5115 break :write_val;
5116 }
5117 }
5033 if (condition_ty.isPtrAtRuntime(zcu)) {5118 if (condition_ty.isPtrAtRuntime(zcu)) {
5034 try writer.writeByte('(');5119 try writer.writeByte('(');
5035 try f.renderType(writer, Type.usize);5120 try f.renderType(writer, Type.usize);
...@@ -5039,37 +5124,76 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue {...@@ -5039,37 +5124,76 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue {
5039 }5124 }
5040 try writer.writeByte(':');5125 try writer.writeByte(':');
5041 }5126 }
5042 try writer.writeByte(' ');5127 try writer.writeAll(" {\n");
50435128 f.object.indent_writer.pushIndent();
5044 if (case.idx != last_case_i) {5129 if (is_dispatch_loop) {
5045 try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, false);5130 try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx });
5046 } else {
5047 for (liveness.deaths[case.idx]) |death| {
5048 try die(f, inst, death.toRef());
5049 }
5050 try genBody(f, case.body);
5051 }5131 }
5132 try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true);
5133 f.object.indent_writer.popIndent();
5134 try writer.writeByte('}');
50525135
5053 // The case body must be noreturn so we don't need to insert a break.5136 // The case body must be noreturn so we don't need to insert a break.
5054 }5137 }
50555138
5056 const else_body = it.elseBody();5139 const else_body = it.elseBody();
5057 try f.object.indent_writer.insertNewline();5140 try f.object.indent_writer.insertNewline();
5141
5142 try writer.writeAll("default: ");
5143 if (any_range_cases) {
5144 // We will iterate the cases again to handle those with ranges, and generate
5145 // code using conditions rather than switch cases for such cases.
5146 it = switch_br.iterateCases();
5147 while (it.next()) |case| {
5148 if (case.ranges.len == 0) continue; // handled above
5149
5150 try writer.writeAll("if (");
5151 for (case.items, 0..) |item, item_i| {
5152 if (item_i != 0) try writer.writeAll(" || ");
5153 try f.writeCValue(writer, condition, .Other);
5154 try writer.writeAll(" == ");
5155 try f.object.dg.renderValue(writer, (try f.air.value(item, pt)).?, .Other);
5156 }
5157 for (case.ranges, 0..) |range, range_i| {
5158 if (case.items.len != 0 or range_i != 0) try writer.writeAll(" || ");
5159 // "(x >= lower && x <= upper)"
5160 try writer.writeByte('(');
5161 try f.writeCValue(writer, condition, .Other);
5162 try writer.writeAll(" >= ");
5163 try f.object.dg.renderValue(writer, (try f.air.value(range[0], pt)).?, .Other);
5164 try writer.writeAll(" && ");
5165 try f.writeCValue(writer, condition, .Other);
5166 try writer.writeAll(" <= ");
5167 try f.object.dg.renderValue(writer, (try f.air.value(range[1], pt)).?, .Other);
5168 try writer.writeByte(')');
5169 }
5170 try writer.writeAll(") {\n");
5171 f.object.indent_writer.pushIndent();
5172 if (is_dispatch_loop) {
5173 try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx });
5174 }
5175 try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true);
5176 f.object.indent_writer.popIndent();
5177 try writer.writeByte('}');
5178 }
5179 }
5180 if (is_dispatch_loop) {
5181 try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), switch_br.cases_len });
5182 }
5058 if (else_body.len > 0) {5183 if (else_body.len > 0) {
5059 // Note that this must be the last case (i.e. the `last_case_i` case was not hit above)5184 // Note that this must be the last case, so we do not need to use `genBodyResolveState` since
5185 // the parent block will do it (because the case body is noreturn).
5060 for (liveness.deaths[liveness.deaths.len - 1]) |death| {5186 for (liveness.deaths[liveness.deaths.len - 1]) |death| {
5061 try die(f, inst, death.toRef());5187 try die(f, inst, death.toRef());
5062 }5188 }
5063 try writer.writeAll("default: ");
5064 try genBody(f, else_body);5189 try genBody(f, else_body);
5065 } else {5190 } else {
5066 try writer.writeAll("default: zig_unreachable();");5191 try writer.writeAll("zig_unreachable();");
5067 }5192 }
5068 try f.object.indent_writer.insertNewline();5193 try f.object.indent_writer.insertNewline();
50695194
5070 f.object.indent_writer.popIndent();5195 f.object.indent_writer.popIndent();
5071 try writer.writeAll("}\n");5196 try writer.writeAll("}\n");
5072 return .none;
5073}5197}
50745198
5075fn asmInputNeedsLocal(f: *Function, constraint: []const u8, value: CValue) bool {5199fn asmInputNeedsLocal(f: *Function, constraint: []const u8, value: CValue) bool {
src/codegen/llvm.zig+471-82
...@@ -1739,6 +1739,8 @@ pub const Object = struct {...@@ -1739,6 +1739,8 @@ pub const Object = struct {
1739 .arg_inline_index = 0,1739 .arg_inline_index = 0,
1740 .func_inst_table = .{},1740 .func_inst_table = .{},
1741 .blocks = .{},1741 .blocks = .{},
1742 .loops = .{},
1743 .switch_dispatch_info = .{},
1742 .sync_scope = if (owner_mod.single_threaded) .singlethread else .system,1744 .sync_scope = if (owner_mod.single_threaded) .singlethread else .system,
1743 .file = file,1745 .file = file,
1744 .scope = subprogram,1746 .scope = subprogram,
...@@ -4860,6 +4862,13 @@ pub const FuncGen = struct {...@@ -4860,6 +4862,13 @@ pub const FuncGen = struct {
4860 breaks: *BreakList,4862 breaks: *BreakList,
4861 }),4863 }),
48624864
4865 /// Maps `loop` instructions to the bb to branch to to repeat the loop.
4866 loops: std.AutoHashMapUnmanaged(Air.Inst.Index, Builder.Function.Block.Index),
4867
4868 /// Maps `loop_switch_br` instructions to the information required to lower
4869 /// dispatches (`switch_dispatch` instructions).
4870 switch_dispatch_info: std.AutoHashMapUnmanaged(Air.Inst.Index, SwitchDispatchInfo),
4871
4863 sync_scope: Builder.SyncScope,4872 sync_scope: Builder.SyncScope,
48644873
4865 const Fuzz = struct {4874 const Fuzz = struct {
...@@ -4872,6 +4881,33 @@ pub const FuncGen = struct {...@@ -4872,6 +4881,33 @@ pub const FuncGen = struct {
4872 }4881 }
4873 };4882 };
48744883
4884 const SwitchDispatchInfo = struct {
4885 /// These are the blocks corresponding to each switch case.
4886 /// The final element corresponds to the `else` case.
4887 /// Slices allocated into `gpa`.
4888 case_blocks: []Builder.Function.Block.Index,
4889 /// This is `.none` if `jmp_table` is set, since we won't use a `switch` instruction to dispatch.
4890 switch_weights: Builder.Function.Instruction.BrCond.Weights,
4891 /// If not `null`, we have manually constructed a jump table to reach the desired block.
4892 /// `table` can be used if the value is between `min` and `max` inclusive.
4893 /// We perform this lowering manually to avoid some questionable behavior from LLVM.
4894 /// See `airSwitchBr` for details.
4895 jmp_table: ?JmpTable,
4896
4897 const JmpTable = struct {
4898 min: Builder.Constant,
4899 max: Builder.Constant,
4900 in_bounds_hint: enum { none, unpredictable, likely, unlikely },
4901 /// Pointer to the jump table itself, to be used with `indirectbr`.
4902 /// The index into the jump table is the dispatch condition minus `min`.
4903 /// The table values are `blockaddress` constants corresponding to blocks in `case_blocks`.
4904 table: Builder.Constant,
4905 /// `true` if `table` conatins a reference to the `else` block.
4906 /// In this case, the `indirectbr` must include the `else` block in its target list.
4907 table_includes_else: bool,
4908 };
4909 };
4910
4875 const BreakList = union {4911 const BreakList = union {
4876 list: std.MultiArrayList(struct {4912 list: std.MultiArrayList(struct {
4877 bb: Builder.Function.Block.Index,4913 bb: Builder.Function.Block.Index,
...@@ -4886,6 +4922,12 @@ pub const FuncGen = struct {...@@ -4886,6 +4922,12 @@ pub const FuncGen = struct {
4886 self.wip.deinit();4922 self.wip.deinit();
4887 self.func_inst_table.deinit(gpa);4923 self.func_inst_table.deinit(gpa);
4888 self.blocks.deinit(gpa);4924 self.blocks.deinit(gpa);
4925 self.loops.deinit(gpa);
4926 var it = self.switch_dispatch_info.valueIterator();
4927 while (it.next()) |info| {
4928 self.gpa.free(info.case_blocks);
4929 }
4930 self.switch_dispatch_info.deinit(gpa);
4889 }4931 }
48904932
4891 fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error {4933 fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error {
...@@ -5077,14 +5119,9 @@ pub const FuncGen = struct {...@@ -5077,14 +5119,9 @@ pub const FuncGen = struct {
5077 .arg => try self.airArg(inst),5119 .arg => try self.airArg(inst),
5078 .bitcast => try self.airBitCast(inst),5120 .bitcast => try self.airBitCast(inst),
5079 .int_from_bool => try self.airIntFromBool(inst),5121 .int_from_bool => try self.airIntFromBool(inst),
5080 .block => try self.airBlock(inst),
5081 .br => try self.airBr(inst),
5082 .switch_br => try self.airSwitchBr(inst),
5083 .trap => try self.airTrap(inst),
5084 .breakpoint => try self.airBreakpoint(inst),5122 .breakpoint => try self.airBreakpoint(inst),
5085 .ret_addr => try self.airRetAddr(inst),5123 .ret_addr => try self.airRetAddr(inst),
5086 .frame_addr => try self.airFrameAddress(inst),5124 .frame_addr => try self.airFrameAddress(inst),
5087 .cond_br => try self.airCondBr(inst),
5088 .@"try" => try self.airTry(body[i..], false),5125 .@"try" => try self.airTry(body[i..], false),
5089 .try_cold => try self.airTry(body[i..], true),5126 .try_cold => try self.airTry(body[i..], true),
5090 .try_ptr => try self.airTryPtr(inst, false),5127 .try_ptr => try self.airTryPtr(inst, false),
...@@ -5095,22 +5132,13 @@ pub const FuncGen = struct {...@@ -5095,22 +5132,13 @@ pub const FuncGen = struct {
5095 .fpext => try self.airFpext(inst),5132 .fpext => try self.airFpext(inst),
5096 .int_from_ptr => try self.airIntFromPtr(inst),5133 .int_from_ptr => try self.airIntFromPtr(inst),
5097 .load => try self.airLoad(body[i..]),5134 .load => try self.airLoad(body[i..]),
5098 .loop => try self.airLoop(inst),
5099 .not => try self.airNot(inst),5135 .not => try self.airNot(inst),
5100 .ret => try self.airRet(inst, false),
5101 .ret_safe => try self.airRet(inst, true),
5102 .ret_load => try self.airRetLoad(inst),
5103 .store => try self.airStore(inst, false),5136 .store => try self.airStore(inst, false),
5104 .store_safe => try self.airStore(inst, true),5137 .store_safe => try self.airStore(inst, true),
5105 .assembly => try self.airAssembly(inst),5138 .assembly => try self.airAssembly(inst),
5106 .slice_ptr => try self.airSliceField(inst, 0),5139 .slice_ptr => try self.airSliceField(inst, 0),
5107 .slice_len => try self.airSliceField(inst, 1),5140 .slice_len => try self.airSliceField(inst, 1),
51085141
5109 .call => try self.airCall(inst, .auto),
5110 .call_always_tail => try self.airCall(inst, .always_tail),
5111 .call_never_tail => try self.airCall(inst, .never_tail),
5112 .call_never_inline => try self.airCall(inst, .never_inline),
5113
5114 .ptr_slice_ptr_ptr => try self.airPtrSliceFieldPtr(inst, 0),5142 .ptr_slice_ptr_ptr => try self.airPtrSliceFieldPtr(inst, 0),
5115 .ptr_slice_len_ptr => try self.airPtrSliceFieldPtr(inst, 1),5143 .ptr_slice_len_ptr => try self.airPtrSliceFieldPtr(inst, 1),
51165144
...@@ -5195,9 +5223,7 @@ pub const FuncGen = struct {...@@ -5195,9 +5223,7 @@ pub const FuncGen = struct {
51955223
5196 .inferred_alloc, .inferred_alloc_comptime => unreachable,5224 .inferred_alloc, .inferred_alloc_comptime => unreachable,
51975225
5198 .unreach => try self.airUnreach(inst),
5199 .dbg_stmt => try self.airDbgStmt(inst),5226 .dbg_stmt => try self.airDbgStmt(inst),
5200 .dbg_inline_block => try self.airDbgInlineBlock(inst),
5201 .dbg_var_ptr => try self.airDbgVarPtr(inst),5227 .dbg_var_ptr => try self.airDbgVarPtr(inst),
5202 .dbg_var_val => try self.airDbgVarVal(inst, false),5228 .dbg_var_val => try self.airDbgVarVal(inst, false),
5203 .dbg_arg_inline => try self.airDbgVarVal(inst, true),5229 .dbg_arg_inline => try self.airDbgVarVal(inst, true),
...@@ -5210,10 +5236,52 @@ pub const FuncGen = struct {...@@ -5210,10 +5236,52 @@ pub const FuncGen = struct {
5210 .work_item_id => try self.airWorkItemId(inst),5236 .work_item_id => try self.airWorkItemId(inst),
5211 .work_group_size => try self.airWorkGroupSize(inst),5237 .work_group_size => try self.airWorkGroupSize(inst),
5212 .work_group_id => try self.airWorkGroupId(inst),5238 .work_group_id => try self.airWorkGroupId(inst),
5239
5240 // Instructions that are known to always be `noreturn` based on their tag.
5241 .br => return self.airBr(inst),
5242 .repeat => return self.airRepeat(inst),
5243 .switch_dispatch => return self.airSwitchDispatch(inst),
5244 .cond_br => return self.airCondBr(inst),
5245 .switch_br => return self.airSwitchBr(inst, false),
5246 .loop_switch_br => return self.airSwitchBr(inst, true),
5247 .loop => return self.airLoop(inst),
5248 .ret => return self.airRet(inst, false),
5249 .ret_safe => return self.airRet(inst, true),
5250 .ret_load => return self.airRetLoad(inst),
5251 .trap => return self.airTrap(inst),
5252 .unreach => return self.airUnreach(inst),
5253
5254 // Instructions which may be `noreturn`.
5255 .block => res: {
5256 const res = try self.airBlock(inst);
5257 if (self.typeOfIndex(inst).isNoReturn(zcu)) return;
5258 break :res res;
5259 },
5260 .dbg_inline_block => res: {
5261 const res = try self.airDbgInlineBlock(inst);
5262 if (self.typeOfIndex(inst).isNoReturn(zcu)) return;
5263 break :res res;
5264 },
5265 .call, .call_always_tail, .call_never_tail, .call_never_inline => |tag| res: {
5266 const res = try self.airCall(inst, switch (tag) {
5267 .call => .auto,
5268 .call_always_tail => .always_tail,
5269 .call_never_tail => .never_tail,
5270 .call_never_inline => .never_inline,
5271 else => unreachable,
5272 });
5273 // TODO: the AIR we emit for calls is a bit weird - the instruction has
5274 // type `noreturn`, but there are instructions (and maybe a safety check) following
5275 // nonetheless. The `unreachable` or safety check should be emitted by backends instead.
5276 //if (self.typeOfIndex(inst).isNoReturn(mod)) return;
5277 break :res res;
5278 },
5279
5213 // zig fmt: on5280 // zig fmt: on
5214 };5281 };
5215 if (val != .none) try self.func_inst_table.putNoClobber(self.gpa, inst.toRef(), val);5282 if (val != .none) try self.func_inst_table.putNoClobber(self.gpa, inst.toRef(), val);
5216 }5283 }
5284 unreachable;
5217 }5285 }
52185286
5219 fn genBodyDebugScope(5287 fn genBodyDebugScope(
...@@ -5659,7 +5727,7 @@ pub const FuncGen = struct {...@@ -5659,7 +5727,7 @@ pub const FuncGen = struct {
5659 _ = try fg.wip.@"unreachable"();5727 _ = try fg.wip.@"unreachable"();
5660 }5728 }
56615729
5662 fn airRet(self: *FuncGen, inst: Air.Inst.Index, safety: bool) !Builder.Value {5730 fn airRet(self: *FuncGen, inst: Air.Inst.Index, safety: bool) !void {
5663 const o = self.ng.object;5731 const o = self.ng.object;
5664 const pt = o.pt;5732 const pt = o.pt;
5665 const zcu = pt.zcu;5733 const zcu = pt.zcu;
...@@ -5694,7 +5762,7 @@ pub const FuncGen = struct {...@@ -5694,7 +5762,7 @@ pub const FuncGen = struct {
5694 try self.valgrindMarkUndef(self.ret_ptr, len);5762 try self.valgrindMarkUndef(self.ret_ptr, len);
5695 }5763 }
5696 _ = try self.wip.retVoid();5764 _ = try self.wip.retVoid();
5697 return .none;5765 return;
5698 }5766 }
56995767
5700 const unwrapped_operand = operand.unwrap();5768 const unwrapped_operand = operand.unwrap();
...@@ -5703,12 +5771,12 @@ pub const FuncGen = struct {...@@ -5703,12 +5771,12 @@ pub const FuncGen = struct {
5703 // Return value was stored previously5771 // Return value was stored previously
5704 if (unwrapped_operand == .instruction and unwrapped_ret == .instruction and unwrapped_operand.instruction == unwrapped_ret.instruction) {5772 if (unwrapped_operand == .instruction and unwrapped_ret == .instruction and unwrapped_operand.instruction == unwrapped_ret.instruction) {
5705 _ = try self.wip.retVoid();5773 _ = try self.wip.retVoid();
5706 return .none;5774 return;
5707 }5775 }
57085776
5709 try self.store(self.ret_ptr, ptr_ty, operand, .none);5777 try self.store(self.ret_ptr, ptr_ty, operand, .none);
5710 _ = try self.wip.retVoid();5778 _ = try self.wip.retVoid();
5711 return .none;5779 return;
5712 }5780 }
5713 const fn_info = zcu.typeToFunc(Type.fromInterned(ip.getNav(self.ng.nav_index).typeOf(ip))).?;5781 const fn_info = zcu.typeToFunc(Type.fromInterned(ip.getNav(self.ng.nav_index).typeOf(ip))).?;
5714 if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {5782 if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
...@@ -5720,7 +5788,7 @@ pub const FuncGen = struct {...@@ -5720,7 +5788,7 @@ pub const FuncGen = struct {
5720 } else {5788 } else {
5721 _ = try self.wip.retVoid();5789 _ = try self.wip.retVoid();
5722 }5790 }
5723 return .none;5791 return;
5724 }5792 }
57255793
5726 const abi_ret_ty = try lowerFnRetTy(o, fn_info);5794 const abi_ret_ty = try lowerFnRetTy(o, fn_info);
...@@ -5744,29 +5812,29 @@ pub const FuncGen = struct {...@@ -5744,29 +5812,29 @@ pub const FuncGen = struct {
5744 try self.valgrindMarkUndef(rp, len);5812 try self.valgrindMarkUndef(rp, len);
5745 }5813 }
5746 _ = try self.wip.ret(try self.wip.load(.normal, abi_ret_ty, rp, alignment, ""));5814 _ = try self.wip.ret(try self.wip.load(.normal, abi_ret_ty, rp, alignment, ""));
5747 return .none;5815 return;
5748 }5816 }
57495817
5750 if (isByRef(ret_ty, zcu)) {5818 if (isByRef(ret_ty, zcu)) {
5751 // operand is a pointer however self.ret_ptr is null so that means5819 // operand is a pointer however self.ret_ptr is null so that means
5752 // we need to return a value.5820 // we need to return a value.
5753 _ = try self.wip.ret(try self.wip.load(.normal, abi_ret_ty, operand, alignment, ""));5821 _ = try self.wip.ret(try self.wip.load(.normal, abi_ret_ty, operand, alignment, ""));
5754 return .none;5822 return;
5755 }5823 }
57565824
5757 const llvm_ret_ty = operand.typeOfWip(&self.wip);5825 const llvm_ret_ty = operand.typeOfWip(&self.wip);
5758 if (abi_ret_ty == llvm_ret_ty) {5826 if (abi_ret_ty == llvm_ret_ty) {
5759 _ = try self.wip.ret(operand);5827 _ = try self.wip.ret(operand);
5760 return .none;5828 return;
5761 }5829 }
57625830
5763 const rp = try self.buildAlloca(llvm_ret_ty, alignment);5831 const rp = try self.buildAlloca(llvm_ret_ty, alignment);
5764 _ = try self.wip.store(.normal, operand, rp, alignment);5832 _ = try self.wip.store(.normal, operand, rp, alignment);
5765 _ = try self.wip.ret(try self.wip.load(.normal, abi_ret_ty, rp, alignment, ""));5833 _ = try self.wip.ret(try self.wip.load(.normal, abi_ret_ty, rp, alignment, ""));
5766 return .none;5834 return;
5767 }5835 }
57685836
5769 fn airRetLoad(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {5837 fn airRetLoad(self: *FuncGen, inst: Air.Inst.Index) !void {
5770 const o = self.ng.object;5838 const o = self.ng.object;
5771 const pt = o.pt;5839 const pt = o.pt;
5772 const zcu = pt.zcu;5840 const zcu = pt.zcu;
...@@ -5784,17 +5852,17 @@ pub const FuncGen = struct {...@@ -5784,17 +5852,17 @@ pub const FuncGen = struct {
5784 } else {5852 } else {
5785 _ = try self.wip.retVoid();5853 _ = try self.wip.retVoid();
5786 }5854 }
5787 return .none;5855 return;
5788 }5856 }
5789 if (self.ret_ptr != .none) {5857 if (self.ret_ptr != .none) {
5790 _ = try self.wip.retVoid();5858 _ = try self.wip.retVoid();
5791 return .none;5859 return;
5792 }5860 }
5793 const ptr = try self.resolveInst(un_op);5861 const ptr = try self.resolveInst(un_op);
5794 const abi_ret_ty = try lowerFnRetTy(o, fn_info);5862 const abi_ret_ty = try lowerFnRetTy(o, fn_info);
5795 const alignment = ret_ty.abiAlignment(zcu).toLlvm();5863 const alignment = ret_ty.abiAlignment(zcu).toLlvm();
5796 _ = try self.wip.ret(try self.wip.load(.normal, abi_ret_ty, ptr, alignment, ""));5864 _ = try self.wip.ret(try self.wip.load(.normal, abi_ret_ty, ptr, alignment, ""));
5797 return .none;5865 return;
5798 }5866 }
57995867
5800 fn airCVaArg(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {5868 fn airCVaArg(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
...@@ -6058,7 +6126,7 @@ pub const FuncGen = struct {...@@ -6058,7 +6126,7 @@ pub const FuncGen = struct {
6058 }6126 }
6059 }6127 }
60606128
6061 fn airBr(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {6129 fn airBr(self: *FuncGen, inst: Air.Inst.Index) !void {
6062 const o = self.ng.object;6130 const o = self.ng.object;
6063 const zcu = o.pt.zcu;6131 const zcu = o.pt.zcu;
6064 const branch = self.air.instructions.items(.data)[@intFromEnum(inst)].br;6132 const branch = self.air.instructions.items(.data)[@intFromEnum(inst)].br;
...@@ -6074,10 +6142,212 @@ pub const FuncGen = struct {...@@ -6074,10 +6142,212 @@ pub const FuncGen = struct {
6074 try block.breaks.list.append(self.gpa, .{ .bb = self.wip.cursor.block, .val = val });6142 try block.breaks.list.append(self.gpa, .{ .bb = self.wip.cursor.block, .val = val });
6075 } else block.breaks.len += 1;6143 } else block.breaks.len += 1;
6076 _ = try self.wip.br(block.parent_bb);6144 _ = try self.wip.br(block.parent_bb);
6077 return .none;
6078 }6145 }
60796146
6080 fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {6147 fn airRepeat(self: *FuncGen, inst: Air.Inst.Index) !void {
6148 const repeat = self.air.instructions.items(.data)[@intFromEnum(inst)].repeat;
6149 const loop_bb = self.loops.get(repeat.loop_inst).?;
6150 loop_bb.ptr(&self.wip).incoming += 1;
6151 _ = try self.wip.br(loop_bb);
6152 }
6153
6154 fn lowerSwitchDispatch(
6155 self: *FuncGen,
6156 switch_inst: Air.Inst.Index,
6157 cond_ref: Air.Inst.Ref,
6158 dispatch_info: SwitchDispatchInfo,
6159 ) !void {
6160 const o = self.ng.object;
6161 const pt = o.pt;
6162 const zcu = pt.zcu;
6163 const cond_ty = self.typeOf(cond_ref);
6164 const switch_br = self.air.unwrapSwitch(switch_inst);
6165
6166 if (try self.air.value(cond_ref, pt)) |cond_val| {
6167 // Comptime-known dispatch. Iterate the cases to find the correct
6168 // one, and branch to the corresponding element of `case_blocks`.
6169 var it = switch_br.iterateCases();
6170 const target_case_idx = target: while (it.next()) |case| {
6171 for (case.items) |item| {
6172 const val = Value.fromInterned(item.toInterned().?);
6173 if (cond_val.compareHetero(.eq, val, zcu)) break :target case.idx;
6174 }
6175 for (case.ranges) |range| {
6176 const low = Value.fromInterned(range[0].toInterned().?);
6177 const high = Value.fromInterned(range[1].toInterned().?);
6178 if (cond_val.compareHetero(.gte, low, zcu) and
6179 cond_val.compareHetero(.lte, high, zcu))
6180 {
6181 break :target case.idx;
6182 }
6183 }
6184 } else dispatch_info.case_blocks.len - 1;
6185 const target_block = dispatch_info.case_blocks[target_case_idx];
6186 target_block.ptr(&self.wip).incoming += 1;
6187 _ = try self.wip.br(target_block);
6188 return;
6189 }
6190
6191 // Runtime-known dispatch.
6192 const cond = try self.resolveInst(cond_ref);
6193
6194 if (dispatch_info.jmp_table) |jmp_table| {
6195 // We should use the constructed jump table.
6196 // First, check the bounds to branch to the `else` case if needed.
6197 const inbounds = try self.wip.bin(
6198 .@"and",
6199 try self.cmp(.normal, .gte, cond_ty, cond, jmp_table.min.toValue()),
6200 try self.cmp(.normal, .lte, cond_ty, cond, jmp_table.max.toValue()),
6201 "",
6202 );
6203 const jmp_table_block = try self.wip.block(1, "Then");
6204 const else_block = dispatch_info.case_blocks[dispatch_info.case_blocks.len - 1];
6205 else_block.ptr(&self.wip).incoming += 1;
6206 _ = try self.wip.brCond(inbounds, jmp_table_block, else_block, switch (jmp_table.in_bounds_hint) {
6207 .none => .none,
6208 .unpredictable => .unpredictable,
6209 .likely => .then_likely,
6210 .unlikely => .else_likely,
6211 });
6212
6213 self.wip.cursor = .{ .block = jmp_table_block };
6214
6215 // Figure out the list of blocks we might branch to.
6216 // This includes all case blocks, but it might not include the `else` block if
6217 // the table is dense.
6218 const target_blocks_len = dispatch_info.case_blocks.len - @intFromBool(!jmp_table.table_includes_else);
6219 const target_blocks = dispatch_info.case_blocks[0..target_blocks_len];
6220
6221 // Make sure to cast the index to a usize so it's not treated as negative!
6222 const table_index = try self.wip.cast(
6223 .zext,
6224 try self.wip.bin(.@"sub nuw", cond, jmp_table.min.toValue(), ""),
6225 try o.lowerType(Type.usize),
6226 "",
6227 );
6228 const target_ptr_ptr = try self.wip.gep(
6229 .inbounds,
6230 .ptr,
6231 jmp_table.table.toValue(),
6232 &.{table_index},
6233 "",
6234 );
6235 const target_ptr = try self.wip.load(.normal, .ptr, target_ptr_ptr, .default, "");
6236
6237 // Do the branch!
6238 _ = try self.wip.indirectbr(target_ptr, target_blocks);
6239
6240 // Mark all target blocks as having one more incoming branch.
6241 for (target_blocks) |case_block| {
6242 case_block.ptr(&self.wip).incoming += 1;
6243 }
6244
6245 return;
6246 }
6247
6248 // We must lower to an actual LLVM `switch` instruction.
6249 // The switch prongs will correspond to our scalar cases. Ranges will
6250 // be handled by conditional branches in the `else` prong.
6251
6252 const llvm_usize = try o.lowerType(Type.usize);
6253 const cond_int = if (cond.typeOfWip(&self.wip).isPointer(&o.builder))
6254 try self.wip.cast(.ptrtoint, cond, llvm_usize, "")
6255 else
6256 cond;
6257
6258 const llvm_cases_len, const last_range_case = info: {
6259 var llvm_cases_len: u32 = 0;
6260 var last_range_case: ?u32 = null;
6261 var it = switch_br.iterateCases();
6262 while (it.next()) |case| {
6263 if (case.ranges.len > 0) last_range_case = case.idx;
6264 llvm_cases_len += @intCast(case.items.len);
6265 }
6266 break :info .{ llvm_cases_len, last_range_case };
6267 };
6268
6269 // The `else` of the LLVM `switch` is the actual `else` prong only
6270 // if there are no ranges. Otherwise, the `else` will have a
6271 // conditional chain before the "true" `else` prong.
6272 const llvm_else_block = if (last_range_case == null)
6273 dispatch_info.case_blocks[dispatch_info.case_blocks.len - 1]
6274 else
6275 try self.wip.block(0, "RangeTest");
6276
6277 llvm_else_block.ptr(&self.wip).incoming += 1;
6278
6279 var wip_switch = try self.wip.@"switch"(cond_int, llvm_else_block, llvm_cases_len, dispatch_info.switch_weights);
6280 defer wip_switch.finish(&self.wip);
6281
6282 // Construct the actual cases. Set the cursor to the `else` block so
6283 // we can construct ranges at the same time as scalar cases.
6284 self.wip.cursor = .{ .block = llvm_else_block };
6285
6286 var it = switch_br.iterateCases();
6287 while (it.next()) |case| {
6288 const case_block = dispatch_info.case_blocks[case.idx];
6289
6290 for (case.items) |item| {
6291 const llvm_item = (try self.resolveInst(item)).toConst().?;
6292 const llvm_int_item = if (llvm_item.typeOf(&o.builder).isPointer(&o.builder))
6293 try o.builder.castConst(.ptrtoint, llvm_item, llvm_usize)
6294 else
6295 llvm_item;
6296 try wip_switch.addCase(llvm_int_item, case_block, &self.wip);
6297 }
6298 case_block.ptr(&self.wip).incoming += @intCast(case.items.len);
6299
6300 if (case.ranges.len == 0) continue;
6301
6302 // Add a conditional for the ranges, directing to the relevant bb.
6303 // We don't need to consider `cold` branch hints since that information is stored
6304 // in the target bb body, but we do care about likely/unlikely/unpredictable.
6305
6306 const hint = switch_br.getHint(case.idx);
6307
6308 var range_cond: ?Builder.Value = null;
6309 for (case.ranges) |range| {
6310 const llvm_min = try self.resolveInst(range[0]);
6311 const llvm_max = try self.resolveInst(range[1]);
6312 const cond_part = try self.wip.bin(
6313 .@"and",
6314 try self.cmp(.normal, .gte, cond_ty, cond, llvm_min),
6315 try self.cmp(.normal, .lte, cond_ty, cond, llvm_max),
6316 "",
6317 );
6318 if (range_cond) |prev| {
6319 range_cond = try self.wip.bin(.@"or", prev, cond_part, "");
6320 } else range_cond = cond_part;
6321 }
6322
6323 // If the check fails, we either branch to the "true" `else` case,
6324 // or to the next range condition.
6325 const range_else_block = if (case.idx == last_range_case.?)
6326 dispatch_info.case_blocks[dispatch_info.case_blocks.len - 1]
6327 else
6328 try self.wip.block(0, "RangeTest");
6329
6330 _ = try self.wip.brCond(range_cond.?, case_block, range_else_block, switch (hint) {
6331 .none, .cold => .none,
6332 .unpredictable => .unpredictable,
6333 .likely => .then_likely,
6334 .unlikely => .else_likely,
6335 });
6336 case_block.ptr(&self.wip).incoming += 1;
6337 range_else_block.ptr(&self.wip).incoming += 1;
6338
6339 // Construct the next range conditional (if any) in the false branch.
6340 self.wip.cursor = .{ .block = range_else_block };
6341 }
6342 }
6343
6344 fn airSwitchDispatch(self: *FuncGen, inst: Air.Inst.Index) !void {
6345 const br = self.air.instructions.items(.data)[@intFromEnum(inst)].br;
6346 const dispatch_info = self.switch_dispatch_info.get(br.block_inst).?;
6347 return self.lowerSwitchDispatch(br.block_inst, br.operand, dispatch_info);
6348 }
6349
6350 fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !void {
6081 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;6351 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6082 const cond = try self.resolveInst(pl_op.operand);6352 const cond = try self.resolveInst(pl_op.operand);
6083 const extra = self.air.extraData(Air.CondBr, pl_op.payload);6353 const extra = self.air.extraData(Air.CondBr, pl_op.payload);
...@@ -6136,7 +6406,6 @@ pub const FuncGen = struct {...@@ -6136,7 +6406,6 @@ pub const FuncGen = struct {
6136 try self.genBodyDebugScope(null, else_body, extra.data.branch_hints.else_cov);6406 try self.genBodyDebugScope(null, else_body, extra.data.branch_hints.else_cov);
61376407
6138 // No need to reset the insert cursor since this instruction is noreturn.6408 // No need to reset the insert cursor since this instruction is noreturn.
6139 return .none;
6140 }6409 }
61416410
6142 fn airTry(self: *FuncGen, body_tail: []const Air.Inst.Index, err_cold: bool) !Builder.Value {6411 fn airTry(self: *FuncGen, body_tail: []const Air.Inst.Index, err_cold: bool) !Builder.Value {
...@@ -6242,28 +6511,123 @@ pub const FuncGen = struct {...@@ -6242,28 +6511,123 @@ pub const FuncGen = struct {
6242 return fg.wip.extractValue(err_union, &.{offset}, "");6511 return fg.wip.extractValue(err_union, &.{offset}, "");
6243 }6512 }
62446513
6245 fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {6514 fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index, is_dispatch_loop: bool) !void {
6246 const o = self.ng.object;6515 const o = self.ng.object;
6516 const zcu = o.pt.zcu;
62476517
6248 const switch_br = self.air.unwrapSwitch(inst);6518 const switch_br = self.air.unwrapSwitch(inst);
62496519
6250 const cond = try self.resolveInst(switch_br.operand);6520 // For `loop_switch_br`, we need these BBs prepared ahead of time to generate dispatches.
6521 // For `switch_br`, they allow us to sometimes generate better IR by sharing a BB between
6522 // scalar and range cases in the same prong.
6523 // +1 for `else` case. This is not the same as the LLVM `else` prong, as that may first contain
6524 // conditionals to handle ranges.
6525 const case_blocks = try self.gpa.alloc(Builder.Function.Block.Index, switch_br.cases_len + 1);
6526 defer self.gpa.free(case_blocks);
6527 // We set incoming as 0 for now, and increment it as we construct dispatches.
6528 for (case_blocks[0 .. case_blocks.len - 1]) |*b| b.* = try self.wip.block(0, "Case");
6529 case_blocks[case_blocks.len - 1] = try self.wip.block(0, "Default");
6530
6531 // There's a special case here to manually generate a jump table in some cases.
6532 //
6533 // Labeled switch in Zig is intended to follow the "direct threading" pattern. We would ideally use a jump
6534 // table, and each `continue` has its own indirect `jmp`, to allow the branch predictor to more accurately
6535 // use data patterns to predict future dispatches. The problem, however, is that LLVM emits fascinatingly
6536 // bad asm for this. Not only does it not share the jump table -- which we really need it to do to prevent
6537 // destroying the cache -- but it also actually generates slightly different jump tables for each case,
6538 // and *a separate conditional branch beforehand* to handle dispatching back to the case we're currently
6539 // within(!!).
6540 //
6541 // This asm is really, really, not what we want. As such, we will construct the jump table manually where
6542 // appropriate (the values are dense and relatively few), and use it when lowering dispatches.
6543
6544 const jmp_table: ?SwitchDispatchInfo.JmpTable = jmp_table: {
6545 if (!is_dispatch_loop) break :jmp_table null;
6546 // On a 64-bit target, 1024 pointers in our jump table is about 8K of pointers. This seems just
6547 // about acceptable - it won't fill L1d cache on most CPUs.
6548 const max_table_len = 1024;
6549
6550 const cond_ty = self.typeOf(switch_br.operand);
6551 switch (cond_ty.zigTypeTag(zcu)) {
6552 .bool, .pointer => break :jmp_table null,
6553 .@"enum", .int, .error_set => {},
6554 else => unreachable,
6555 }
62516556
6252 const else_block = try self.wip.block(1, "Default");6557 if (cond_ty.intInfo(zcu).signedness == .signed) break :jmp_table null;
6253 const llvm_usize = try o.lowerType(Type.usize);6558
6254 const cond_int = if (cond.typeOfWip(&self.wip).isPointer(&o.builder))6559 // Don't worry about the size of the type -- it's irrelevant, because the prong values could be fairly dense.
6255 try self.wip.cast(.ptrtoint, cond, llvm_usize, "")6560 // If they are, then we will construct a jump table.
6256 else6561 const min, const max = self.switchCaseItemRange(switch_br);
6257 cond;6562 const min_int = min.getUnsignedInt(zcu) orelse break :jmp_table null;
6563 const max_int = max.getUnsignedInt(zcu) orelse break :jmp_table null;
6564 const table_len = max_int - min_int + 1;
6565 if (table_len > max_table_len) break :jmp_table null;
6566
6567 const table_elems = try self.gpa.alloc(Builder.Constant, @intCast(table_len));
6568 defer self.gpa.free(table_elems);
62586569
6259 const llvm_cases_len = llvm_cases_len: {6570 // Set them all to the `else` branch, then iterate over the AIR switch
6260 var len: u32 = 0;6571 // and replace all values which correspond to other prongs.
6572 @memset(table_elems, try o.builder.blockAddrConst(
6573 self.wip.function,
6574 case_blocks[case_blocks.len - 1],
6575 ));
6576 var item_count: u32 = 0;
6261 var it = switch_br.iterateCases();6577 var it = switch_br.iterateCases();
6262 while (it.next()) |case| len += @intCast(case.items.len);6578 while (it.next()) |case| {
6263 break :llvm_cases_len len;6579 const case_block = case_blocks[case.idx];
6580 const case_block_addr = try o.builder.blockAddrConst(
6581 self.wip.function,
6582 case_block,
6583 );
6584 for (case.items) |item| {
6585 const val = Value.fromInterned(item.toInterned().?);
6586 const table_idx = val.toUnsignedInt(zcu) - min_int;
6587 table_elems[@intCast(table_idx)] = case_block_addr;
6588 item_count += 1;
6589 }
6590 for (case.ranges) |range| {
6591 const low = Value.fromInterned(range[0].toInterned().?);
6592 const high = Value.fromInterned(range[1].toInterned().?);
6593 const low_idx = low.toUnsignedInt(zcu) - min_int;
6594 const high_idx = high.toUnsignedInt(zcu) - min_int;
6595 @memset(table_elems[@intCast(low_idx)..@intCast(high_idx + 1)], case_block_addr);
6596 item_count += @intCast(high_idx + 1 - low_idx);
6597 }
6598 }
6599
6600 const table_llvm_ty = try o.builder.arrayType(table_elems.len, .ptr);
6601 const table_val = try o.builder.arrayConst(table_llvm_ty, table_elems);
6602
6603 const table_variable = try o.builder.addVariable(
6604 try o.builder.strtabStringFmt("__jmptab_{d}", .{@intFromEnum(inst)}),
6605 table_llvm_ty,
6606 .default,
6607 );
6608 try table_variable.setInitializer(table_val, &o.builder);
6609 table_variable.setLinkage(.internal, &o.builder);
6610 table_variable.setUnnamedAddr(.unnamed_addr, &o.builder);
6611
6612 const table_includes_else = item_count != table_len;
6613
6614 break :jmp_table .{
6615 .min = try o.lowerValue(min.toIntern()),
6616 .max = try o.lowerValue(max.toIntern()),
6617 .in_bounds_hint = if (table_includes_else) .none else switch (switch_br.getElseHint()) {
6618 .none, .cold => .none,
6619 .unpredictable => .unpredictable,
6620 .likely => .likely,
6621 .unlikely => .unlikely,
6622 },
6623 .table = table_variable.toConst(&o.builder),
6624 .table_includes_else = table_includes_else,
6625 };
6264 };6626 };
62656627
6266 const weights: Builder.Function.Instruction.BrCond.Weights = weights: {6628 const weights: Builder.Function.Instruction.BrCond.Weights = weights: {
6629 if (jmp_table != null) break :weights .none; // not used
6630
6267 // First pass. If any weights are `.unpredictable`, unpredictable.6631 // First pass. If any weights are `.unpredictable`, unpredictable.
6268 // If all are `.none` or `.cold`, none.6632 // If all are `.none` or `.cold`, none.
6269 var any_likely = false;6633 var any_likely = false;
...@@ -6281,6 +6645,13 @@ pub const FuncGen = struct {...@@ -6281,6 +6645,13 @@ pub const FuncGen = struct {
6281 }6645 }
6282 if (!any_likely) break :weights .none;6646 if (!any_likely) break :weights .none;
62836647
6648 const llvm_cases_len = llvm_cases_len: {
6649 var len: u32 = 0;
6650 var it = switch_br.iterateCases();
6651 while (it.next()) |case| len += @intCast(case.items.len);
6652 break :llvm_cases_len len;
6653 };
6654
6284 var weights = try self.gpa.alloc(Builder.Metadata, llvm_cases_len + 1);6655 var weights = try self.gpa.alloc(Builder.Metadata, llvm_cases_len + 1);
6285 defer self.gpa.free(weights);6656 defer self.gpa.free(weights);
62866657
...@@ -6313,60 +6684,80 @@ pub const FuncGen = struct {...@@ -6313,60 +6684,80 @@ pub const FuncGen = struct {
6313 break :weights @enumFromInt(@intFromEnum(tuple));6684 break :weights @enumFromInt(@intFromEnum(tuple));
6314 };6685 };
63156686
6316 var wip_switch = try self.wip.@"switch"(cond_int, else_block, llvm_cases_len, weights);6687 const dispatch_info: SwitchDispatchInfo = .{
6317 defer wip_switch.finish(&self.wip);6688 .case_blocks = case_blocks,
6689 .switch_weights = weights,
6690 .jmp_table = jmp_table,
6691 };
6692
6693 if (is_dispatch_loop) {
6694 try self.switch_dispatch_info.putNoClobber(self.gpa, inst, dispatch_info);
6695 }
6696 defer if (is_dispatch_loop) {
6697 assert(self.switch_dispatch_info.remove(inst));
6698 };
6699
6700 // Generate the initial dispatch.
6701 // If this is a simple `switch_br`, this is the only dispatch.
6702 try self.lowerSwitchDispatch(inst, switch_br.operand, dispatch_info);
63186703
6704 // Iterate the cases and generate their bodies.
6319 var it = switch_br.iterateCases();6705 var it = switch_br.iterateCases();
6320 while (it.next()) |case| {6706 while (it.next()) |case| {
6321 const case_block = try self.wip.block(@intCast(case.items.len), "Case");6707 const case_block = case_blocks[case.idx];
6322 for (case.items) |item| {
6323 const llvm_item = (try self.resolveInst(item)).toConst().?;
6324 const llvm_int_item = if (llvm_item.typeOf(&o.builder).isPointer(&o.builder))
6325 try o.builder.castConst(.ptrtoint, llvm_item, llvm_usize)
6326 else
6327 llvm_item;
6328 try wip_switch.addCase(llvm_int_item, case_block, &self.wip);
6329 }
6330 self.wip.cursor = .{ .block = case_block };6708 self.wip.cursor = .{ .block = case_block };
6331 if (switch_br.getHint(case.idx) == .cold) _ = try self.wip.callIntrinsicAssumeCold();6709 if (switch_br.getHint(case.idx) == .cold) _ = try self.wip.callIntrinsicAssumeCold();
6332 try self.genBodyDebugScope(null, case.body, .poi);6710 try self.genBodyDebugScope(null, case.body, .none);
6333 }6711 }
63346712 self.wip.cursor = .{ .block = case_blocks[case_blocks.len - 1] };
6335 const else_body = it.elseBody();6713 const else_body = it.elseBody();
6336 self.wip.cursor = .{ .block = else_block };
6337 if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold();6714 if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold();
6338 if (else_body.len != 0) {6715 if (else_body.len > 0) {
6339 try self.genBodyDebugScope(null, else_body, .poi);6716 try self.genBodyDebugScope(null, it.elseBody(), .none);
6340 } else {6717 } else {
6341 _ = try self.wip.@"unreachable"();6718 _ = try self.wip.@"unreachable"();
6342 }6719 }
6720 }
63436721
6344 // No need to reset the insert cursor since this instruction is noreturn.6722 fn switchCaseItemRange(self: *FuncGen, switch_br: Air.UnwrappedSwitch) [2]Value {
6345 return .none;6723 const zcu = self.ng.object.pt.zcu;
6724 var it = switch_br.iterateCases();
6725 var min: ?Value = null;
6726 var max: ?Value = null;
6727 while (it.next()) |case| {
6728 for (case.items) |item| {
6729 const val = Value.fromInterned(item.toInterned().?);
6730 const low = if (min) |m| val.compareHetero(.lt, m, zcu) else true;
6731 const high = if (max) |m| val.compareHetero(.gt, m, zcu) else true;
6732 if (low) min = val;
6733 if (high) max = val;
6734 }
6735 for (case.ranges) |range| {
6736 const vals: [2]Value = .{
6737 Value.fromInterned(range[0].toInterned().?),
6738 Value.fromInterned(range[1].toInterned().?),
6739 };
6740 const low = if (min) |m| vals[0].compareHetero(.lt, m, zcu) else true;
6741 const high = if (max) |m| vals[1].compareHetero(.gt, m, zcu) else true;
6742 if (low) min = vals[0];
6743 if (high) max = vals[1];
6744 }
6745 }
6746 return .{ min.?, max.? };
6346 }6747 }
63476748
6348 fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {6749 fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !void {
6349 const o = self.ng.object;
6350 const zcu = o.pt.zcu;
6351 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;6750 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
6352 const loop = self.air.extraData(Air.Block, ty_pl.payload);6751 const loop = self.air.extraData(Air.Block, ty_pl.payload);
6353 const body: []const Air.Inst.Index = @ptrCast(self.air.extra[loop.end..][0..loop.data.body_len]);6752 const body: []const Air.Inst.Index = @ptrCast(self.air.extra[loop.end..][0..loop.data.body_len]);
6354 const loop_block = try self.wip.block(2, "Loop");6753 const loop_block = try self.wip.block(1, "Loop"); // `airRepeat` will increment incoming each time
6355 _ = try self.wip.br(loop_block);6754 _ = try self.wip.br(loop_block);
63566755
6756 try self.loops.putNoClobber(self.gpa, inst, loop_block);
6757 defer assert(self.loops.remove(inst));
6758
6357 self.wip.cursor = .{ .block = loop_block };6759 self.wip.cursor = .{ .block = loop_block };
6358 try self.genBodyDebugScope(null, body, .none);6760 try self.genBodyDebugScope(null, body, .none);
6359
6360 // TODO instead of this logic, change AIR to have the property that
6361 // every block is guaranteed to end with a noreturn instruction.
6362 // Then we can simply rely on the fact that a repeat or break instruction
6363 // would have been emitted already. Also the main loop in genBody can
6364 // be while(true) instead of for(body), which will eliminate 1 branch on
6365 // a hot path.
6366 if (body.len == 0 or !self.typeOfIndex(body[body.len - 1]).isNoReturn(zcu)) {
6367 _ = try self.wip.br(loop_block);
6368 }
6369 return .none;
6370 }6761 }
63716762
6372 fn airArrayToSlice(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {6763 fn airArrayToSlice(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
...@@ -6861,10 +7252,9 @@ pub const FuncGen = struct {...@@ -6861,10 +7252,9 @@ pub const FuncGen = struct {
6861 return self.wip.not(operand, "");7252 return self.wip.not(operand, "");
6862 }7253 }
68637254
6864 fn airUnreach(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {7255 fn airUnreach(self: *FuncGen, inst: Air.Inst.Index) !void {
6865 _ = inst;7256 _ = inst;
6866 _ = try self.wip.@"unreachable"();7257 _ = try self.wip.@"unreachable"();
6867 return .none;
6868 }7258 }
68697259
6870 fn airDbgStmt(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {7260 fn airDbgStmt(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
...@@ -9267,11 +9657,10 @@ pub const FuncGen = struct {...@@ -9267,11 +9657,10 @@ pub const FuncGen = struct {
9267 return fg.load(ptr, ptr_ty);9657 return fg.load(ptr, ptr_ty);
9268 }9658 }
92699659
9270 fn airTrap(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {9660 fn airTrap(self: *FuncGen, inst: Air.Inst.Index) !void {
9271 _ = inst;9661 _ = inst;
9272 _ = try self.wip.callIntrinsic(.normal, .none, .trap, &.{}, &.{}, "");9662 _ = try self.wip.callIntrinsic(.normal, .none, .trap, &.{}, &.{}, "");
9273 _ = try self.wip.@"unreachable"();9663 _ = try self.wip.@"unreachable"();
9274 return .none;
9275 }9664 }
92769665
9277 fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {9666 fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
src/codegen/llvm/Builder.zig+82-15
...@@ -4157,6 +4157,7 @@ pub const Function = struct {...@@ -4157,6 +4157,7 @@ pub const Function = struct {
4157 @"icmp ugt",4157 @"icmp ugt",
4158 @"icmp ule",4158 @"icmp ule",
4159 @"icmp ult",4159 @"icmp ult",
4160 indirectbr,
4160 insertelement,4161 insertelement,
4161 insertvalue,4162 insertvalue,
4162 inttoptr,4163 inttoptr,
...@@ -4367,6 +4368,7 @@ pub const Function = struct {...@@ -4367,6 +4368,7 @@ pub const Function = struct {
4367 return switch (wip.instructions.items(.tag)[@intFromEnum(self)]) {4368 return switch (wip.instructions.items(.tag)[@intFromEnum(self)]) {
4368 .br,4369 .br,
4369 .br_cond,4370 .br_cond,
4371 .indirectbr,
4370 .ret,4372 .ret,
4371 .@"ret void",4373 .@"ret void",
4372 .@"switch",4374 .@"switch",
...@@ -4381,6 +4383,7 @@ pub const Function = struct {...@@ -4381,6 +4383,7 @@ pub const Function = struct {
4381 .br,4383 .br,
4382 .br_cond,4384 .br_cond,
4383 .fence,4385 .fence,
4386 .indirectbr,
4384 .ret,4387 .ret,
4385 .@"ret void",4388 .@"ret void",
4386 .store,4389 .store,
...@@ -4471,6 +4474,7 @@ pub const Function = struct {...@@ -4471,6 +4474,7 @@ pub const Function = struct {
4471 .br,4474 .br,
4472 .br_cond,4475 .br_cond,
4473 .fence,4476 .fence,
4477 .indirectbr,
4474 .ret,4478 .ret,
4475 .@"ret void",4479 .@"ret void",
4476 .store,4480 .store,
...@@ -4657,6 +4661,7 @@ pub const Function = struct {...@@ -4657,6 +4661,7 @@ pub const Function = struct {
4657 .br,4661 .br,
4658 .br_cond,4662 .br_cond,
4659 .fence,4663 .fence,
4664 .indirectbr,
4660 .ret,4665 .ret,
4661 .@"ret void",4666 .@"ret void",
4662 .store,4667 .store,
...@@ -4837,6 +4842,12 @@ pub const Function = struct {...@@ -4837,6 +4842,12 @@ pub const Function = struct {
4837 //case_blocks: [cases_len]Block.Index,4842 //case_blocks: [cases_len]Block.Index,
4838 };4843 };
48394844
4845 pub const IndirectBr = struct {
4846 addr: Value,
4847 targets_len: u32,
4848 //targets: [targets_len]Block.Index,
4849 };
4850
4840 pub const Binary = struct {4851 pub const Binary = struct {
4841 lhs: Value,4852 lhs: Value,
4842 rhs: Value,4853 rhs: Value,
...@@ -5294,10 +5305,27 @@ pub const WipFunction = struct {...@@ -5294,10 +5305,27 @@ pub const WipFunction = struct {
5294 return .{ .index = 0, .instruction = instruction };5305 return .{ .index = 0, .instruction = instruction };
5295 }5306 }
52965307
5308 pub fn indirectbr(
5309 self: *WipFunction,
5310 addr: Value,
5311 targets: []const Block.Index,
5312 ) Allocator.Error!Instruction.Index {
5313 try self.ensureUnusedExtraCapacity(1, Instruction.IndirectBr, targets.len);
5314 const instruction = try self.addInst(null, .{
5315 .tag = .indirectbr,
5316 .data = self.addExtraAssumeCapacity(Instruction.IndirectBr{
5317 .addr = addr,
5318 .targets_len = @intCast(targets.len),
5319 }),
5320 });
5321 _ = self.extra.appendSliceAssumeCapacity(@ptrCast(targets));
5322 for (targets) |target| target.ptr(self).branches += 1;
5323 return instruction;
5324 }
5325
5297 pub fn @"unreachable"(self: *WipFunction) Allocator.Error!Instruction.Index {5326 pub fn @"unreachable"(self: *WipFunction) Allocator.Error!Instruction.Index {
5298 try self.ensureUnusedExtraCapacity(1, NoExtra, 0);5327 try self.ensureUnusedExtraCapacity(1, NoExtra, 0);
5299 const instruction = try self.addInst(null, .{ .tag = .@"unreachable", .data = undefined });5328 return try self.addInst(null, .{ .tag = .@"unreachable", .data = undefined });
5300 return instruction;
5301 }5329 }
53025330
5303 pub fn un(5331 pub fn un(
...@@ -6299,8 +6327,7 @@ pub const WipFunction = struct {...@@ -6299,8 +6327,7 @@ pub const WipFunction = struct {
6299 });6327 });
6300 names[@intFromEnum(new_block_index)] = try wip_name.map(current_block.name, "");6328 names[@intFromEnum(new_block_index)] = try wip_name.map(current_block.name, "");
6301 for (current_block.instructions.items) |old_instruction_index| {6329 for (current_block.instructions.items) |old_instruction_index| {
6302 const new_instruction_index: Instruction.Index =6330 const new_instruction_index: Instruction.Index = @enumFromInt(function.instructions.len);
6303 @enumFromInt(function.instructions.len);
6304 var instruction = self.instructions.get(@intFromEnum(old_instruction_index));6331 var instruction = self.instructions.get(@intFromEnum(old_instruction_index));
6305 switch (instruction.tag) {6332 switch (instruction.tag) {
6306 .add,6333 .add,
...@@ -6509,6 +6536,15 @@ pub const WipFunction = struct {...@@ -6509,6 +6536,15 @@ pub const WipFunction = struct {
6509 });6536 });
6510 wip_extra.appendMappedValues(indices, instructions);6537 wip_extra.appendMappedValues(indices, instructions);
6511 },6538 },
6539 .indirectbr => {
6540 var extra = self.extraDataTrail(Instruction.IndirectBr, instruction.data);
6541 const targets = extra.trail.next(extra.data.targets_len, Block.Index, self);
6542 instruction.data = wip_extra.addExtra(Instruction.IndirectBr{
6543 .addr = instructions.map(extra.data.addr),
6544 .targets_len = extra.data.targets_len,
6545 });
6546 wip_extra.appendSlice(targets);
6547 },
6512 .insertelement => {6548 .insertelement => {
6513 const extra = self.extraData(Instruction.InsertElement, instruction.data);6549 const extra = self.extraData(Instruction.InsertElement, instruction.data);
6514 instruction.data = wip_extra.addExtra(Instruction.InsertElement{6550 instruction.data = wip_extra.addExtra(Instruction.InsertElement{
...@@ -7555,10 +7591,10 @@ pub const Constant = enum(u32) {...@@ -7555,10 +7591,10 @@ pub const Constant = enum(u32) {
7555 .blockaddress => |tag| {7591 .blockaddress => |tag| {
7556 const extra = data.builder.constantExtraData(BlockAddress, item.data);7592 const extra = data.builder.constantExtraData(BlockAddress, item.data);
7557 const function = extra.function.ptrConst(data.builder);7593 const function = extra.function.ptrConst(data.builder);
7558 try writer.print("{s}({}, %{d})", .{7594 try writer.print("{s}({}, {})", .{
7559 @tagName(tag),7595 @tagName(tag),
7560 function.global.fmt(data.builder),7596 function.global.fmt(data.builder),
7561 @intFromEnum(extra.block), // TODO7597 extra.block.toInst(function).fmt(extra.function, data.builder),
7562 });7598 });
7563 },7599 },
7564 .dso_local_equivalent,7600 .dso_local_equivalent,
...@@ -9902,6 +9938,23 @@ pub fn printUnbuffered(...@@ -9902,6 +9938,23 @@ pub fn printUnbuffered(
9902 index.fmt(function_index, self),9938 index.fmt(function_index, self),
9903 });9939 });
9904 },9940 },
9941 .indirectbr => |tag| {
9942 var extra =
9943 function.extraDataTrail(Function.Instruction.IndirectBr, instruction.data);
9944 const targets =
9945 extra.trail.next(extra.data.targets_len, Function.Block.Index, &function);
9946 try writer.print(" {s} {%}, [", .{
9947 @tagName(tag),
9948 extra.data.addr.fmt(function_index, self),
9949 });
9950 for (0.., targets) |target_index, target| {
9951 if (target_index > 0) try writer.writeAll(", ");
9952 try writer.print("{%}", .{
9953 target.toInst(&function).fmt(function_index, self),
9954 });
9955 }
9956 try writer.writeByte(']');
9957 },
9905 .insertelement => |tag| {9958 .insertelement => |tag| {
9906 const extra =9959 const extra =
9907 function.extraData(Function.Instruction.InsertElement, instruction.data);9960 function.extraData(Function.Instruction.InsertElement, instruction.data);
...@@ -14775,15 +14828,6 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co...@@ -14775,15 +14828,6 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co
14775 .indices = indices,14828 .indices = indices,
14776 });14829 });
14777 },14830 },
14778 .insertvalue => {
14779 var extra = func.extraDataTrail(Function.Instruction.InsertValue, data);
14780 const indices = extra.trail.next(extra.data.indices_len, u32, &func);
14781 try function_block.writeAbbrev(FunctionBlock.InsertValue{
14782 .val = adapter.getOffsetValueIndex(extra.data.val),
14783 .elem = adapter.getOffsetValueIndex(extra.data.elem),
14784 .indices = indices,
14785 });
14786 },
14787 .extractelement => {14831 .extractelement => {
14788 const extra = func.extraData(Function.Instruction.ExtractElement, data);14832 const extra = func.extraData(Function.Instruction.ExtractElement, data);
14789 try function_block.writeAbbrev(FunctionBlock.ExtractElement{14833 try function_block.writeAbbrev(FunctionBlock.ExtractElement{
...@@ -14791,6 +14835,20 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co...@@ -14791,6 +14835,20 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co
14791 .index = adapter.getOffsetValueIndex(extra.index),14835 .index = adapter.getOffsetValueIndex(extra.index),
14792 });14836 });
14793 },14837 },
14838 .indirectbr => {
14839 var extra =
14840 func.extraDataTrail(Function.Instruction.IndirectBr, datas[instr_index]);
14841 const targets =
14842 extra.trail.next(extra.data.targets_len, Function.Block.Index, &func);
14843 try function_block.writeAbbrevAdapted(
14844 FunctionBlock.IndirectBr{
14845 .ty = extra.data.addr.typeOf(@enumFromInt(func_index), self),
14846 .addr = extra.data.addr,
14847 .targets = targets,
14848 },
14849 adapter,
14850 );
14851 },
14794 .insertelement => {14852 .insertelement => {
14795 const extra = func.extraData(Function.Instruction.InsertElement, data);14853 const extra = func.extraData(Function.Instruction.InsertElement, data);
14796 try function_block.writeAbbrev(FunctionBlock.InsertElement{14854 try function_block.writeAbbrev(FunctionBlock.InsertElement{
...@@ -14799,6 +14857,15 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co...@@ -14799,6 +14857,15 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co
14799 .index = adapter.getOffsetValueIndex(extra.index),14857 .index = adapter.getOffsetValueIndex(extra.index),
14800 });14858 });
14801 },14859 },
14860 .insertvalue => {
14861 var extra = func.extraDataTrail(Function.Instruction.InsertValue, datas[instr_index]);
14862 const indices = extra.trail.next(extra.data.indices_len, u32, &func);
14863 try function_block.writeAbbrev(FunctionBlock.InsertValue{
14864 .val = adapter.getOffsetValueIndex(extra.data.val),
14865 .elem = adapter.getOffsetValueIndex(extra.data.elem),
14866 .indices = indices,
14867 });
14868 },
14802 .select => {14869 .select => {
14803 const extra = func.extraData(Function.Instruction.Select, data);14870 const extra = func.extraData(Function.Instruction.Select, data);
14804 try function_block.writeAbbrev(FunctionBlock.Select{14871 try function_block.writeAbbrev(FunctionBlock.Select{
src/codegen/llvm/ir.zig+14
...@@ -19,6 +19,7 @@ const LineAbbrev = AbbrevOp{ .vbr = 8 };...@@ -19,6 +19,7 @@ const LineAbbrev = AbbrevOp{ .vbr = 8 };
19const ColumnAbbrev = AbbrevOp{ .vbr = 8 };19const ColumnAbbrev = AbbrevOp{ .vbr = 8 };
2020
21const BlockAbbrev = AbbrevOp{ .vbr = 6 };21const BlockAbbrev = AbbrevOp{ .vbr = 6 };
22const BlockArrayAbbrev = AbbrevOp{ .array_vbr = 6 };
2223
23/// Unused tags are commented out so that they are omitted in the generated24/// Unused tags are commented out so that they are omitted in the generated
24/// bitcode, which scans over this enum using reflection.25/// bitcode, which scans over this enum using reflection.
...@@ -1294,6 +1295,7 @@ pub const FunctionBlock = struct {...@@ -1294,6 +1295,7 @@ pub const FunctionBlock = struct {
1294 DebugLoc,1295 DebugLoc,
1295 DebugLocAgain,1296 DebugLocAgain,
1296 ColdOperandBundle,1297 ColdOperandBundle,
1298 IndirectBr,
1297 };1299 };
12981300
1299 pub const DeclareBlocks = struct {1301 pub const DeclareBlocks = struct {
...@@ -1813,6 +1815,18 @@ pub const FunctionBlock = struct {...@@ -1813,6 +1815,18 @@ pub const FunctionBlock = struct {
1813 .{ .literal = 0 },1815 .{ .literal = 0 },
1814 };1816 };
1815 };1817 };
1818
1819 pub const IndirectBr = struct {
1820 pub const ops = [_]AbbrevOp{
1821 .{ .literal = 31 },
1822 .{ .fixed_runtime = Builder.Type },
1823 ValueAbbrev,
1824 BlockArrayAbbrev,
1825 };
1826 ty: Builder.Type,
1827 addr: Builder.Value,
1828 targets: []const Builder.Function.Block.Index,
1829 };
1816};1830};
18171831
1818pub const FunctionValueSymbolTable = struct {1832pub const FunctionValueSymbolTable = struct {
src/codegen/spirv.zig+2
...@@ -3340,6 +3340,7 @@ const NavGen = struct {...@@ -3340,6 +3340,7 @@ const NavGen = struct {
3340 .store, .store_safe => return self.airStore(inst),3340 .store, .store_safe => return self.airStore(inst),
33413341
3342 .br => return self.airBr(inst),3342 .br => return self.airBr(inst),
3343 .repeat => return self.fail("TODO implement `repeat`", .{}),
3343 .breakpoint => return,3344 .breakpoint => return,
3344 .cond_br => return self.airCondBr(inst),3345 .cond_br => return self.airCondBr(inst),
3345 .loop => return self.airLoop(inst),3346 .loop => return self.airLoop(inst),
...@@ -6211,6 +6212,7 @@ const NavGen = struct {...@@ -6211,6 +6212,7 @@ const NavGen = struct {
6211 var num_conditions: u32 = 0;6212 var num_conditions: u32 = 0;
6212 var it = switch_br.iterateCases();6213 var it = switch_br.iterateCases();
6213 while (it.next()) |case| {6214 while (it.next()) |case| {
6215 if (case.ranges.len > 0) return self.todo("switch with ranges", .{});
6214 num_conditions += @intCast(case.items.len);6216 num_conditions += @intCast(case.items.len);
6215 }6217 }
6216 break :blk num_conditions;6218 break :blk num_conditions;
src/print_air.zig+14-1
...@@ -296,10 +296,12 @@ const Writer = struct {...@@ -296,10 +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),
300 .repeat => try w.writeRepeat(s, inst),
299 .cond_br => try w.writeCondBr(s, inst),301 .cond_br => try w.writeCondBr(s, inst),
300 .@"try", .try_cold => try w.writeTry(s, inst),302 .@"try", .try_cold => try w.writeTry(s, inst),
301 .try_ptr, .try_ptr_cold => try w.writeTryPtr(s, inst),303 .try_ptr, .try_ptr_cold => try w.writeTryPtr(s, inst),
302 .switch_br => try w.writeSwitchBr(s, inst),304 .loop_switch_br, .switch_br => try w.writeSwitchBr(s, inst),
303 .cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst),305 .cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst),
304 .fence => try w.writeFence(s, inst),306 .fence => try w.writeFence(s, inst),
305 .atomic_load => try w.writeAtomicLoad(s, inst),307 .atomic_load => try w.writeAtomicLoad(s, inst),
...@@ -708,6 +710,11 @@ const Writer = struct {...@@ -708,6 +710,11 @@ const Writer = struct {
708 try w.writeOperand(s, inst, 0, br.operand);710 try w.writeOperand(s, inst, 0, br.operand);
709 }711 }
710712
713 fn writeRepeat(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
714 const repeat = w.air.instructions.items(.data)[@intFromEnum(inst)].repeat;
715 try w.writeInstIndex(s, repeat.loop_inst, false);
716 }
717
711 fn writeTry(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {718 fn writeTry(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
712 const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;719 const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
713 const extra = w.air.extraData(Air.Try, pl_op.payload);720 const extra = w.air.extraData(Air.Try, pl_op.payload);
...@@ -864,6 +871,12 @@ const Writer = struct {...@@ -864,6 +871,12 @@ const Writer = struct {
864 if (item_i != 0) try s.writeAll(", ");871 if (item_i != 0) try s.writeAll(", ");
865 try w.writeInstRef(s, item, false);872 try w.writeInstRef(s, item, false);
866 }873 }
874 for (case.ranges, 0..) |range, range_i| {
875 if (range_i != 0 or case.items.len != 0) try s.writeAll(", ");
876 try w.writeInstRef(s, range[0], false);
877 try s.writeAll("...");
878 try w.writeInstRef(s, range[1], false);
879 }
867 try s.writeAll("] ");880 try s.writeAll("] ");
868 const hint = switch_br.getHint(case.idx);881 const hint = switch_br.getHint(case.idx);
869 if (hint != .none) {882 if (hint != .none) {
src/print_zir.zig+1
...@@ -304,6 +304,7 @@ const Writer = struct {...@@ -304,6 +304,7 @@ const Writer = struct {
304304
305 .@"break",305 .@"break",
306 .break_inline,306 .break_inline,
307 .switch_continue,
307 => try self.writeBreak(stream, inst),308 => try self.writeBreak(stream, inst),
308309
309 .slice_start => try self.writeSliceStart(stream, inst),310 .slice_start => try self.writeSliceStart(stream, inst),
test/behavior.zig+1
...@@ -89,6 +89,7 @@ test {...@@ -89,6 +89,7 @@ test {
89 _ = @import("behavior/struct_contains_null_ptr_itself.zig");89 _ = @import("behavior/struct_contains_null_ptr_itself.zig");
90 _ = @import("behavior/struct_contains_slice_of_itself.zig");90 _ = @import("behavior/struct_contains_slice_of_itself.zig");
91 _ = @import("behavior/switch.zig");91 _ = @import("behavior/switch.zig");
92 _ = @import("behavior/switch_loop.zig");
92 _ = @import("behavior/switch_prong_err_enum.zig");93 _ = @import("behavior/switch_prong_err_enum.zig");
93 _ = @import("behavior/switch_prong_implicit_cast.zig");94 _ = @import("behavior/switch_prong_implicit_cast.zig");
94 _ = @import("behavior/switch_on_captured_error.zig");95 _ = @import("behavior/switch_on_captured_error.zig");
test/behavior/switch.zig+24
...@@ -961,3 +961,27 @@ test "block error return trace index is reset between prongs" {...@@ -961,3 +961,27 @@ test "block error return trace index is reset between prongs" {
961 };961 };
962 try result;962 try result;
963}963}
964
965test "labeled switch with break" {
966 var six: u32 = undefined;
967 six = 6;
968
969 const val = s: switch (six) {
970 0...4 => break :s false,
971 5 => break :s false,
972 6...7 => break :s true,
973 else => break :s false,
974 };
975
976 try expect(val);
977
978 // Make sure the switch is implicitly comptime!
979 const comptime_val = s: switch (@as(u32, 6)) {
980 0...4 => break :s false,
981 5 => break :s false,
982 6...7 => break :s true,
983 else => break :s false,
984 };
985
986 comptime assert(comptime_val);
987}
test/behavior/switch_loop.zig created+200
...@@ -0,0 +1,200 @@
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_aarch64) return error.SkipZigTest; // TODO
8 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
9 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
10 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
11
12 const S = struct {
13 fn doTheTest() !void {
14 var start: u32 = undefined;
15 start = 32;
16 const result: u32 = s: switch (start) {
17 0 => 0,
18 1 => 1,
19 2 => 2,
20 3 => 3,
21 else => |x| continue :s x / 2,
22 };
23 try expect(result == 2);
24 }
25 };
26 try S.doTheTest();
27 try comptime S.doTheTest();
28}
29
30test "switch loop with ranges" {
31 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
32 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
33 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
34 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
35 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
36
37 const S = struct {
38 fn doTheTest() !void {
39 var start: u32 = undefined;
40 start = 32;
41 const result = s: switch (start) {
42 0...3 => |x| x,
43 else => |x| continue :s x / 2,
44 };
45 try expect(result == 2);
46 }
47 };
48 try S.doTheTest();
49 try comptime S.doTheTest();
50}
51
52test "switch loop on enum" {
53 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
54 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
55 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
56 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
57 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
58
59 const S = struct {
60 const E = enum { a, b, c };
61
62 fn doTheTest() !void {
63 var start: E = undefined;
64 start = .a;
65 const result: u32 = s: switch (start) {
66 .a => continue :s .b,
67 .b => continue :s .c,
68 .c => 123,
69 };
70 try expect(result == 123);
71 }
72 };
73 try S.doTheTest();
74 try comptime S.doTheTest();
75}
76
77test "switch loop on tagged union" {
78 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
79 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
80 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
81 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
82 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
83 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
84
85 const S = struct {
86 const U = union(enum) {
87 a: u32,
88 b: f32,
89 c: f32,
90 };
91
92 fn doTheTest() !void {
93 var start: U = undefined;
94 start = .{ .a = 80 };
95 const result = s: switch (start) {
96 .a => |x| switch (x) {
97 0...49 => continue :s .{ .b = @floatFromInt(x) },
98 50 => continue :s .{ .c = @floatFromInt(x) },
99 else => continue :s .{ .a = x / 2 },
100 },
101 .b => |x| x,
102 .c => return error.TestFailed,
103 };
104 try expect(result == 40.0);
105 }
106 };
107 try S.doTheTest();
108 try comptime S.doTheTest();
109}
110
111test "switch loop dispatching instructions" {
112 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
113 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
114 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
115 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
116 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
117
118 const S = struct {
119 const Inst = union(enum) {
120 set: u32,
121 add: u32,
122 sub: u32,
123 end,
124 };
125
126 fn doTheTest() !void {
127 var insts: [5]Inst = undefined;
128 @memcpy(&insts, &[5]Inst{
129 .{ .set = 123 },
130 .{ .add = 100 },
131 .{ .sub = 50 },
132 .{ .sub = 10 },
133 .end,
134 });
135 var i: u32 = 0;
136 var cur: u32 = undefined;
137 eval: switch (insts[0]) {
138 .set => |x| {
139 cur = x;
140 i += 1;
141 continue :eval insts[i];
142 },
143 .add => |x| {
144 cur += x;
145 i += 1;
146 continue :eval insts[i];
147 },
148 .sub => |x| {
149 cur -= x;
150 i += 1;
151 continue :eval insts[i];
152 },
153 .end => {},
154 }
155 try expect(cur == 163);
156 }
157 };
158 try S.doTheTest();
159 try comptime S.doTheTest();
160}
161
162test "switch loop with pointer capture" {
163 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
164 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
165 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
166 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
167 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
168
169 const S = struct {
170 const U = union(enum) {
171 a: u32,
172 b: u32,
173 c: u32,
174 };
175
176 fn doTheTest() !void {
177 var a: U = .{ .a = 100 };
178 var b: U = .{ .b = 200 };
179 var c: U = .{ .c = 300 };
180 inc: switch (a) {
181 .a => |*x| {
182 x.* += 1;
183 continue :inc b;
184 },
185 .b => |*x| {
186 x.* += 10;
187 continue :inc c;
188 },
189 .c => |*x| {
190 x.* += 50;
191 },
192 }
193 try expect(a.a == 101);
194 try expect(b.b == 210);
195 try expect(c.c == 350);
196 }
197 };
198 try S.doTheTest();
199 try comptime S.doTheTest();
200}
test/cases/compile_errors/duplicate-unused_labels.zig+6
...@@ -22,6 +22,11 @@ comptime {...@@ -22,6 +22,11 @@ comptime {
22comptime {22comptime {
23 blk: for (@as([0]void, undefined)) |_| {}23 blk: for (@as([0]void, undefined)) |_| {}
24}24}
25comptime {
26 blk: switch (true) {
27 else => {},
28 }
29}
2530
26// error31// error
27// target=native32// target=native
...@@ -35,3 +40,4 @@ comptime {...@@ -35,3 +40,4 @@ comptime {
35// :17:5: error: unused block label40// :17:5: error: unused block label
36// :20:5: error: unused while loop label41// :20:5: error: unused while loop label
37// :23:5: error: unused for loop label42// :23:5: error: unused for loop label
43// :26:5: error: unused switch label