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