| 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,14 +1184,7 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex { |
| 1184 | n = extra.sentinel; | 1184 | n = extra.sentinel; |
| 1185 | }, | 1185 | }, |
| 1186 | 1186 | ||
| 1187 | .@"continue" => { | 1187 | .@"continue", .@"break" => { |
| 1188 | if (datas[n].lhs != 0) { | ||
| 1189 | return datas[n].lhs + end_offset; | ||
| 1190 | } else { | ||
| 1191 | return main_tokens[n] + end_offset; | ||
| 1192 | } | ||
| 1193 | }, | ||
| 1194 | .@"break" => { | ||
| 1195 | if (datas[n].rhs != 0) { | 1188 | if (datas[n].rhs != 0) { |
| 1196 | n = datas[n].rhs; | 1189 | n = datas[n].rhs; |
| 1197 | } else if (datas[n].lhs != 0) { | 1190 | } else if (datas[n].lhs != 0) { |
| ... | @@ -1895,6 +1888,15 @@ pub fn taggedUnionEnumTag(tree: Ast, node: Node.Index) full.ContainerDecl { | ... | @@ -1895,6 +1888,15 @@ pub fn taggedUnionEnumTag(tree: Ast, node: Node.Index) full.ContainerDecl { |
| 1895 | }); | 1888 | }); |
| 1896 | } | 1889 | } |
| 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 | pub fn switchCaseOne(tree: Ast, node: Node.Index) full.SwitchCase { | 1900 | pub fn switchCaseOne(tree: Ast, node: Node.Index) full.SwitchCase { |
| 1899 | const data = &tree.nodes.items(.data)[node]; | 1901 | const data = &tree.nodes.items(.data)[node]; |
| 1900 | const values: *[1]Node.Index = &data.lhs; | 1902 | const values: *[1]Node.Index = &data.lhs; |
| ... | @@ -2206,6 +2208,21 @@ fn fullContainerDeclComponents(tree: Ast, info: full.ContainerDecl.Components) f | ... | @@ -2206,6 +2208,21 @@ fn fullContainerDeclComponents(tree: Ast, info: full.ContainerDecl.Components) f |
| 2206 | return result; | 2208 | return result; |
| 2207 | } | 2209 | } |
| 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 | fn fullSwitchCaseComponents(tree: Ast, info: full.SwitchCase.Components, node: Node.Index) full.SwitchCase { | 2226 | fn fullSwitchCaseComponents(tree: Ast, info: full.SwitchCase.Components, node: Node.Index) full.SwitchCase { |
| 2210 | const token_tags = tree.tokens.items(.tag); | 2227 | const token_tags = tree.tokens.items(.tag); |
| 2211 | const node_tags = tree.nodes.items(.tag); | 2228 | const node_tags = tree.nodes.items(.tag); |
| ... | @@ -2477,6 +2494,13 @@ pub fn fullContainerDecl(tree: Ast, buffer: *[2]Ast.Node.Index, node: Node.Index | ... | @@ -2477,6 +2494,13 @@ pub fn fullContainerDecl(tree: Ast, buffer: *[2]Ast.Node.Index, node: Node.Index |
| 2477 | }; | 2494 | }; |
| 2478 | } | 2495 | } |
| 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 | pub fn fullSwitchCase(tree: Ast, node: Node.Index) ?full.SwitchCase { | 2504 | pub fn fullSwitchCase(tree: Ast, node: Node.Index) ?full.SwitchCase { |
| 2481 | return switch (tree.nodes.items(.tag)[node]) { | 2505 | return switch (tree.nodes.items(.tag)[node]) { |
| 2482 | .switch_case_one, .switch_case_inline_one => tree.switchCaseOne(node), | 2506 | .switch_case_one, .switch_case_inline_one => tree.switchCaseOne(node), |
| ... | @@ -2829,6 +2853,17 @@ pub const full = struct { | ... | @@ -2829,6 +2853,17 @@ pub const full = struct { |
| 2829 | }; | 2853 | }; |
| 2830 | }; | 2854 | }; |
| 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 | pub const SwitchCase = struct { | 2867 | pub const SwitchCase = struct { |
| 2833 | inline_token: ?TokenIndex, | 2868 | inline_token: ?TokenIndex, |
| 2834 | /// Points to the first token after the `|`. Will either be an identifier or | 2869 | /// Points to the first token after the `|`. Will either be an identifier or |
| ... | @@ -3287,7 +3322,8 @@ pub const Node = struct { | ... | @@ -3287,7 +3322,8 @@ pub const Node = struct { |
| 3287 | @"suspend", | 3322 | @"suspend", |
| 3288 | /// `resume lhs`. rhs is unused. | 3323 | /// `resume lhs`. rhs is unused. |
| 3289 | @"resume", | 3324 | @"resume", |
| 3290 | /// `continue`. lhs is token index of label if any. rhs is unused. | 3325 | /// `continue :lhs rhs` |
| 3326 | /// both lhs and rhs may be omitted. | ||
| 3291 | @"continue", | 3327 | @"continue", |
| 3292 | /// `break :lhs rhs` | 3328 | /// `break :lhs rhs` |
| 3293 | /// both lhs and rhs may be omitted. | 3329 | /// both lhs and rhs may be omitted. |
lib/std/zig/AstGen.zig+97-16| ... | @@ -1144,7 +1144,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE | ... | @@ -1144,7 +1144,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE |
| 1144 | .error_set_decl => return errorSetDecl(gz, ri, node), | 1144 | .error_set_decl => return errorSetDecl(gz, ri, node), |
| 1145 | .array_access => return arrayAccess(gz, scope, ri, node), | 1145 | .array_access => return arrayAccess(gz, scope, ri, node), |
| 1146 | .@"comptime" => return comptimeExprAst(gz, scope, ri, node), | 1146 | .@"comptime" => return comptimeExprAst(gz, scope, ri, node), |
| 1147 | .@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node), | 1147 | .@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node, tree.fullSwitch(node).?), |
| 1148 | 1148 | ||
| 1149 | .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node), | 1149 | .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node), |
| 1150 | .@"suspend" => return suspendExpr(gz, scope, node), | 1150 | .@"suspend" => return suspendExpr(gz, scope, node), |
| ... | @@ -2160,6 +2160,11 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn | ... | @@ -2160,6 +2160,11 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn |
| 2160 | if (break_label != 0) { | 2160 | if (break_label != 0) { |
| 2161 | if (block_gz.label) |*label| { | 2161 | if (block_gz.label) |*label| { |
| 2162 | if (try astgen.tokenIdentEql(label.token, break_label)) { | 2162 | if (try astgen.tokenIdentEql(label.token, break_label)) { |
| 2163 | const maybe_switch_tag = astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)]; | ||
| 2164 | switch (maybe_switch_tag) { | ||
| 2165 | .switch_block, .switch_block_ref => return astgen.failNode(node, "cannot break from switch", .{}), | ||
| 2166 | else => {}, | ||
| 2167 | } | ||
| 2163 | label.used = true; | 2168 | label.used = true; |
| 2164 | break :blk label.block_inst; | 2169 | break :blk label.block_inst; |
| 2165 | } | 2170 | } |
| ... | @@ -2234,6 +2239,11 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) | ... | @@ -2234,6 +2239,11 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) |
| 2234 | const tree = astgen.tree; | 2239 | const tree = astgen.tree; |
| 2235 | const node_datas = tree.nodes.items(.data); | 2240 | const node_datas = tree.nodes.items(.data); |
| 2236 | const break_label = node_datas[node].lhs; | 2241 | const break_label = node_datas[node].lhs; |
| 2242 | const rhs = node_datas[node].rhs; | ||
| 2243 | |||
| 2244 | if (break_label == 0 and rhs != 0) { | ||
| 2245 | return astgen.failNode(node, "cannot continue with operand without label", .{}); | ||
| 2246 | } | ||
| 2237 | 2247 | ||
| 2238 | // Look for the label in the scope. | 2248 | // Look for the label in the scope. |
| 2239 | var scope = parent_scope; | 2249 | var scope = parent_scope; |
| ... | @@ -2258,6 +2268,15 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) | ... | @@ -2258,6 +2268,15 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) |
| 2258 | if (break_label != 0) blk: { | 2268 | if (break_label != 0) blk: { |
| 2259 | if (gen_zir.label) |*label| { | 2269 | if (gen_zir.label) |*label| { |
| 2260 | if (try astgen.tokenIdentEql(label.token, break_label)) { | 2270 | if (try astgen.tokenIdentEql(label.token, break_label)) { |
| 2271 | const maybe_switch_tag = astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)]; | ||
| 2272 | if (rhs != 0) switch (maybe_switch_tag) { | ||
| 2273 | .switch_block, .switch_block_ref => {}, | ||
| 2274 | else => return astgen.failNode(node, "cannot continue loop with operand", .{}), | ||
| 2275 | } else switch (maybe_switch_tag) { | ||
| 2276 | .switch_block, .switch_block_ref => return astgen.failNode(node, "cannot continue switch without operand", .{}), | ||
| 2277 | else => {}, | ||
| 2278 | } | ||
| 2279 | |||
| 2261 | label.used = true; | 2280 | label.used = true; |
| 2262 | break :blk; | 2281 | break :blk; |
| 2263 | } | 2282 | } |
| ... | @@ -2265,8 +2284,35 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) | ... | @@ -2265,8 +2284,35 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) |
| 2265 | // found continue but either it has a different label, or no label | 2284 | // found continue but either it has a different label, or no label |
| 2266 | scope = gen_zir.parent; | 2285 | scope = gen_zir.parent; |
| 2267 | continue; | 2286 | continue; |
| 2287 | } else if (gen_zir.label) |label| { | ||
| 2288 | // This `continue` is unlabeled. If the gz we've found corresponds to a labeled | ||
| 2289 | // `switch`, ignore it and continue to parent scopes. | ||
| 2290 | switch (astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)]) { | ||
| 2291 | .switch_block, .switch_block_ref => { | ||
| 2292 | scope = gen_zir.parent; | ||
| 2293 | continue; | ||
| 2294 | }, | ||
| 2295 | else => {}, | ||
| 2296 | } | ||
| 2297 | } | ||
| 2298 | |||
| 2299 | if (rhs != 0) { | ||
| 2300 | // We need to figure out the result info to use. | ||
| 2301 | // The type should match | ||
| 2302 | const operand = try reachableExpr(parent_gz, parent_scope, gen_zir.continue_result_info, rhs, node); | ||
| 2303 | |||
| 2304 | try genDefers(parent_gz, scope, parent_scope, .normal_only); | ||
| 2305 | |||
| 2306 | // As our last action before the continue, "pop" the error trace if needed | ||
| 2307 | if (!gen_zir.is_comptime) | ||
| 2308 | _ = try parent_gz.addRestoreErrRetIndex(.{ .block = continue_block }, .always, node); | ||
| 2309 | |||
| 2310 | _ = try parent_gz.addBreakWithSrcNode(.switch_continue, continue_block, operand, rhs); | ||
| 2311 | return Zir.Inst.Ref.unreachable_value; | ||
| 2268 | } | 2312 | } |
| 2269 | 2313 | ||
| 2314 | try genDefers(parent_gz, scope, parent_scope, .normal_only); | ||
| 2315 | |||
| 2270 | const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline) | 2316 | const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline) |
| 2271 | .break_inline | 2317 | .break_inline |
| 2272 | else | 2318 | else |
| ... | @@ -2284,12 +2330,7 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) | ... | @@ -2284,12 +2330,7 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) |
| 2284 | }, | 2330 | }, |
| 2285 | .local_val => scope = scope.cast(Scope.LocalVal).?.parent, | 2331 | .local_val => scope = scope.cast(Scope.LocalVal).?.parent, |
| 2286 | .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, | 2332 | .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, |
| 2287 | .defer_normal => { | 2333 | .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent, |
| 2288 | const defer_scope = scope.cast(Scope.Defer).?; | ||
| 2289 | scope = defer_scope.parent; | ||
| 2290 | try parent_gz.addDefer(defer_scope.index, defer_scope.len); | ||
| 2291 | }, | ||
| 2292 | .defer_error => scope = scope.cast(Scope.Defer).?.parent, | ||
| 2293 | .namespace => break, | 2334 | .namespace => break, |
| 2294 | .top => unreachable, | 2335 | .top => unreachable, |
| 2295 | } | 2336 | } |
| ... | @@ -2881,6 +2922,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As | ... | @@ -2881,6 +2922,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As |
| 2881 | .panic, | 2922 | .panic, |
| 2882 | .trap, | 2923 | .trap, |
| 2883 | .check_comptime_control_flow, | 2924 | .check_comptime_control_flow, |
| 2925 | .switch_continue, | ||
| 2884 | => { | 2926 | => { |
| 2885 | noreturn_src_node = statement; | 2927 | noreturn_src_node = statement; |
| 2886 | break :b true; | 2928 | break :b true; |
| ... | @@ -7546,7 +7588,8 @@ fn switchExpr( | ... | @@ -7546,7 +7588,8 @@ fn switchExpr( |
| 7546 | parent_gz: *GenZir, | 7588 | parent_gz: *GenZir, |
| 7547 | scope: *Scope, | 7589 | scope: *Scope, |
| 7548 | ri: ResultInfo, | 7590 | ri: ResultInfo, |
| 7549 | switch_node: Ast.Node.Index, | 7591 | node: Ast.Node.Index, |
| 7592 | switch_full: Ast.full.Switch, | ||
| 7550 | ) InnerError!Zir.Inst.Ref { | 7593 | ) InnerError!Zir.Inst.Ref { |
| 7551 | const astgen = parent_gz.astgen; | 7594 | const astgen = parent_gz.astgen; |
| 7552 | const gpa = astgen.gpa; | 7595 | const gpa = astgen.gpa; |
| ... | @@ -7555,14 +7598,14 @@ fn switchExpr( | ... | @@ -7555,14 +7598,14 @@ fn switchExpr( |
| 7555 | const node_tags = tree.nodes.items(.tag); | 7598 | const node_tags = tree.nodes.items(.tag); |
| 7556 | const main_tokens = tree.nodes.items(.main_token); | 7599 | const main_tokens = tree.nodes.items(.main_token); |
| 7557 | const token_tags = tree.tokens.items(.tag); | 7600 | const token_tags = tree.tokens.items(.tag); |
| 7558 | const operand_node = node_datas[switch_node].lhs; | 7601 | const operand_node = node_datas[node].lhs; |
| 7559 | const extra = tree.extraData(node_datas[switch_node].rhs, Ast.Node.SubRange); | 7602 | const extra = tree.extraData(node_datas[node].rhs, Ast.Node.SubRange); |
| 7560 | const case_nodes = tree.extra_data[extra.start..extra.end]; | 7603 | const case_nodes = tree.extra_data[extra.start..extra.end]; |
| 7561 | 7604 | ||
| 7562 | const need_rl = astgen.nodes_need_rl.contains(switch_node); | 7605 | const need_rl = astgen.nodes_need_rl.contains(node); |
| 7563 | const block_ri: ResultInfo = if (need_rl) ri else .{ | 7606 | const block_ri: ResultInfo = if (need_rl) ri else .{ |
| 7564 | .rl = switch (ri.rl) { | 7607 | .rl = switch (ri.rl) { |
| 7565 | .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, switch_node)).? }, | 7608 | .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? }, |
| 7566 | .inferred_ptr => .none, | 7609 | .inferred_ptr => .none, |
| 7567 | else => ri.rl, | 7610 | else => ri.rl, |
| 7568 | }, | 7611 | }, |
| ... | @@ -7573,11 +7616,16 @@ fn switchExpr( | ... | @@ -7573,11 +7616,16 @@ fn switchExpr( |
| 7573 | const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?; | 7616 | const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?; |
| 7574 | const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl); | 7617 | const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl); |
| 7575 | 7618 | ||
| 7619 | if (switch_full.label_token) |label_token| { | ||
| 7620 | try astgen.checkLabelRedefinition(scope, label_token); | ||
| 7621 | } | ||
| 7622 | |||
| 7576 | // We perform two passes over the AST. This first pass is to collect information | 7623 | // We perform two passes over the AST. This first pass is to collect information |
| 7577 | // for the following variables, make note of the special prong AST node index, | 7624 | // for the following variables, make note of the special prong AST node index, |
| 7578 | // and bail out with a compile error if there are multiple special prongs present. | 7625 | // and bail out with a compile error if there are multiple special prongs present. |
| 7579 | var any_payload_is_ref = false; | 7626 | var any_payload_is_ref = false; |
| 7580 | var any_has_tag_capture = false; | 7627 | var any_has_tag_capture = false; |
| 7628 | var any_non_inline_capture = false; | ||
| 7581 | var scalar_cases_len: u32 = 0; | 7629 | var scalar_cases_len: u32 = 0; |
| 7582 | var multi_cases_len: u32 = 0; | 7630 | var multi_cases_len: u32 = 0; |
| 7583 | var inline_cases_len: u32 = 0; | 7631 | var inline_cases_len: u32 = 0; |
| ... | @@ -7595,6 +7643,15 @@ fn switchExpr( | ... | @@ -7595,6 +7643,15 @@ fn switchExpr( |
| 7595 | if (token_tags[ident + 1] == .comma) { | 7643 | if (token_tags[ident + 1] == .comma) { |
| 7596 | any_has_tag_capture = true; | 7644 | any_has_tag_capture = true; |
| 7597 | } | 7645 | } |
| 7646 | |||
| 7647 | // If the first capture is ignored, then there is no runtime-known | ||
| 7648 | // capture, as the tag capture must be for an inline prong. | ||
| 7649 | // This check isn't perfect, because for things like enums, the | ||
| 7650 | // first prong *is* comptime-known for inline prongs! But such | ||
| 7651 | // knowledge requires semantic analysis. | ||
| 7652 | if (!mem.eql(u8, tree.tokenSlice(ident), "_")) { | ||
| 7653 | any_non_inline_capture = true; | ||
| 7654 | } | ||
| 7598 | } | 7655 | } |
| 7599 | // Check for else/`_` prong. | 7656 | // Check for else/`_` prong. |
| 7600 | if (case.ast.values.len == 0) { | 7657 | if (case.ast.values.len == 0) { |
| ... | @@ -7614,7 +7671,7 @@ fn switchExpr( | ... | @@ -7614,7 +7671,7 @@ fn switchExpr( |
| 7614 | ); | 7671 | ); |
| 7615 | } else if (underscore_src) |some_underscore| { | 7672 | } else if (underscore_src) |some_underscore| { |
| 7616 | return astgen.failNodeNotes( | 7673 | return astgen.failNodeNotes( |
| 7617 | switch_node, | 7674 | node, |
| 7618 | "else and '_' prong in switch expression", | 7675 | "else and '_' prong in switch expression", |
| 7619 | .{}, | 7676 | .{}, |
| 7620 | &[_]u32{ | 7677 | &[_]u32{ |
| ... | @@ -7655,7 +7712,7 @@ fn switchExpr( | ... | @@ -7655,7 +7712,7 @@ fn switchExpr( |
| 7655 | ); | 7712 | ); |
| 7656 | } else if (else_src) |some_else| { | 7713 | } else if (else_src) |some_else| { |
| 7657 | return astgen.failNodeNotes( | 7714 | return astgen.failNodeNotes( |
| 7658 | switch_node, | 7715 | node, |
| 7659 | "else and '_' prong in switch expression", | 7716 | "else and '_' prong in switch expression", |
| 7660 | .{}, | 7717 | .{}, |
| 7661 | &[_]u32{ | 7718 | &[_]u32{ |
| ... | @@ -7704,6 +7761,12 @@ fn switchExpr( | ... | @@ -7704,6 +7761,12 @@ fn switchExpr( |
| 7704 | const raw_operand = try expr(parent_gz, scope, operand_ri, operand_node); | 7761 | const raw_operand = try expr(parent_gz, scope, operand_ri, operand_node); |
| 7705 | const item_ri: ResultInfo = .{ .rl = .none }; | 7762 | const item_ri: ResultInfo = .{ .rl = .none }; |
| 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 | // This contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti, | 7770 | // This contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti, |
| 7708 | // except the first cases_nodes.len slots are a table that indexes payloads later in the array, with | 7771 | // except the first cases_nodes.len slots are a table that indexes payloads later in the array, with |
| 7709 | // the special case index coming first, then scalar_case_len indexes, then multi_cases_len indexes | 7772 | // the special case index coming first, then scalar_case_len indexes, then multi_cases_len indexes |
| ... | @@ -7725,7 +7788,22 @@ fn switchExpr( | ... | @@ -7725,7 +7788,22 @@ fn switchExpr( |
| 7725 | try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc); | 7788 | try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc); |
| 7726 | // This gets added to the parent block later, after the item expressions. | 7789 | // This gets added to the parent block later, after the item expressions. |
| 7727 | const switch_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_block_ref else .switch_block; | 7790 | const switch_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_block_ref else .switch_block; |
| 7728 | const switch_block = try parent_gz.makeBlockInst(switch_tag, switch_node); | 7791 | const switch_block = try parent_gz.makeBlockInst(switch_tag, node); |
| 7792 | |||
| 7793 | if (switch_full.label_token) |label_token| { | ||
| 7794 | block_scope.continue_block = switch_block.toOptional(); | ||
| 7795 | block_scope.continue_result_info = .{ | ||
| 7796 | .rl = if (any_payload_is_ref) | ||
| 7797 | .{ .ref_coerced_ty = raw_operand_ty_ref } | ||
| 7798 | else | ||
| 7799 | .{ .coerced_ty = raw_operand_ty_ref }, | ||
| 7800 | }; | ||
| 7801 | |||
| 7802 | block_scope.label = .{ | ||
| 7803 | .token = label_token, | ||
| 7804 | .block_inst = switch_block, | ||
| 7805 | }; | ||
| 7806 | } | ||
| 7729 | 7807 | ||
| 7730 | // We re-use this same scope for all cases, including the special prong, if any. | 7808 | // We re-use this same scope for all cases, including the special prong, if any. |
| 7731 | var case_scope = parent_gz.makeSubBlock(&block_scope.base); | 7809 | var case_scope = parent_gz.makeSubBlock(&block_scope.base); |
| ... | @@ -7946,6 +8024,8 @@ fn switchExpr( | ... | @@ -7946,6 +8024,8 @@ fn switchExpr( |
| 7946 | .has_else = special_prong == .@"else", | 8024 | .has_else = special_prong == .@"else", |
| 7947 | .has_under = special_prong == .under, | 8025 | .has_under = special_prong == .under, |
| 7948 | .any_has_tag_capture = any_has_tag_capture, | 8026 | .any_has_tag_capture = any_has_tag_capture, |
| 8027 | .any_non_inline_capture = any_non_inline_capture, | ||
| 8028 | .has_continue = switch_full.label_token != null, | ||
| 7949 | .scalar_cases_len = @intCast(scalar_cases_len), | 8029 | .scalar_cases_len = @intCast(scalar_cases_len), |
| 7950 | }, | 8030 | }, |
| 7951 | }); | 8031 | }); |
| ... | @@ -7982,7 +8062,7 @@ fn switchExpr( | ... | @@ -7982,7 +8062,7 @@ fn switchExpr( |
| 7982 | } | 8062 | } |
| 7983 | 8063 | ||
| 7984 | if (need_result_rvalue) { | 8064 | if (need_result_rvalue) { |
| 7985 | return rvalue(parent_gz, ri, switch_block.toRef(), switch_node); | 8065 | return rvalue(parent_gz, ri, switch_block.toRef(), node); |
| 7986 | } else { | 8066 | } else { |
| 7987 | return switch_block.toRef(); | 8067 | return switch_block.toRef(); |
| 7988 | } | 8068 | } |
| ... | @@ -11824,6 +11904,7 @@ const GenZir = struct { | ... | @@ -11824,6 +11904,7 @@ const GenZir = struct { |
| 11824 | continue_block: Zir.Inst.OptionalIndex = .none, | 11904 | continue_block: Zir.Inst.OptionalIndex = .none, |
| 11825 | /// Only valid when setBreakResultInfo is called. | 11905 | /// Only valid when setBreakResultInfo is called. |
| 11826 | break_result_info: AstGen.ResultInfo = undefined, | 11906 | break_result_info: AstGen.ResultInfo = undefined, |
| 11907 | continue_result_info: AstGen.ResultInfo = undefined, | ||
| 11827 | 11908 | ||
| 11828 | suspend_node: Ast.Node.Index = 0, | 11909 | suspend_node: Ast.Node.Index = 0, |
| 11829 | nosuspend_node: Ast.Node.Index = 0, | 11910 | nosuspend_node: Ast.Node.Index = 0, |
lib/std/zig/Parse.zig+20-6| ... | @@ -924,7 +924,6 @@ fn expectContainerField(p: *Parse) !Node.Index { | ... | @@ -924,7 +924,6 @@ fn expectContainerField(p: *Parse) !Node.Index { |
| 924 | /// / KEYWORD_errdefer Payload? BlockExprStatement | 924 | /// / KEYWORD_errdefer Payload? BlockExprStatement |
| 925 | /// / IfStatement | 925 | /// / IfStatement |
| 926 | /// / LabeledStatement | 926 | /// / LabeledStatement |
| 927 | /// / SwitchExpr | ||
| 928 | /// / VarDeclExprStatement | 927 | /// / VarDeclExprStatement |
| 929 | fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index { | 928 | fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index { |
| 930 | if (p.eatToken(.keyword_comptime)) |comptime_token| { | 929 | if (p.eatToken(.keyword_comptime)) |comptime_token| { |
| ... | @@ -995,7 +994,6 @@ fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index { | ... | @@ -995,7 +994,6 @@ fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index { |
| 995 | .rhs = try p.expectBlockExprStatement(), | 994 | .rhs = try p.expectBlockExprStatement(), |
| 996 | }, | 995 | }, |
| 997 | }), | 996 | }), |
| 998 | .keyword_switch => return p.expectSwitchExpr(), | ||
| 999 | .keyword_if => return p.expectIfStatement(), | 997 | .keyword_if => return p.expectIfStatement(), |
| 1000 | .keyword_enum, .keyword_struct, .keyword_union => { | 998 | .keyword_enum, .keyword_struct, .keyword_union => { |
| 1001 | const identifier = p.tok_i + 1; | 999 | const identifier = p.tok_i + 1; |
| ... | @@ -1238,7 +1236,7 @@ fn expectIfStatement(p: *Parse) !Node.Index { | ... | @@ -1238,7 +1236,7 @@ fn expectIfStatement(p: *Parse) !Node.Index { |
| 1238 | }); | 1236 | }); |
| 1239 | } | 1237 | } |
| 1240 | 1238 | ||
| 1241 | /// LabeledStatement <- BlockLabel? (Block / LoopStatement) | 1239 | /// LabeledStatement <- BlockLabel? (Block / LoopStatement / SwitchExpr) |
| 1242 | fn parseLabeledStatement(p: *Parse) !Node.Index { | 1240 | fn parseLabeledStatement(p: *Parse) !Node.Index { |
| 1243 | const label_token = p.parseBlockLabel(); | 1241 | const label_token = p.parseBlockLabel(); |
| 1244 | const block = try p.parseBlock(); | 1242 | const block = try p.parseBlock(); |
| ... | @@ -1247,6 +1245,9 @@ fn parseLabeledStatement(p: *Parse) !Node.Index { | ... | @@ -1247,6 +1245,9 @@ fn parseLabeledStatement(p: *Parse) !Node.Index { |
| 1247 | const loop_stmt = try p.parseLoopStatement(); | 1245 | const loop_stmt = try p.parseLoopStatement(); |
| 1248 | if (loop_stmt != 0) return loop_stmt; | 1246 | if (loop_stmt != 0) return loop_stmt; |
| 1249 | 1247 | ||
| 1248 | const switch_expr = try p.parseSwitchExpr(); | ||
| 1249 | if (switch_expr != 0) return switch_expr; | ||
| 1250 | |||
| 1250 | if (label_token != 0) { | 1251 | if (label_token != 0) { |
| 1251 | const after_colon = p.tok_i; | 1252 | const after_colon = p.tok_i; |
| 1252 | const node = try p.parseTypeExpr(); | 1253 | const node = try p.parseTypeExpr(); |
| ... | @@ -2072,7 +2073,7 @@ fn expectTypeExpr(p: *Parse) Error!Node.Index { | ... | @@ -2072,7 +2073,7 @@ fn expectTypeExpr(p: *Parse) Error!Node.Index { |
| 2072 | /// / KEYWORD_break BreakLabel? Expr? | 2073 | /// / KEYWORD_break BreakLabel? Expr? |
| 2073 | /// / KEYWORD_comptime Expr | 2074 | /// / KEYWORD_comptime Expr |
| 2074 | /// / KEYWORD_nosuspend Expr | 2075 | /// / KEYWORD_nosuspend Expr |
| 2075 | /// / KEYWORD_continue BreakLabel? | 2076 | /// / KEYWORD_continue BreakLabel? Expr? |
| 2076 | /// / KEYWORD_resume Expr | 2077 | /// / KEYWORD_resume Expr |
| 2077 | /// / KEYWORD_return Expr? | 2078 | /// / KEYWORD_return Expr? |
| 2078 | /// / BlockLabel? LoopExpr | 2079 | /// / BlockLabel? LoopExpr |
| ... | @@ -2098,7 +2099,7 @@ fn parsePrimaryExpr(p: *Parse) !Node.Index { | ... | @@ -2098,7 +2099,7 @@ fn parsePrimaryExpr(p: *Parse) !Node.Index { |
| 2098 | .main_token = p.nextToken(), | 2099 | .main_token = p.nextToken(), |
| 2099 | .data = .{ | 2100 | .data = .{ |
| 2100 | .lhs = try p.parseBreakLabel(), | 2101 | .lhs = try p.parseBreakLabel(), |
| 2101 | .rhs = undefined, | 2102 | .rhs = try p.parseExpr(), |
| 2102 | }, | 2103 | }, |
| 2103 | }); | 2104 | }); |
| 2104 | }, | 2105 | }, |
| ... | @@ -2627,7 +2628,6 @@ fn parseSuffixExpr(p: *Parse) !Node.Index { | ... | @@ -2627,7 +2628,6 @@ fn parseSuffixExpr(p: *Parse) !Node.Index { |
| 2627 | /// / KEYWORD_anyframe | 2628 | /// / KEYWORD_anyframe |
| 2628 | /// / KEYWORD_unreachable | 2629 | /// / KEYWORD_unreachable |
| 2629 | /// / STRINGLITERAL | 2630 | /// / STRINGLITERAL |
| 2630 | /// / SwitchExpr | ||
| 2631 | /// | 2631 | /// |
| 2632 | /// ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto | 2632 | /// ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto |
| 2633 | /// | 2633 | /// |
| ... | @@ -2647,6 +2647,7 @@ fn parseSuffixExpr(p: *Parse) !Node.Index { | ... | @@ -2647,6 +2647,7 @@ fn parseSuffixExpr(p: *Parse) !Node.Index { |
| 2647 | /// LabeledTypeExpr | 2647 | /// LabeledTypeExpr |
| 2648 | /// <- BlockLabel Block | 2648 | /// <- BlockLabel Block |
| 2649 | /// / BlockLabel? LoopTypeExpr | 2649 | /// / BlockLabel? LoopTypeExpr |
| 2650 | /// / BlockLabel? SwitchExpr | ||
| 2650 | /// | 2651 | /// |
| 2651 | /// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr) | 2652 | /// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr) |
| 2652 | fn parsePrimaryTypeExpr(p: *Parse) !Node.Index { | 2653 | fn parsePrimaryTypeExpr(p: *Parse) !Node.Index { |
| ... | @@ -2753,6 +2754,10 @@ fn parsePrimaryTypeExpr(p: *Parse) !Node.Index { | ... | @@ -2753,6 +2754,10 @@ fn parsePrimaryTypeExpr(p: *Parse) !Node.Index { |
| 2753 | p.tok_i += 2; | 2754 | p.tok_i += 2; |
| 2754 | return p.parseWhileTypeExpr(); | 2755 | return p.parseWhileTypeExpr(); |
| 2755 | }, | 2756 | }, |
| 2757 | .keyword_switch => { | ||
| 2758 | p.tok_i += 2; | ||
| 2759 | return p.expectSwitchExpr(); | ||
| 2760 | }, | ||
| 2756 | .l_brace => { | 2761 | .l_brace => { |
| 2757 | p.tok_i += 2; | 2762 | p.tok_i += 2; |
| 2758 | return p.parseBlock(); | 2763 | return p.parseBlock(); |
| ... | @@ -3029,8 +3034,17 @@ fn parseWhileTypeExpr(p: *Parse) !Node.Index { | ... | @@ -3029,8 +3034,17 @@ fn parseWhileTypeExpr(p: *Parse) !Node.Index { |
| 3029 | } | 3034 | } |
| 3030 | 3035 | ||
| 3031 | /// SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE | 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 | fn expectSwitchExpr(p: *Parse) !Node.Index { | 3042 | fn expectSwitchExpr(p: *Parse) !Node.Index { |
| 3033 | const switch_token = p.assertToken(.keyword_switch); | 3043 | const switch_token = p.assertToken(.keyword_switch); |
| 3044 | return p.expectSwitchSuffix(switch_token); | ||
| 3045 | } | ||
| 3046 | |||
| 3047 | fn expectSwitchSuffix(p: *Parse, switch_token: TokenIndex) !Node.Index { | ||
| 3034 | _ = try p.expectToken(.l_paren); | 3048 | _ = try p.expectToken(.l_paren); |
| 3035 | const expr_node = try p.expectExpr(); | 3049 | const expr_node = try p.expectExpr(); |
| 3036 | _ = try p.expectToken(.r_paren); | 3050 | _ = try p.expectToken(.r_paren); |
lib/std/zig/Zir.zig+13-1| ... | @@ -314,6 +314,9 @@ pub const Inst = struct { | ... | @@ -314,6 +314,9 @@ pub const Inst = struct { |
| 314 | /// break instruction in a block, and the target block is the parent. | 314 | /// break instruction in a block, and the target block is the parent. |
| 315 | /// Uses the `break` union field. | 315 | /// Uses the `break` union field. |
| 316 | break_inline, | 316 | break_inline, |
| 317 | /// Branch from within a switch case to the case specified by the operand. | ||
| 318 | /// Uses the `break` union field. `block_inst` refers to a `switch_block` or `switch_block_ref`. | ||
| 319 | switch_continue, | ||
| 317 | /// Checks that comptime control flow does not happen inside a runtime block. | 320 | /// Checks that comptime control flow does not happen inside a runtime block. |
| 318 | /// Uses the `un_node` union field. | 321 | /// Uses the `un_node` union field. |
| 319 | check_comptime_control_flow, | 322 | check_comptime_control_flow, |
| ... | @@ -1273,6 +1276,7 @@ pub const Inst = struct { | ... | @@ -1273,6 +1276,7 @@ pub const Inst = struct { |
| 1273 | .panic, | 1276 | .panic, |
| 1274 | .trap, | 1277 | .trap, |
| 1275 | .check_comptime_control_flow, | 1278 | .check_comptime_control_flow, |
| 1279 | .switch_continue, | ||
| 1276 | => true, | 1280 | => true, |
| 1277 | }; | 1281 | }; |
| 1278 | } | 1282 | } |
| ... | @@ -1512,6 +1516,7 @@ pub const Inst = struct { | ... | @@ -1512,6 +1516,7 @@ pub const Inst = struct { |
| 1512 | .break_inline, | 1516 | .break_inline, |
| 1513 | .condbr, | 1517 | .condbr, |
| 1514 | .condbr_inline, | 1518 | .condbr_inline, |
| 1519 | .switch_continue, | ||
| 1515 | .compile_error, | 1520 | .compile_error, |
| 1516 | .ret_node, | 1521 | .ret_node, |
| 1517 | .ret_load, | 1522 | .ret_load, |
| ... | @@ -1597,6 +1602,7 @@ pub const Inst = struct { | ... | @@ -1597,6 +1602,7 @@ pub const Inst = struct { |
| 1597 | .bool_br_or = .pl_node, | 1602 | .bool_br_or = .pl_node, |
| 1598 | .@"break" = .@"break", | 1603 | .@"break" = .@"break", |
| 1599 | .break_inline = .@"break", | 1604 | .break_inline = .@"break", |
| 1605 | .switch_continue = .@"break", | ||
| 1600 | .check_comptime_control_flow = .un_node, | 1606 | .check_comptime_control_flow = .un_node, |
| 1601 | .for_len = .pl_node, | 1607 | .for_len = .pl_node, |
| 1602 | .call = .pl_node, | 1608 | .call = .pl_node, |
| ... | @@ -2288,6 +2294,7 @@ pub const Inst = struct { | ... | @@ -2288,6 +2294,7 @@ pub const Inst = struct { |
| 2288 | }, | 2294 | }, |
| 2289 | @"break": struct { | 2295 | @"break": struct { |
| 2290 | operand: Ref, | 2296 | operand: Ref, |
| 2297 | /// Index of a `Break` payload. | ||
| 2291 | payload_index: u32, | 2298 | payload_index: u32, |
| 2292 | }, | 2299 | }, |
| 2293 | dbg_stmt: LineColumn, | 2300 | dbg_stmt: LineColumn, |
| ... | @@ -2945,9 +2952,13 @@ pub const Inst = struct { | ... | @@ -2945,9 +2952,13 @@ pub const Inst = struct { |
| 2945 | has_under: bool, | 2952 | has_under: bool, |
| 2946 | /// If true, at least one prong has an inline tag capture. | 2953 | /// If true, at least one prong has an inline tag capture. |
| 2947 | any_has_tag_capture: bool, | 2954 | any_has_tag_capture: bool, |
| 2955 | /// If true, at least one prong has a capture which may not | ||
| 2956 | /// be comptime-known via `inline`. | ||
| 2957 | any_non_inline_capture: bool, | ||
| 2958 | has_continue: bool, | ||
| 2948 | scalar_cases_len: ScalarCasesLen, | 2959 | scalar_cases_len: ScalarCasesLen, |
| 2949 | 2960 | ||
| 2950 | pub const ScalarCasesLen = u28; | 2961 | pub const ScalarCasesLen = u26; |
| 2951 | 2962 | ||
| 2952 | pub fn specialProng(bits: Bits) SpecialProng { | 2963 | pub fn specialProng(bits: Bits) SpecialProng { |
| 2953 | const has_else: u2 = @intFromBool(bits.has_else); | 2964 | const has_else: u2 = @intFromBool(bits.has_else); |
| ... | @@ -3750,6 +3761,7 @@ fn findDeclsInner( | ... | @@ -3750,6 +3761,7 @@ fn findDeclsInner( |
| 3750 | .bool_br_or, | 3761 | .bool_br_or, |
| 3751 | .@"break", | 3762 | .@"break", |
| 3752 | .break_inline, | 3763 | .break_inline, |
| 3764 | .switch_continue, | ||
| 3753 | .check_comptime_control_flow, | 3765 | .check_comptime_control_flow, |
| 3754 | .builtin_call, | 3766 | .builtin_call, |
| 3755 | .cmp_lt, | 3767 | .cmp_lt, |
src/Air.zig+16-1| ... | @@ -429,6 +429,14 @@ pub const Inst = struct { | ... | @@ -429,6 +429,14 @@ pub const Inst = struct { |
| 429 | /// Result type is always noreturn; no instructions in a block follow this one. | 429 | /// Result type is always noreturn; no instructions in a block follow this one. |
| 430 | /// Uses the `pl_op` field. Operand is the condition. Payload is `SwitchBr`. | 430 | /// Uses the `pl_op` field. Operand is the condition. Payload is `SwitchBr`. |
| 431 | switch_br, | 431 | switch_br, |
| 432 | /// Switch branch which can dispatch back to itself with a different operand. | ||
| 433 | /// Result type is always noreturn; no instructions in a block follow this one. | ||
| 434 | /// Uses the `pl_op` field. Operand is the condition. Payload is `SwitchBr`. | ||
| 435 | loop_switch_br, | ||
| 436 | /// Dispatches back to a branch of a parent `loop_switch_br`. | ||
| 437 | /// Result type is always noreturn; no instructions in a block follow this one. | ||
| 438 | /// Uses the `br` field. `block_inst` is a `loop_switch_br` instruction. | ||
| 439 | switch_dispatch, | ||
| 432 | /// Given an operand which is an error union, splits control flow. In | 440 | /// Given an operand which is an error union, splits control flow. In |
| 433 | /// case of error, control flow goes into the block that is part of this | 441 | /// case of error, control flow goes into the block that is part of this |
| 434 | /// instruction, which is guaranteed to end with a return instruction | 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,6 +1462,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool) |
| 1454 | .br, | 1462 | .br, |
| 1455 | .cond_br, | 1463 | .cond_br, |
| 1456 | .switch_br, | 1464 | .switch_br, |
| 1465 | .loop_switch_br, | ||
| 1466 | .switch_dispatch, | ||
| 1457 | .ret, | 1467 | .ret, |
| 1458 | .ret_safe, | 1468 | .ret_safe, |
| 1459 | .ret_load, | 1469 | .ret_load, |
| ... | @@ -1618,6 +1628,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool { | ... | @@ -1618,6 +1628,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool { |
| 1618 | .call_never_inline, | 1628 | .call_never_inline, |
| 1619 | .cond_br, | 1629 | .cond_br, |
| 1620 | .switch_br, | 1630 | .switch_br, |
| 1631 | .loop_switch_br, | ||
| 1632 | .switch_dispatch, | ||
| 1621 | .@"try", | 1633 | .@"try", |
| 1622 | .try_cold, | 1634 | .try_cold, |
| 1623 | .try_ptr, | 1635 | .try_ptr, |
| ... | @@ -1903,7 +1915,10 @@ pub const UnwrappedSwitch = struct { | ... | @@ -1903,7 +1915,10 @@ pub const UnwrappedSwitch = struct { |
| 1903 | 1915 | ||
| 1904 | pub fn unwrapSwitch(air: *const Air, switch_inst: Inst.Index) UnwrappedSwitch { | 1916 | pub fn unwrapSwitch(air: *const Air, switch_inst: Inst.Index) UnwrappedSwitch { |
| 1905 | const inst = air.instructions.get(@intFromEnum(switch_inst)); | 1917 | const inst = air.instructions.get(@intFromEnum(switch_inst)); |
| 1906 | assert(inst.tag == .switch_br); | 1918 | switch (inst.tag) { |
| 1919 | .switch_br, .loop_switch_br => {}, | ||
| 1920 | else => unreachable, // assertion failure | ||
| 1921 | } | ||
| 1907 | const pl_op = inst.data.pl_op; | 1922 | const pl_op = inst.data.pl_op; |
| 1908 | const extra = air.extraData(SwitchBr, pl_op.payload); | 1923 | const extra = air.extraData(SwitchBr, pl_op.payload); |
| 1909 | const hint_bag_count = std.math.divCeil(usize, extra.data.cases_len + 1, 10) catch unreachable; | 1924 | const hint_bag_count = std.math.divCeil(usize, extra.data.cases_len + 1, 10) catch unreachable; |
src/Air/types_resolved.zig+2-2| ... | @@ -222,7 +222,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool { | ... | @@ -222,7 +222,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool { |
| 222 | if (!checkRef(data.un_op, zcu)) return false; | 222 | if (!checkRef(data.un_op, zcu)) return false; |
| 223 | }, | 223 | }, |
| 224 | 224 | ||
| 225 | .br => { | 225 | .br, .switch_dispatch => { |
| 226 | if (!checkRef(data.br.operand, zcu)) return false; | 226 | if (!checkRef(data.br.operand, zcu)) return false; |
| 227 | }, | 227 | }, |
| 228 | 228 | ||
| ... | @@ -380,7 +380,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool { | ... | @@ -380,7 +380,7 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool { |
| 380 | )) return false; | 380 | )) return false; |
| 381 | }, | 381 | }, |
| 382 | 382 | ||
| 383 | .switch_br => { | 383 | .switch_br, .loop_switch_br => { |
| 384 | const switch_br = air.unwrapSwitch(inst); | 384 | const switch_br = air.unwrapSwitch(inst); |
| 385 | if (!checkRef(switch_br.operand, zcu)) return false; | 385 | if (!checkRef(switch_br.operand, zcu)) return false; |
| 386 | var it = switch_br.iterateCases(); | 386 | var it = switch_br.iterateCases(); |
src/Liveness.zig+174-93| ... | @@ -31,6 +31,7 @@ tomb_bits: []usize, | ... | @@ -31,6 +31,7 @@ tomb_bits: []usize, |
| 31 | /// * `try`, `try_ptr` - points to a `CondBr` in `extra` at this index. The error path (the block | 31 | /// * `try`, `try_ptr` - points to a `CondBr` in `extra` at this index. The error path (the block |
| 32 | /// in the instruction) is considered the "else" path, and the rest of the block the "then". | 32 | /// in the instruction) is considered the "else" path, and the rest of the block the "then". |
| 33 | /// * `switch_br` - points to a `SwitchBr` in `extra` at this index. | 33 | /// * `switch_br` - points to a `SwitchBr` in `extra` at this index. |
| 34 | /// * `loop_switch_br` - points to a `SwitchBr` in `extra` at this index. | ||
| 34 | /// * `block` - points to a `Block` in `extra` at this index. | 35 | /// * `block` - points to a `Block` in `extra` at this index. |
| 35 | /// * `asm`, `call`, `aggregate_init` - the value is a set of bits which are the extra tomb | 36 | /// * `asm`, `call`, `aggregate_init` - the value is a set of bits which are the extra tomb |
| 36 | /// bits of operands. | 37 | /// bits of operands. |
| ... | @@ -68,8 +69,8 @@ pub const Block = struct { | ... | @@ -68,8 +69,8 @@ pub const Block = struct { |
| 68 | /// Liveness analysis runs in several passes. Each pass iterates backwards over instructions in | 69 | /// Liveness analysis runs in several passes. Each pass iterates backwards over instructions in |
| 69 | /// bodies, and recurses into bodies. | 70 | /// bodies, and recurses into bodies. |
| 70 | const LivenessPass = enum { | 71 | const LivenessPass = enum { |
| 71 | /// In this pass, we perform some basic analysis of loops to gain information the main pass | 72 | /// In this pass, we perform some basic analysis of loops to gain information the main pass needs. |
| 72 | /// needs. In particular, for every `loop`, we track the following information: | 73 | /// In particular, for every `loop` and `loop_switch_br`, we track the following information: |
| 73 | /// * Every outer block which the loop body contains a `br` to. | 74 | /// * Every outer block which the loop body contains a `br` to. |
| 74 | /// * Every outer loop which the loop body contains a `repeat` to. | 75 | /// * Every outer loop which the loop body contains a `repeat` to. |
| 75 | /// * Every operand referenced within the loop body but created outside the loop. | 76 | /// * Every operand referenced within the loop body but created outside the loop. |
| ... | @@ -91,7 +92,8 @@ fn LivenessPassData(comptime pass: LivenessPass) type { | ... | @@ -91,7 +92,8 @@ fn LivenessPassData(comptime pass: LivenessPass) type { |
| 91 | .loop_analysis => struct { | 92 | .loop_analysis => struct { |
| 92 | /// The set of blocks which are exited with a `br` instruction at some point within this | 93 | /// The set of blocks which are exited with a `br` instruction at some point within this |
| 93 | /// body and which we are currently within. Also includes `loop`s which are the target | 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 | breaks: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{}, | 97 | breaks: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{}, |
| 96 | 98 | ||
| 97 | /// The set of operands for which we have seen at least one usage but not their birth. | 99 | /// The set of operands for which we have seen at least one usage but not their birth. |
| ... | @@ -330,6 +332,7 @@ pub fn categorizeOperand( | ... | @@ -330,6 +332,7 @@ pub fn categorizeOperand( |
| 330 | .trap, | 332 | .trap, |
| 331 | .breakpoint, | 333 | .breakpoint, |
| 332 | .repeat, | 334 | .repeat, |
| 335 | .switch_dispatch, | ||
| 333 | .dbg_stmt, | 336 | .dbg_stmt, |
| 334 | .unreach, | 337 | .unreach, |
| 335 | .ret_addr, | 338 | .ret_addr, |
| ... | @@ -662,21 +665,17 @@ pub fn categorizeOperand( | ... | @@ -662,21 +665,17 @@ pub fn categorizeOperand( |
| 662 | 665 | ||
| 663 | return .complex; | 666 | return .complex; |
| 664 | }, | 667 | }, |
| 665 | .@"try", .try_cold => { | 668 | |
| 666 | return .complex; | 669 | .@"try", |
| 667 | }, | 670 | .try_cold, |
| 668 | .try_ptr, .try_ptr_cold => { | 671 | .try_ptr, |
| 669 | return .complex; | 672 | .try_ptr_cold, |
| 670 | }, | 673 | .loop, |
| 671 | .loop => { | 674 | .cond_br, |
| 672 | return .complex; | 675 | .switch_br, |
| 673 | }, | 676 | .loop_switch_br, |
| 674 | .cond_br => { | 677 | => return .complex, |
| 675 | return .complex; | 678 | |
| 676 | }, | ||
| 677 | .switch_br => { | ||
| 678 | return .complex; | ||
| 679 | }, | ||
| 680 | .wasm_memory_grow => { | 679 | .wasm_memory_grow => { |
| 681 | const pl_op = air_datas[@intFromEnum(inst)].pl_op; | 680 | const pl_op = air_datas[@intFromEnum(inst)].pl_op; |
| 682 | if (pl_op.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none); | 681 | if (pl_op.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none); |
| ... | @@ -1206,6 +1205,7 @@ fn analyzeInst( | ... | @@ -1206,6 +1205,7 @@ fn analyzeInst( |
| 1206 | 1205 | ||
| 1207 | .br => return analyzeInstBr(a, pass, data, inst), | 1206 | .br => return analyzeInstBr(a, pass, data, inst), |
| 1208 | .repeat => return analyzeInstRepeat(a, pass, data, inst), | 1207 | .repeat => return analyzeInstRepeat(a, pass, data, inst), |
| 1208 | .switch_dispatch => return analyzeInstSwitchDispatch(a, pass, data, inst), | ||
| 1209 | 1209 | ||
| 1210 | .assembly => { | 1210 | .assembly => { |
| 1211 | const extra = a.air.extraData(Air.Asm, inst_datas[@intFromEnum(inst)].ty_pl.payload); | 1211 | const extra = a.air.extraData(Air.Asm, inst_datas[@intFromEnum(inst)].ty_pl.payload); |
| ... | @@ -1262,7 +1262,8 @@ fn analyzeInst( | ... | @@ -1262,7 +1262,8 @@ fn analyzeInst( |
| 1262 | .@"try", .try_cold => return analyzeInstCondBr(a, pass, data, inst, .@"try"), | 1262 | .@"try", .try_cold => return analyzeInstCondBr(a, pass, data, inst, .@"try"), |
| 1263 | .try_ptr, .try_ptr_cold => return analyzeInstCondBr(a, pass, data, inst, .try_ptr), | 1263 | .try_ptr, .try_ptr_cold => return analyzeInstCondBr(a, pass, data, inst, .try_ptr), |
| 1264 | .cond_br => return analyzeInstCondBr(a, pass, data, inst, .cond_br), | 1264 | .cond_br => return analyzeInstCondBr(a, pass, data, inst, .cond_br), |
| 1265 | .switch_br => return analyzeInstSwitchBr(a, pass, data, inst), | 1265 | .switch_br => return analyzeInstSwitchBr(a, pass, data, inst, false), |
| 1266 | .loop_switch_br => return analyzeInstSwitchBr(a, pass, data, inst, true), | ||
| 1266 | 1267 | ||
| 1267 | .wasm_memory_grow => { | 1268 | .wasm_memory_grow => { |
| 1268 | const pl_op = inst_datas[@intFromEnum(inst)].pl_op; | 1269 | const pl_op = inst_datas[@intFromEnum(inst)].pl_op; |
| ... | @@ -1412,6 +1413,35 @@ fn analyzeInstRepeat( | ... | @@ -1412,6 +1413,35 @@ fn analyzeInstRepeat( |
| 1412 | return analyzeOperands(a, pass, data, inst, .{ .none, .none, .none }); | 1413 | return analyzeOperands(a, pass, data, inst, .{ .none, .none, .none }); |
| 1413 | } | 1414 | } |
| 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 | fn analyzeInstBlock( | 1445 | fn analyzeInstBlock( |
| 1416 | a: *Analysis, | 1446 | a: *Analysis, |
| 1417 | comptime pass: LivenessPass, | 1447 | comptime pass: LivenessPass, |
| ... | @@ -1482,109 +1512,133 @@ fn analyzeInstBlock( | ... | @@ -1482,109 +1512,133 @@ fn analyzeInstBlock( |
| 1482 | } | 1512 | } |
| 1483 | } | 1513 | } |
| 1484 | 1514 | ||
| 1485 | fn analyzeInstLoop( | 1515 | fn writeLoopInfo( |
| 1486 | a: *Analysis, | 1516 | a: *Analysis, |
| 1487 | comptime pass: LivenessPass, | 1517 | data: *LivenessPassData(.loop_analysis), |
| 1488 | data: *LivenessPassData(pass), | ||
| 1489 | inst: Air.Inst.Index, | 1518 | inst: Air.Inst.Index, |
| 1519 | old_breaks: std.AutoHashMapUnmanaged(Air.Inst.Index, void), | ||
| 1520 | old_live: std.AutoHashMapUnmanaged(Air.Inst.Index, void), | ||
| 1490 | ) !void { | 1521 | ) !void { |
| 1491 | const inst_datas = a.air.instructions.items(.data); | ||
| 1492 | const extra = a.air.extraData(Air.Block, inst_datas[@intFromEnum(inst)].ty_pl.payload); | ||
| 1493 | const body: []const Air.Inst.Index = @ptrCast(a.air.extra[extra.end..][0..extra.data.body_len]); | ||
| 1494 | const gpa = a.gpa; | 1522 | const gpa = a.gpa; |
| 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) { | 1530 | const extra_index: u32 = @intCast(a.extra.items.len); |
| 1499 | .loop_analysis => { | ||
| 1500 | var old_breaks = data.breaks.move(); | ||
| 1501 | defer old_breaks.deinit(gpa); | ||
| 1502 | 1531 | ||
| 1503 | var old_live = data.live_set.move(); | 1532 | const num_breaks = data.breaks.count(); |
| 1504 | defer old_live.deinit(gpa); | 1533 | try a.extra.ensureUnusedCapacity(gpa, 1 + num_breaks); |
| 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`. | 1537 | var it = data.breaks.keyIterator(); |
| 1509 | // However, we no longer care about repeats of this loop itself. | 1538 | while (it.next()) |key| { |
| 1510 | assert(data.breaks.remove(inst)); | 1539 | const block_inst = key.*; |
| 1540 | a.extra.appendAssumeCapacity(@intFromEnum(block_inst)); | ||
| 1541 | } | ||
| 1542 | log.debug("[{}] %{}: includes breaks to {}", .{ LivenessPass.loop_analysis, inst, fmtInstSet(&data.breaks) }); | ||
| 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(); | 1548 | a.extra.appendAssumeCapacity(num_live); |
| 1515 | try a.extra.ensureUnusedCapacity(gpa, 1 + num_breaks); | 1549 | it = data.live_set.keyIterator(); |
| 1550 | while (it.next()) |key| { | ||
| 1551 | const alive = key.*; | ||
| 1552 | a.extra.appendAssumeCapacity(@intFromEnum(alive)); | ||
| 1553 | } | ||
| 1554 | log.debug("[{}] %{}: maintain liveness of {}", .{ LivenessPass.loop_analysis, inst, fmtInstSet(&data.live_set) }); | ||
| 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(); | 1558 | // Add back operands which were previously alive |
| 1520 | while (it.next()) |key| { | 1559 | it = old_live.keyIterator(); |
| 1521 | const block_inst = key.*; | 1560 | while (it.next()) |key| { |
| 1522 | a.extra.appendAssumeCapacity(@intFromEnum(block_inst)); | 1561 | const alive = key.*; |
| 1523 | } | 1562 | try data.live_set.put(gpa, alive, {}); |
| 1524 | log.debug("[{}] %{}: includes breaks to {}", .{ pass, inst, fmtInstSet(&data.breaks) }); | 1563 | } |
| 1525 | 1564 | ||
| 1526 | // Now we put the live operands from the loop body in too | 1565 | // And the same for breaks |
| 1527 | const num_live = data.live_set.count(); | 1566 | it = old_breaks.keyIterator(); |
| 1528 | try a.extra.ensureUnusedCapacity(gpa, 1 + num_live); | 1567 | while (it.next()) |key| { |
| 1568 | const block_inst = key.*; | ||
| 1569 | try data.breaks.put(gpa, block_inst, {}); | ||
| 1570 | } | ||
| 1571 | } | ||
| 1529 | 1572 | ||
| 1530 | a.extra.appendAssumeCapacity(num_live); | 1573 | /// When analyzing a loop in the main pass, sets up `data.live_set` to be the set |
| 1531 | it = data.live_set.keyIterator(); | 1574 | /// of operands known to be alive when the loop repeats. |
| 1532 | while (it.next()) |key| { | 1575 | fn resolveLoopLiveSet( |
| 1533 | const alive = key.*; | 1576 | a: *Analysis, |
| 1534 | a.extra.appendAssumeCapacity(@intFromEnum(alive)); | 1577 | data: *LivenessPassData(.main_analysis), |
| 1535 | } | 1578 | inst: Air.Inst.Index, |
| 1536 | log.debug("[{}] %{}: maintain liveness of {}", .{ pass, inst, fmtInstSet(&data.live_set) }); | 1579 | ) !void { |
| 1580 | const gpa = a.gpa; | ||
| 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 | 1586 | const num_loop_live = data.old_extra.items[extra_idx + num_breaks + 1]; |
| 1541 | it = old_live.keyIterator(); | 1587 | const loop_live: []const Air.Inst.Index = @ptrCast(data.old_extra.items[extra_idx + num_breaks + 2 ..][0..num_loop_live]); |
| 1542 | while (it.next()) |key| { | ||
| 1543 | const alive = key.*; | ||
| 1544 | try data.live_set.put(gpa, alive, {}); | ||
| 1545 | } | ||
| 1546 | 1588 | ||
| 1547 | // And the same for breaks | 1589 | // This is necessarily not in the same control flow branch, because loops are noreturn |
| 1548 | it = old_breaks.keyIterator(); | 1590 | data.live_set.clearRetainingCapacity(); |
| 1549 | while (it.next()) |key| { | ||
| 1550 | const block_inst = key.*; | ||
| 1551 | try data.breaks.put(gpa, block_inst, {}); | ||
| 1552 | } | ||
| 1553 | }, | ||
| 1554 | 1591 | ||
| 1555 | .main_analysis => { | 1592 | try data.live_set.ensureUnusedCapacity(gpa, @intCast(loop_live.len)); |
| 1556 | const extra_idx = a.special.fetchRemove(inst).?.value; // remove because this data does not exist after analysis | 1593 | for (loop_live) |alive| data.live_set.putAssumeCapacity(alive, {}); |
| 1557 | 1594 | ||
| 1558 | const num_breaks = data.old_extra.items[extra_idx]; | 1595 | log.debug("[{}] %{}: block live set is {}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) }); |
| 1559 | const breaks: []const Air.Inst.Index = @ptrCast(data.old_extra.items[extra_idx + 1 ..][0..num_breaks]); | ||
| 1560 | 1596 | ||
| 1561 | const num_loop_live = data.old_extra.items[extra_idx + num_breaks + 1]; | 1597 | for (breaks) |block_inst| { |
| 1562 | const loop_live: []const Air.Inst.Index = @ptrCast(data.old_extra.items[extra_idx + num_breaks + 2 ..][0..num_loop_live]); | 1598 | // We might break to this block, so include every operand that the block needs alive |
| 1599 | const block_scope = data.block_scopes.get(block_inst).?; | ||
| 1563 | 1600 | ||
| 1564 | // This is necessarily not in the same control flow branch, because loops are noreturn | 1601 | var it = block_scope.live_set.keyIterator(); |
| 1565 | data.live_set.clearRetainingCapacity(); | 1602 | while (it.next()) |key| { |
| 1603 | const alive = key.*; | ||
| 1604 | try data.live_set.put(gpa, alive, {}); | ||
| 1605 | } | ||
| 1606 | } | ||
| 1566 | 1607 | ||
| 1567 | try data.live_set.ensureUnusedCapacity(gpa, @intCast(loop_live.len)); | 1608 | log.debug("[{}] %{}: loop live set is {}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) }); |
| 1568 | for (loop_live) |alive| { | 1609 | } |
| 1569 | data.live_set.putAssumeCapacity(alive, {}); | ||
| 1570 | } | ||
| 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| { | 1622 | try analyzeOperands(a, pass, data, inst, .{ .none, .none, .none }); |
| 1575 | // We might break to this block, so include every operand that the block needs alive | ||
| 1576 | const block_scope = data.block_scopes.get(block_inst).?; | ||
| 1577 | 1623 | ||
| 1578 | var it = block_scope.live_set.keyIterator(); | 1624 | switch (pass) { |
| 1579 | while (it.next()) |key| { | 1625 | .loop_analysis => { |
| 1580 | const alive = key.*; | 1626 | var old_breaks = data.breaks.move(); |
| 1581 | try data.live_set.put(gpa, alive, {}); | 1627 | defer old_breaks.deinit(gpa); |
| 1582 | } | 1628 | |
| 1583 | } | 1629 | var old_live = data.live_set.move(); |
| 1630 | defer old_live.deinit(gpa); | ||
| 1631 | |||
| 1632 | try analyzeBody(a, pass, data, body); | ||
| 1633 | |||
| 1634 | try writeLoopInfo(a, data, inst, old_breaks, old_live); | ||
| 1635 | }, | ||
| 1636 | |||
| 1637 | .main_analysis => { | ||
| 1638 | try resolveLoopLiveSet(a, data, inst); | ||
| 1584 | 1639 | ||
| 1585 | // Now, `data.live_set` is the operands which must be alive when the loop repeats. | 1640 | // Now, `data.live_set` is the operands which must be alive when the loop repeats. |
| 1586 | // Move them into a block scope for corresponding `repeat` instructions to notice. | 1641 | // Move them into a block scope for corresponding `repeat` instructions to notice. |
| 1587 | log.debug("[{}] %{}: loop live set is {}", .{ pass, inst, fmtInstSet(&data.live_set) }); | ||
| 1588 | try data.block_scopes.putNoClobber(gpa, inst, .{ | 1642 | try data.block_scopes.putNoClobber(gpa, inst, .{ |
| 1589 | .live_set = data.live_set.move(), | 1643 | .live_set = data.live_set.move(), |
| 1590 | }); | 1644 | }); |
| ... | @@ -1720,6 +1774,7 @@ fn analyzeInstSwitchBr( | ... | @@ -1720,6 +1774,7 @@ fn analyzeInstSwitchBr( |
| 1720 | comptime pass: LivenessPass, | 1774 | comptime pass: LivenessPass, |
| 1721 | data: *LivenessPassData(pass), | 1775 | data: *LivenessPassData(pass), |
| 1722 | inst: Air.Inst.Index, | 1776 | inst: Air.Inst.Index, |
| 1777 | is_dispatch_loop: bool, | ||
| 1723 | ) !void { | 1778 | ) !void { |
| 1724 | const inst_datas = a.air.instructions.items(.data); | 1779 | const inst_datas = a.air.instructions.items(.data); |
| 1725 | const pl_op = inst_datas[@intFromEnum(inst)].pl_op; | 1780 | const pl_op = inst_datas[@intFromEnum(inst)].pl_op; |
| ... | @@ -1730,6 +1785,17 @@ fn analyzeInstSwitchBr( | ... | @@ -1730,6 +1785,17 @@ fn analyzeInstSwitchBr( |
| 1730 | 1785 | ||
| 1731 | switch (pass) { | 1786 | switch (pass) { |
| 1732 | .loop_analysis => { | 1787 | .loop_analysis => { |
| 1788 | var old_breaks: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{}; | ||
| 1789 | defer old_breaks.deinit(gpa); | ||
| 1790 | |||
| 1791 | var old_live: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{}; | ||
| 1792 | defer old_live.deinit(gpa); | ||
| 1793 | |||
| 1794 | if (is_dispatch_loop) { | ||
| 1795 | old_breaks = data.breaks.move(); | ||
| 1796 | old_live = data.live_set.move(); | ||
| 1797 | } | ||
| 1798 | |||
| 1733 | var it = switch_br.iterateCases(); | 1799 | var it = switch_br.iterateCases(); |
| 1734 | while (it.next()) |case| { | 1800 | while (it.next()) |case| { |
| 1735 | try analyzeBody(a, pass, data, case.body); | 1801 | try analyzeBody(a, pass, data, case.body); |
| ... | @@ -1738,9 +1804,24 @@ fn analyzeInstSwitchBr( | ... | @@ -1738,9 +1804,24 @@ fn analyzeInstSwitchBr( |
| 1738 | const else_body = it.elseBody(); | 1804 | const else_body = it.elseBody(); |
| 1739 | try analyzeBody(a, pass, data, else_body); | 1805 | try analyzeBody(a, pass, data, else_body); |
| 1740 | } | 1806 | } |
| 1807 | |||
| 1808 | if (is_dispatch_loop) { | ||
| 1809 | try writeLoopInfo(a, data, inst, old_breaks, old_live); | ||
| 1810 | } | ||
| 1741 | }, | 1811 | }, |
| 1742 | 1812 | ||
| 1743 | .main_analysis => { | 1813 | .main_analysis => { |
| 1814 | if (is_dispatch_loop) { | ||
| 1815 | try resolveLoopLiveSet(a, data, inst); | ||
| 1816 | try data.block_scopes.putNoClobber(gpa, inst, .{ | ||
| 1817 | .live_set = data.live_set.move(), | ||
| 1818 | }); | ||
| 1819 | } | ||
| 1820 | defer if (is_dispatch_loop) { | ||
| 1821 | log.debug("[{}] %{}: popped loop block scop", .{ pass, inst }); | ||
| 1822 | var scope = data.block_scopes.fetchRemove(inst).?.value; | ||
| 1823 | scope.live_set.deinit(gpa); | ||
| 1824 | }; | ||
| 1744 | // This is, all in all, just a messier version of the `cond_br` logic. If you're trying | 1825 | // This is, all in all, just a messier version of the `cond_br` logic. If you're trying |
| 1745 | // to understand it, I encourage looking at `analyzeInstCondBr` first. | 1826 | // to understand it, I encourage looking at `analyzeInstCondBr` first. |
| 1746 | 1827 |
src/Liveness/Verify.zig+25-6| ... | @@ -447,6 +447,16 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { | ... | @@ -447,6 +447,16 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { |
| 447 | 447 | ||
| 448 | try self.verifyMatchingLiveness(repeat.loop_inst, expected_live); | 448 | try self.verifyMatchingLiveness(repeat.loop_inst, expected_live); |
| 449 | }, | 449 | }, |
| 450 | .switch_dispatch => { | ||
| 451 | const br = data[@intFromEnum(inst)].br; | ||
| 452 | |||
| 453 | try self.verifyOperand(inst, br.operand, self.liveness.operandDies(inst, 0)); | ||
| 454 | |||
| 455 | const expected_live = self.loops.get(br.block_inst) orelse | ||
| 456 | return invalid("%{}: loop %{} not in scope", .{ @intFromEnum(inst), @intFromEnum(br.block_inst) }); | ||
| 457 | |||
| 458 | try self.verifyMatchingLiveness(br.block_inst, expected_live); | ||
| 459 | }, | ||
| 450 | .block, .dbg_inline_block => |tag| { | 460 | .block, .dbg_inline_block => |tag| { |
| 451 | const ty_pl = data[@intFromEnum(inst)].ty_pl; | 461 | const ty_pl = data[@intFromEnum(inst)].ty_pl; |
| 452 | const block_ty = ty_pl.ty.toType(); | 462 | const block_ty = ty_pl.ty.toType(); |
| ... | @@ -494,11 +504,11 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { | ... | @@ -494,11 +504,11 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { |
| 494 | 504 | ||
| 495 | // The same stuff should be alive after the loop as before it. | 505 | // The same stuff should be alive after the loop as before it. |
| 496 | const gop = try self.loops.getOrPut(self.gpa, inst); | 506 | const gop = try self.loops.getOrPut(self.gpa, inst); |
| 507 | if (gop.found_existing) return invalid("%{}: loop already exists", .{@intFromEnum(inst)}); | ||
| 497 | defer { | 508 | defer { |
| 498 | var live = self.loops.fetchRemove(inst).?; | 509 | var live = self.loops.fetchRemove(inst).?; |
| 499 | live.value.deinit(self.gpa); | 510 | live.value.deinit(self.gpa); |
| 500 | } | 511 | } |
| 501 | if (gop.found_existing) return invalid("%{}: loop already exists", .{@intFromEnum(inst)}); | ||
| 502 | gop.value_ptr.* = try self.live.clone(self.gpa); | 512 | gop.value_ptr.* = try self.live.clone(self.gpa); |
| 503 | 513 | ||
| 504 | try self.verifyBody(loop_body); | 514 | try self.verifyBody(loop_body); |
| ... | @@ -528,7 +538,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { | ... | @@ -528,7 +538,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { |
| 528 | 538 | ||
| 529 | try self.verifyInst(inst); | 539 | try self.verifyInst(inst); |
| 530 | }, | 540 | }, |
| 531 | .switch_br => { | 541 | .switch_br, .loop_switch_br => { |
| 532 | const switch_br = self.air.unwrapSwitch(inst); | 542 | const switch_br = self.air.unwrapSwitch(inst); |
| 533 | const switch_br_liveness = try self.liveness.getSwitchBr( | 543 | const switch_br_liveness = try self.liveness.getSwitchBr( |
| 534 | self.gpa, | 544 | self.gpa, |
| ... | @@ -539,13 +549,22 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { | ... | @@ -539,13 +549,22 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { |
| 539 | 549 | ||
| 540 | try self.verifyOperand(inst, switch_br.operand, self.liveness.operandDies(inst, 0)); | 550 | try self.verifyOperand(inst, switch_br.operand, self.liveness.operandDies(inst, 0)); |
| 541 | 551 | ||
| 542 | var live = self.live.move(); | 552 | // Excluding the operand (which we just handled), the same stuff should be alive |
| 543 | defer live.deinit(self.gpa); | 553 | // after the loop as before it. |
| 554 | { | ||
| 555 | const gop = try self.loops.getOrPut(self.gpa, inst); | ||
| 556 | if (gop.found_existing) return invalid("%{}: loop already exists", .{@intFromEnum(inst)}); | ||
| 557 | gop.value_ptr.* = self.live.move(); | ||
| 558 | } | ||
| 559 | defer { | ||
| 560 | var live = self.loops.fetchRemove(inst).?; | ||
| 561 | live.value.deinit(self.gpa); | ||
| 562 | } | ||
| 544 | 563 | ||
| 545 | var it = switch_br.iterateCases(); | 564 | var it = switch_br.iterateCases(); |
| 546 | while (it.next()) |case| { | 565 | while (it.next()) |case| { |
| 547 | self.live.deinit(self.gpa); | 566 | self.live.deinit(self.gpa); |
| 548 | self.live = try live.clone(self.gpa); | 567 | self.live = try self.loops.get(inst).?.clone(self.gpa); |
| 549 | 568 | ||
| 550 | for (switch_br_liveness.deaths[case.idx]) |death| try self.verifyDeath(inst, death); | 569 | for (switch_br_liveness.deaths[case.idx]) |death| try self.verifyDeath(inst, death); |
| 551 | try self.verifyBody(case.body); | 570 | try self.verifyBody(case.body); |
| ... | @@ -554,7 +573,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { | ... | @@ -554,7 +573,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { |
| 554 | const else_body = it.elseBody(); | 573 | const else_body = it.elseBody(); |
| 555 | if (else_body.len > 0) { | 574 | if (else_body.len > 0) { |
| 556 | self.live.deinit(self.gpa); | 575 | self.live.deinit(self.gpa); |
| 557 | self.live = try live.clone(self.gpa); | 576 | self.live = try self.loops.get(inst).?.clone(self.gpa); |
| 558 | for (switch_br_liveness.deaths[switch_br.cases_len]) |death| try self.verifyDeath(inst, death); | 577 | for (switch_br_liveness.deaths[switch_br.cases_len]) |death| try self.verifyDeath(inst, death); |
| 559 | try self.verifyBody(else_body); | 578 | try self.verifyBody(else_body); |
| 560 | } | 579 | } |
src/Sema.zig+479-141| ... | @@ -503,11 +503,21 @@ pub const Block = struct { | ... | @@ -503,11 +503,21 @@ pub const Block = struct { |
| 503 | /// to enable more precise compile errors. | 503 | /// to enable more precise compile errors. |
| 504 | /// Same indexes, capacity, length as `results`. | 504 | /// Same indexes, capacity, length as `results`. |
| 505 | src_locs: std.ArrayListUnmanaged(?LazySrcLoc), | 505 | src_locs: std.ArrayListUnmanaged(?LazySrcLoc), |
| 506 | 506 | /// Most blocks do not utilize this field. When it is used, its use is | |
| 507 | pub fn deinit(merges: *@This(), allocator: mem.Allocator) void { | 507 | /// contextual. The possible uses are as follows: |
| 508 | /// * for a `switch_block[_ref]`, this refers to dummy `br` instructions | ||
| 509 | /// which correspond to `switch_continue` ZIR. The switch logic will | ||
| 510 | /// rewrite these to appropriate AIR switch dispatches. | ||
| 511 | extra_insts: std.ArrayListUnmanaged(Air.Inst.Index) = .{}, | ||
| 512 | /// Same indexes, capacity, length as `extra_insts`. | ||
| 513 | extra_src_locs: std.ArrayListUnmanaged(LazySrcLoc) = .{}, | ||
| 514 | |||
| 515 | pub fn deinit(merges: *@This(), allocator: Allocator) void { | ||
| 508 | merges.results.deinit(allocator); | 516 | merges.results.deinit(allocator); |
| 509 | merges.br_list.deinit(allocator); | 517 | merges.br_list.deinit(allocator); |
| 510 | merges.src_locs.deinit(allocator); | 518 | merges.src_locs.deinit(allocator); |
| 519 | merges.extra_insts.deinit(allocator); | ||
| 520 | merges.extra_src_locs.deinit(allocator); | ||
| 511 | } | 521 | } |
| 512 | }; | 522 | }; |
| 513 | 523 | ||
| ... | @@ -946,14 +956,21 @@ fn analyzeInlineBody( | ... | @@ -946,14 +956,21 @@ fn analyzeInlineBody( |
| 946 | error.ComptimeBreak => {}, | 956 | error.ComptimeBreak => {}, |
| 947 | else => |e| return e, | 957 | else => |e| return e, |
| 948 | } | 958 | } |
| 949 | const break_inst = sema.comptime_break_inst; | 959 | const break_inst = sema.code.instructions.get(@intFromEnum(sema.comptime_break_inst)); |
| 950 | const break_data = sema.code.instructions.items(.data)[@intFromEnum(break_inst)].@"break"; | 960 | switch (break_inst.tag) { |
| 951 | const extra = sema.code.extraData(Zir.Inst.Break, break_data.payload_index).data; | 961 | .switch_continue => { |
| 962 | // This is handled by separate logic. | ||
| 963 | return error.ComptimeBreak; | ||
| 964 | }, | ||
| 965 | .break_inline, .@"break" => {}, | ||
| 966 | else => unreachable, | ||
| 967 | } | ||
| 968 | const extra = sema.code.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data; | ||
| 952 | if (extra.block_inst != break_target) { | 969 | if (extra.block_inst != break_target) { |
| 953 | // This control flow goes further up the stack. | 970 | // This control flow goes further up the stack. |
| 954 | return error.ComptimeBreak; | 971 | return error.ComptimeBreak; |
| 955 | } | 972 | } |
| 956 | return try sema.resolveInst(break_data.operand); | 973 | return try sema.resolveInst(break_inst.data.@"break".operand); |
| 957 | } | 974 | } |
| 958 | 975 | ||
| 959 | /// Like `analyzeInlineBody`, but if the body does not break with a value, returns | 976 | /// Like `analyzeInlineBody`, but if the body does not break with a value, returns |
| ... | @@ -1571,6 +1588,13 @@ fn analyzeBodyInner( | ... | @@ -1571,6 +1588,13 @@ fn analyzeBodyInner( |
| 1571 | i = 0; | 1588 | i = 0; |
| 1572 | continue; | 1589 | continue; |
| 1573 | }, | 1590 | }, |
| 1591 | .switch_continue => if (block.is_comptime) { | ||
| 1592 | sema.comptime_break_inst = inst; | ||
| 1593 | return error.ComptimeBreak; | ||
| 1594 | } else { | ||
| 1595 | try sema.zirSwitchContinue(block, inst); | ||
| 1596 | break; | ||
| 1597 | }, | ||
| 1574 | .loop => blk: { | 1598 | .loop => blk: { |
| 1575 | if (!block.is_comptime) break :blk try sema.zirLoop(block, inst); | 1599 | if (!block.is_comptime) break :blk try sema.zirLoop(block, inst); |
| 1576 | // Same as `block_inline`. TODO https://github.com/ziglang/zig/issues/8220 | 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,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 | fn zirDbgStmt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { | 6608 | fn zirDbgStmt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { |
| 6535 | if (block.is_comptime or block.ownerModule().strip) return; | 6609 | if (block.is_comptime or block.ownerModule().strip) return; |
| 6536 | 6610 | ||
| ... | @@ -10940,12 +11014,7 @@ const SwitchProngAnalysis = struct { | ... | @@ -10940,12 +11014,7 @@ const SwitchProngAnalysis = struct { |
| 10940 | sema: *Sema, | 11014 | sema: *Sema, |
| 10941 | /// The block containing the `switch_block` itself. | 11015 | /// The block containing the `switch_block` itself. |
| 10942 | parent_block: *Block, | 11016 | parent_block: *Block, |
| 10943 | /// The raw switch operand value (*not* the condition). Always defined. | 11017 | operand: Operand, |
| 10944 | operand: Air.Inst.Ref, | ||
| 10945 | /// May be `undefined` if no prong has a by-ref capture. | ||
| 10946 | operand_ptr: Air.Inst.Ref, | ||
| 10947 | /// The switch condition value. For unions, `operand` is the union and `cond` is its tag. | ||
| 10948 | cond: Air.Inst.Ref, | ||
| 10949 | /// If this switch is on an error set, this is the type to assign to the | 11018 | /// If this switch is on an error set, this is the type to assign to the |
| 10950 | /// `else` prong. If `null`, the prong should be unreachable. | 11019 | /// `else` prong. If `null`, the prong should be unreachable. |
| 10951 | else_error_ty: ?Type, | 11020 | else_error_ty: ?Type, |
| ... | @@ -10955,6 +11024,34 @@ const SwitchProngAnalysis = struct { | ... | @@ -10955,6 +11024,34 @@ const SwitchProngAnalysis = struct { |
| 10955 | /// undefined if no prong has a tag capture. | 11024 | /// undefined if no prong has a tag capture. |
| 10956 | tag_capture_inst: Zir.Inst.Index, | 11025 | tag_capture_inst: Zir.Inst.Index, |
| 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 | /// Resolve a switch prong which is determined at comptime to have no peers. | 11055 | /// Resolve a switch prong which is determined at comptime to have no peers. |
| 10959 | /// Uses `resolveBlockBody`. Sets up captures as needed. | 11056 | /// Uses `resolveBlockBody`. Sets up captures as needed. |
| 10960 | fn resolveProngComptime( | 11057 | fn resolveProngComptime( |
| ... | @@ -11086,7 +11183,15 @@ const SwitchProngAnalysis = struct { | ... | @@ -11086,7 +11183,15 @@ const SwitchProngAnalysis = struct { |
| 11086 | const sema = spa.sema; | 11183 | const sema = spa.sema; |
| 11087 | const pt = sema.pt; | 11184 | const pt = sema.pt; |
| 11088 | const zcu = pt.zcu; | 11185 | const zcu = pt.zcu; |
| 11089 | const operand_ty = sema.typeOf(spa.operand); | 11186 | const operand_ty = switch (spa.operand) { |
| 11187 | .simple => |s| sema.typeOf(s.by_val), | ||
| 11188 | .loop => |l| ty: { | ||
| 11189 | const alloc_ty = sema.typeOf(l.operand_alloc); | ||
| 11190 | const alloc_child = alloc_ty.childType(zcu); | ||
| 11191 | if (l.operand_is_ref) break :ty alloc_child.childType(zcu); | ||
| 11192 | break :ty alloc_child; | ||
| 11193 | }, | ||
| 11194 | }; | ||
| 11090 | if (operand_ty.zigTypeTag(zcu) != .@"union") { | 11195 | if (operand_ty.zigTypeTag(zcu) != .@"union") { |
| 11091 | const tag_capture_src: LazySrcLoc = .{ | 11196 | const tag_capture_src: LazySrcLoc = .{ |
| 11092 | .base_node_inst = capture_src.base_node_inst, | 11197 | .base_node_inst = capture_src.base_node_inst, |
| ... | @@ -11117,10 +11222,24 @@ const SwitchProngAnalysis = struct { | ... | @@ -11117,10 +11222,24 @@ const SwitchProngAnalysis = struct { |
| 11117 | const zir_datas = sema.code.instructions.items(.data); | 11222 | const zir_datas = sema.code.instructions.items(.data); |
| 11118 | const switch_node_offset = zir_datas[@intFromEnum(spa.switch_block_inst)].pl_node.src_node; | 11223 | const switch_node_offset = zir_datas[@intFromEnum(spa.switch_block_inst)].pl_node.src_node; |
| 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 | const operand_src = block.src(.{ .node_offset_switch_operand = switch_node_offset }); | 11225 | const operand_src = block.src(.{ .node_offset_switch_operand = switch_node_offset }); |
| 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 | if (inline_case_capture != .none) { | 11243 | if (inline_case_capture != .none) { |
| 11125 | const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inline_case_capture, undefined) catch unreachable; | 11244 | const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inline_case_capture, undefined) catch unreachable; |
| 11126 | if (operand_ty.zigTypeTag(zcu) == .@"union") { | 11245 | if (operand_ty.zigTypeTag(zcu) == .@"union") { |
| ... | @@ -11136,16 +11255,16 @@ const SwitchProngAnalysis = struct { | ... | @@ -11136,16 +11255,16 @@ const SwitchProngAnalysis = struct { |
| 11136 | .address_space = operand_ptr_ty.ptrAddressSpace(zcu), | 11255 | .address_space = operand_ptr_ty.ptrAddressSpace(zcu), |
| 11137 | }, | 11256 | }, |
| 11138 | }); | 11257 | }); |
| 11139 | if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |union_ptr| { | 11258 | if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |union_ptr| { |
| 11140 | return Air.internedToRef((try union_ptr.ptrField(field_index, pt)).toIntern()); | 11259 | return Air.internedToRef((try union_ptr.ptrField(field_index, pt)).toIntern()); |
| 11141 | } | 11260 | } |
| 11142 | return block.addStructFieldPtr(spa.operand_ptr, field_index, ptr_field_ty); | 11261 | return block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty); |
| 11143 | } else { | 11262 | } else { |
| 11144 | if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |union_val| { | 11263 | if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |union_val| { |
| 11145 | const tag_and_val = ip.indexToKey(union_val.toIntern()).un; | 11264 | const tag_and_val = ip.indexToKey(union_val.toIntern()).un; |
| 11146 | return Air.internedToRef(tag_and_val.val); | 11265 | return Air.internedToRef(tag_and_val.val); |
| 11147 | } | 11266 | } |
| 11148 | return block.addStructFieldVal(spa.operand, field_index, field_ty); | 11267 | return block.addStructFieldVal(operand_val, field_index, field_ty); |
| 11149 | } | 11268 | } |
| 11150 | } else if (capture_byref) { | 11269 | } else if (capture_byref) { |
| 11151 | return sema.uavRef(item_val.toIntern()); | 11270 | return sema.uavRef(item_val.toIntern()); |
| ... | @@ -11156,17 +11275,17 @@ const SwitchProngAnalysis = struct { | ... | @@ -11156,17 +11275,17 @@ const SwitchProngAnalysis = struct { |
| 11156 | 11275 | ||
| 11157 | if (is_special_prong) { | 11276 | if (is_special_prong) { |
| 11158 | if (capture_byref) { | 11277 | if (capture_byref) { |
| 11159 | return spa.operand_ptr; | 11278 | return operand_ptr; |
| 11160 | } | 11279 | } |
| 11161 | 11280 | ||
| 11162 | switch (operand_ty.zigTypeTag(zcu)) { | 11281 | switch (operand_ty.zigTypeTag(zcu)) { |
| 11163 | .error_set => if (spa.else_error_ty) |ty| { | 11282 | .error_set => if (spa.else_error_ty) |ty| { |
| 11164 | return sema.bitCast(block, ty, spa.operand, operand_src, null); | 11283 | return sema.bitCast(block, ty, operand_val, operand_src, null); |
| 11165 | } else { | 11284 | } else { |
| 11166 | try sema.analyzeUnreachable(block, operand_src, false); | 11285 | try sema.analyzeUnreachable(block, operand_src, false); |
| 11167 | return .unreachable_value; | 11286 | return .unreachable_value; |
| 11168 | }, | 11287 | }, |
| 11169 | else => return spa.operand, | 11288 | else => return operand_val, |
| 11170 | } | 11289 | } |
| 11171 | } | 11290 | } |
| 11172 | 11291 | ||
| ... | @@ -11265,19 +11384,19 @@ const SwitchProngAnalysis = struct { | ... | @@ -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 | if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty); | 11388 | if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty); |
| 11270 | const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt); | 11389 | const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt); |
| 11271 | return Air.internedToRef((try pt.getCoerced(field_ptr_val, capture_ptr_ty)).toIntern()); | 11390 | return Air.internedToRef((try pt.getCoerced(field_ptr_val, capture_ptr_ty)).toIntern()); |
| 11272 | } | 11391 | } |
| 11273 | 11392 | ||
| 11274 | try sema.requireRuntimeBlock(block, operand_src, null); | 11393 | try sema.requireRuntimeBlock(block, operand_src, null); |
| 11275 | return block.addStructFieldPtr(spa.operand_ptr, first_field_index, capture_ptr_ty); | 11394 | return block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty); |
| 11276 | } | 11395 | } |
| 11277 | 11396 | ||
| 11278 | if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |operand_val| { | 11397 | if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |operand_val_val| { |
| 11279 | if (operand_val.isUndef(zcu)) return pt.undefRef(capture_ty); | 11398 | if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty); |
| 11280 | const union_val = ip.indexToKey(operand_val.toIntern()).un; | 11399 | const union_val = ip.indexToKey(operand_val_val.toIntern()).un; |
| 11281 | if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty); | 11400 | if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty); |
| 11282 | const uncoerced = Air.internedToRef(union_val.val); | 11401 | const uncoerced = Air.internedToRef(union_val.val); |
| 11283 | return sema.coerce(block, capture_ty, uncoerced, operand_src); | 11402 | return sema.coerce(block, capture_ty, uncoerced, operand_src); |
| ... | @@ -11286,7 +11405,7 @@ const SwitchProngAnalysis = struct { | ... | @@ -11286,7 +11405,7 @@ const SwitchProngAnalysis = struct { |
| 11286 | try sema.requireRuntimeBlock(block, operand_src, null); | 11405 | try sema.requireRuntimeBlock(block, operand_src, null); |
| 11287 | 11406 | ||
| 11288 | if (same_types) { | 11407 | if (same_types) { |
| 11289 | return block.addStructFieldVal(spa.operand, first_field_index, capture_ty); | 11408 | return block.addStructFieldVal(operand_val, first_field_index, capture_ty); |
| 11290 | } | 11409 | } |
| 11291 | 11410 | ||
| 11292 | // We may have to emit a switch block which coerces the operand to the capture type. | 11411 | // We may have to emit a switch block which coerces the operand to the capture type. |
| ... | @@ -11300,7 +11419,7 @@ const SwitchProngAnalysis = struct { | ... | @@ -11300,7 +11419,7 @@ const SwitchProngAnalysis = struct { |
| 11300 | } | 11419 | } |
| 11301 | // All fields are in-memory coercible to the resolved type! | 11420 | // All fields are in-memory coercible to the resolved type! |
| 11302 | // Just take the first field and bitcast the result. | 11421 | // Just take the first field and bitcast the result. |
| 11303 | const uncoerced = try block.addStructFieldVal(spa.operand, first_field_index, first_field_ty); | 11422 | const uncoerced = try block.addStructFieldVal(operand_val, first_field_index, first_field_ty); |
| 11304 | return block.addBitCast(capture_ty, uncoerced); | 11423 | return block.addBitCast(capture_ty, uncoerced); |
| 11305 | }; | 11424 | }; |
| 11306 | 11425 | ||
| ... | @@ -11364,7 +11483,7 @@ const SwitchProngAnalysis = struct { | ... | @@ -11364,7 +11483,7 @@ const SwitchProngAnalysis = struct { |
| 11364 | 11483 | ||
| 11365 | const field_idx = field_indices[idx]; | 11484 | const field_idx = field_indices[idx]; |
| 11366 | const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_idx]); | 11485 | const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_idx]); |
| 11367 | const uncoerced = try coerce_block.addStructFieldVal(spa.operand, field_idx, field_ty); | 11486 | const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty); |
| 11368 | const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src); | 11487 | const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src); |
| 11369 | _ = try coerce_block.addBr(capture_block_inst, coerced); | 11488 | _ = try coerce_block.addBr(capture_block_inst, coerced); |
| 11370 | 11489 | ||
| ... | @@ -11388,7 +11507,7 @@ const SwitchProngAnalysis = struct { | ... | @@ -11388,7 +11507,7 @@ const SwitchProngAnalysis = struct { |
| 11388 | const first_imc_item_idx = in_mem_coercible.findFirstSet().?; | 11507 | const first_imc_item_idx = in_mem_coercible.findFirstSet().?; |
| 11389 | const first_imc_field_idx = field_indices[first_imc_item_idx]; | 11508 | const first_imc_field_idx = field_indices[first_imc_item_idx]; |
| 11390 | const first_imc_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]); | 11509 | const first_imc_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]); |
| 11391 | const uncoerced = try coerce_block.addStructFieldVal(spa.operand, first_imc_field_idx, first_imc_field_ty); | 11510 | const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty); |
| 11392 | const coerced = try coerce_block.addBitCast(capture_ty, uncoerced); | 11511 | const coerced = try coerce_block.addBitCast(capture_ty, uncoerced); |
| 11393 | _ = try coerce_block.addBr(capture_block_inst, coerced); | 11512 | _ = try coerce_block.addBr(capture_block_inst, coerced); |
| 11394 | 11513 | ||
| ... | @@ -11404,21 +11523,47 @@ const SwitchProngAnalysis = struct { | ... | @@ -11404,21 +11523,47 @@ const SwitchProngAnalysis = struct { |
| 11404 | const switch_br_inst: u32 = @intCast(sema.air_instructions.len); | 11523 | const switch_br_inst: u32 = @intCast(sema.air_instructions.len); |
| 11405 | try sema.air_instructions.append(sema.gpa, .{ | 11524 | try sema.air_instructions.append(sema.gpa, .{ |
| 11406 | .tag = .switch_br, | 11525 | .tag = .switch_br, |
| 11407 | .data = .{ .pl_op = .{ | 11526 | .data = .{ |
| 11408 | .operand = spa.cond, | 11527 | .pl_op = .{ |
| 11409 | .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{ | 11528 | .operand = undefined, // set by switch below |
| 11410 | .cases_len = @intCast(prong_count), | 11529 | .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{ |
| 11411 | .else_body_len = @intCast(else_body_len), | 11530 | .cases_len = @intCast(prong_count), |
| 11412 | }), | 11531 | .else_body_len = @intCast(else_body_len), |
| 11413 | } }, | 11532 | }), |
| 11533 | }, | ||
| 11534 | }, | ||
| 11414 | }); | 11535 | }); |
| 11415 | sema.air_extra.appendSliceAssumeCapacity(cases_extra.items); | 11536 | sema.air_extra.appendSliceAssumeCapacity(cases_extra.items); |
| 11416 | 11537 | ||
| 11417 | // Set up block body | 11538 | // Set up block body |
| 11418 | sema.air_instructions.items(.data)[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{ | 11539 | switch (spa.operand) { |
| 11419 | .body_len = 1, | 11540 | .simple => |s| { |
| 11420 | }); | 11541 | const air_datas = sema.air_instructions.items(.data); |
| 11421 | sema.air_extra.appendAssumeCapacity(switch_br_inst); | 11542 | air_datas[switch_br_inst].pl_op.operand = s.cond; |
| 11543 | air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{ | ||
| 11544 | .body_len = 1, | ||
| 11545 | }); | ||
| 11546 | sema.air_extra.appendAssumeCapacity(switch_br_inst); | ||
| 11547 | }, | ||
| 11548 | .loop => { | ||
| 11549 | // The block must first extract the tag from the loaded union. | ||
| 11550 | const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len); | ||
| 11551 | try sema.air_instructions.append(sema.gpa, .{ | ||
| 11552 | .tag = .get_union_tag, | ||
| 11553 | .data = .{ .ty_op = .{ | ||
| 11554 | .ty = Air.internedToRef(union_obj.enum_tag_ty), | ||
| 11555 | .operand = operand_val, | ||
| 11556 | } }, | ||
| 11557 | }); | ||
| 11558 | const air_datas = sema.air_instructions.items(.data); | ||
| 11559 | air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef(); | ||
| 11560 | air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{ | ||
| 11561 | .body_len = 2, | ||
| 11562 | }); | ||
| 11563 | sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst)); | ||
| 11564 | sema.air_extra.appendAssumeCapacity(switch_br_inst); | ||
| 11565 | }, | ||
| 11566 | } | ||
| 11422 | 11567 | ||
| 11423 | return capture_block_inst.toRef(); | 11568 | return capture_block_inst.toRef(); |
| 11424 | }, | 11569 | }, |
| ... | @@ -11435,7 +11580,7 @@ const SwitchProngAnalysis = struct { | ... | @@ -11435,7 +11580,7 @@ const SwitchProngAnalysis = struct { |
| 11435 | if (case_vals.len == 1) { | 11580 | if (case_vals.len == 1) { |
| 11436 | const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable; | 11581 | const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable; |
| 11437 | const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?); | 11582 | const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?); |
| 11438 | return sema.bitCast(block, item_ty, spa.operand, operand_src, null); | 11583 | return sema.bitCast(block, item_ty, operand_val, operand_src, null); |
| 11439 | } | 11584 | } |
| 11440 | 11585 | ||
| 11441 | var names: InferredErrorSet.NameMap = .{}; | 11586 | var names: InferredErrorSet.NameMap = .{}; |
| ... | @@ -11445,15 +11590,15 @@ const SwitchProngAnalysis = struct { | ... | @@ -11445,15 +11590,15 @@ const SwitchProngAnalysis = struct { |
| 11445 | names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {}); | 11590 | names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {}); |
| 11446 | } | 11591 | } |
| 11447 | const error_ty = try pt.errorSetFromUnsortedNames(names.keys()); | 11592 | const error_ty = try pt.errorSetFromUnsortedNames(names.keys()); |
| 11448 | return sema.bitCast(block, error_ty, spa.operand, operand_src, null); | 11593 | return sema.bitCast(block, error_ty, operand_val, operand_src, null); |
| 11449 | }, | 11594 | }, |
| 11450 | else => { | 11595 | else => { |
| 11451 | // In this case the capture value is just the passed-through value | 11596 | // In this case the capture value is just the passed-through value |
| 11452 | // of the switch condition. | 11597 | // of the switch condition. |
| 11453 | if (capture_byref) { | 11598 | if (capture_byref) { |
| 11454 | return spa.operand_ptr; | 11599 | return operand_ptr; |
| 11455 | } else { | 11600 | } else { |
| 11456 | return spa.operand; | 11601 | return operand_val; |
| 11457 | } | 11602 | } |
| 11458 | }, | 11603 | }, |
| 11459 | } | 11604 | } |
| ... | @@ -11686,9 +11831,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp | ... | @@ -11686,9 +11831,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp |
| 11686 | var spa: SwitchProngAnalysis = .{ | 11831 | var spa: SwitchProngAnalysis = .{ |
| 11687 | .sema = sema, | 11832 | .sema = sema, |
| 11688 | .parent_block = block, | 11833 | .parent_block = block, |
| 11689 | .operand = undefined, // must be set to the unwrapped error code before use | 11834 | .operand = .{ |
| 11690 | .operand_ptr = .none, | 11835 | .simple = .{ |
| 11691 | .cond = raw_operand_val, | 11836 | .by_val = undefined, // must be set to the unwrapped error code before use |
| 11837 | .by_ref = undefined, | ||
| 11838 | .cond = raw_operand_val, | ||
| 11839 | }, | ||
| 11840 | }, | ||
| 11692 | .else_error_ty = else_error_ty, | 11841 | .else_error_ty = else_error_ty, |
| 11693 | .switch_block_inst = inst, | 11842 | .switch_block_inst = inst, |
| 11694 | .tag_capture_inst = undefined, | 11843 | .tag_capture_inst = undefined, |
| ... | @@ -11709,13 +11858,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp | ... | @@ -11709,13 +11858,13 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp |
| 11709 | .name = operand_val.getErrorName(zcu).unwrap().?, | 11858 | .name = operand_val.getErrorName(zcu).unwrap().?, |
| 11710 | }, | 11859 | }, |
| 11711 | })); | 11860 | })); |
| 11712 | spa.operand = if (extra.data.bits.payload_is_ref) | 11861 | spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref) |
| 11713 | try sema.analyzeErrUnionCodePtr(block, switch_operand_src, raw_operand_val) | 11862 | try sema.analyzeErrUnionCodePtr(block, switch_operand_src, raw_operand_val) |
| 11714 | else | 11863 | else |
| 11715 | try sema.analyzeErrUnionCode(block, switch_operand_src, raw_operand_val); | 11864 | try sema.analyzeErrUnionCode(block, switch_operand_src, raw_operand_val); |
| 11716 | 11865 | ||
| 11717 | if (extra.data.bits.any_uses_err_capture) { | 11866 | if (extra.data.bits.any_uses_err_capture) { |
| 11718 | sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand); | 11867 | sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val); |
| 11719 | } | 11868 | } |
| 11720 | defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst)); | 11869 | defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst)); |
| 11721 | 11870 | ||
| ... | @@ -11723,7 +11872,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp | ... | @@ -11723,7 +11872,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp |
| 11723 | sema, | 11872 | sema, |
| 11724 | spa, | 11873 | spa, |
| 11725 | &child_block, | 11874 | &child_block, |
| 11726 | try sema.switchCond(block, switch_operand_src, spa.operand), | 11875 | try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val), |
| 11727 | err_val, | 11876 | err_val, |
| 11728 | operand_err_set_ty, | 11877 | operand_err_set_ty, |
| 11729 | switch_src_node_offset, | 11878 | switch_src_node_offset, |
| ... | @@ -11777,20 +11926,20 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp | ... | @@ -11777,20 +11926,20 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp |
| 11777 | const true_instructions = try sub_block.instructions.toOwnedSlice(gpa); | 11926 | const true_instructions = try sub_block.instructions.toOwnedSlice(gpa); |
| 11778 | defer gpa.free(true_instructions); | 11927 | defer gpa.free(true_instructions); |
| 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 | try sema.analyzeErrUnionCodePtr(&sub_block, switch_operand_src, raw_operand_val) | 11930 | try sema.analyzeErrUnionCodePtr(&sub_block, switch_operand_src, raw_operand_val) |
| 11782 | else | 11931 | else |
| 11783 | try sema.analyzeErrUnionCode(&sub_block, switch_operand_src, raw_operand_val); | 11932 | try sema.analyzeErrUnionCode(&sub_block, switch_operand_src, raw_operand_val); |
| 11784 | 11933 | ||
| 11785 | if (extra.data.bits.any_uses_err_capture) { | 11934 | if (extra.data.bits.any_uses_err_capture) { |
| 11786 | sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand); | 11935 | sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val); |
| 11787 | } | 11936 | } |
| 11788 | defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst)); | 11937 | defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst)); |
| 11789 | _ = try sema.analyzeSwitchRuntimeBlock( | 11938 | _ = try sema.analyzeSwitchRuntimeBlock( |
| 11790 | spa, | 11939 | spa, |
| 11791 | &sub_block, | 11940 | &sub_block, |
| 11792 | switch_src, | 11941 | switch_src, |
| 11793 | try sema.switchCond(block, switch_operand_src, spa.operand), | 11942 | try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val), |
| 11794 | operand_err_set_ty, | 11943 | operand_err_set_ty, |
| 11795 | switch_operand_src, | 11944 | switch_operand_src, |
| 11796 | case_vals, | 11945 | case_vals, |
| ... | @@ -11859,17 +12008,63 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -11859,17 +12008,63 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 11859 | const special_prong_src = block.src(.{ .node_offset_switch_special_prong = src_node_offset }); | 12008 | const special_prong_src = block.src(.{ .node_offset_switch_special_prong = src_node_offset }); |
| 11860 | const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index); | 12009 | const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index); |
| 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 | const maybe_ptr = try sema.resolveInst(extra.data.operand); | 12012 | const maybe_ptr = try sema.resolveInst(extra.data.operand); |
| 11864 | if (operand_is_ref) { | 12013 | const val, const ref = if (operand_is_ref) |
| 11865 | const val = try sema.analyzeLoad(block, src, maybe_ptr, operand_src); | 12014 | .{ try sema.analyzeLoad(block, src, maybe_ptr, operand_src), maybe_ptr } |
| 11866 | break :blk .{ val, maybe_ptr }; | 12015 | else |
| 11867 | } else { | 12016 | .{ maybe_ptr, undefined }; |
| 11868 | break :blk .{ maybe_ptr, undefined }; | 12017 | |
| 12018 | const init_cond = try sema.switchCond(block, operand_src, val); | ||
| 12019 | |||
| 12020 | const operand_ty = sema.typeOf(val); | ||
| 12021 | |||
| 12022 | if (extra.data.bits.has_continue and !block.is_comptime) { | ||
| 12023 | // Even if the operand is comptime-known, this `switch` is runtime. | ||
| 12024 | if (try operand_ty.comptimeOnlySema(pt)) { | ||
| 12025 | return sema.failWithOwnedErrorMsg(block, msg: { | ||
| 12026 | const msg = try sema.errMsg(operand_src, "operand of switch loop has comptime-only type '{}'", .{operand_ty.fmt(pt)}); | ||
| 12027 | errdefer msg.destroy(gpa); | ||
| 12028 | try sema.errNote(operand_src, msg, "switch loops are evalauted at runtime outside of comptime scopes", .{}); | ||
| 12029 | break :msg msg; | ||
| 12030 | }); | ||
| 12031 | } | ||
| 12032 | try sema.validateRuntimeValue(block, operand_src, maybe_ptr); | ||
| 12033 | const operand_alloc = if (extra.data.bits.any_non_inline_capture) a: { | ||
| 12034 | const operand_ptr_ty = try pt.singleMutPtrType(sema.typeOf(maybe_ptr)); | ||
| 12035 | const operand_alloc = try block.addTy(.alloc, operand_ptr_ty); | ||
| 12036 | _ = try block.addBinOp(.store, operand_alloc, maybe_ptr); | ||
| 12037 | break :a operand_alloc; | ||
| 12038 | } else undefined; | ||
| 12039 | break :op .{ | ||
| 12040 | .{ .loop = .{ | ||
| 12041 | .operand_alloc = operand_alloc, | ||
| 12042 | .operand_is_ref = operand_is_ref, | ||
| 12043 | .init_cond = init_cond, | ||
| 12044 | } }, | ||
| 12045 | operand_ty, | ||
| 12046 | }; | ||
| 11869 | } | 12047 | } |
| 12048 | |||
| 12049 | // We always use `simple` in the comptime case, because as far as the dispatching logic | ||
| 12050 | // is concerned, it really is dispatching a single prong. `resolveSwitchComptime` will | ||
| 12051 | // be resposible for recursively resolving different prongs as needed. | ||
| 12052 | break :op .{ | ||
| 12053 | .{ .simple = .{ | ||
| 12054 | .by_val = val, | ||
| 12055 | .by_ref = ref, | ||
| 12056 | .cond = init_cond, | ||
| 12057 | } }, | ||
| 12058 | operand_ty, | ||
| 12059 | }; | ||
| 11870 | }; | 12060 | }; |
| 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 | // AstGen guarantees that the instruction immediately preceding | 12069 | // AstGen guarantees that the instruction immediately preceding |
| 11875 | // switch_block(_ref) is a dbg_stmt | 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,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 | // Duplicate checking variables later also used for `inline else`. | 12117 | // Duplicate checking variables later also used for `inline else`. |
| 11926 | var seen_enum_fields: []?LazySrcLoc = &.{}; | 12118 | var seen_enum_fields: []?LazySrcLoc = &.{}; |
| 11927 | var seen_errors = SwitchErrorSet.init(gpa); | 12119 | var seen_errors = SwitchErrorSet.init(gpa); |
| ... | @@ -11937,13 +12129,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -11937,13 +12129,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 11937 | 12129 | ||
| 11938 | var empty_enum = false; | 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 | var else_error_ty: ?Type = null; | 12132 | var else_error_ty: ?Type = null; |
| 11944 | 12133 | ||
| 11945 | // Validate usage of '_' prongs. | 12134 | // Validate usage of '_' prongs. |
| 11946 | if (special_prong == .under and (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally)) { | 12135 | if (special_prong == .under and !raw_operand_ty.isNonexhaustiveEnum(zcu)) { |
| 11947 | const msg = msg: { | 12136 | const msg = msg: { |
| 11948 | const msg = try sema.errMsg( | 12137 | const msg = try sema.errMsg( |
| 11949 | src, | 12138 | src, |
| ... | @@ -11969,11 +12158,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -11969,11 +12158,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 11969 | } | 12158 | } |
| 11970 | 12159 | ||
| 11971 | // Validate for duplicate items, missing else prong, and invalid range. | 12160 | // Validate for duplicate items, missing else prong, and invalid range. |
| 11972 | switch (operand_ty.zigTypeTag(zcu)) { | 12161 | switch (cond_ty.zigTypeTag(zcu)) { |
| 11973 | .@"union" => unreachable, // handled in `switchCond` | 12162 | .@"union" => unreachable, // handled in `switchCond` |
| 11974 | .@"enum" => { | 12163 | .@"enum" => { |
| 11975 | seen_enum_fields = try gpa.alloc(?LazySrcLoc, operand_ty.enumFieldCount(zcu)); | 12164 | seen_enum_fields = try gpa.alloc(?LazySrcLoc, cond_ty.enumFieldCount(zcu)); |
| 11976 | empty_enum = seen_enum_fields.len == 0 and !operand_ty.isNonexhaustiveEnum(zcu); | 12165 | empty_enum = seen_enum_fields.len == 0 and !cond_ty.isNonexhaustiveEnum(zcu); |
| 11977 | @memset(seen_enum_fields, null); | 12166 | @memset(seen_enum_fields, null); |
| 11978 | // `range_set` is used for non-exhaustive enum values that do not correspond to any tags. | 12167 | // `range_set` is used for non-exhaustive enum values that do not correspond to any tags. |
| 11979 | 12168 | ||
| ... | @@ -11991,7 +12180,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -11991,7 +12180,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 11991 | seen_enum_fields, | 12180 | seen_enum_fields, |
| 11992 | &range_set, | 12181 | &range_set, |
| 11993 | item_ref, | 12182 | item_ref, |
| 11994 | operand_ty, | 12183 | cond_ty, |
| 11995 | block.src(.{ .switch_case_item = .{ | 12184 | block.src(.{ .switch_case_item = .{ |
| 11996 | .switch_node_offset = src_node_offset, | 12185 | .switch_node_offset = src_node_offset, |
| 11997 | .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) }, | 12186 | .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) }, |
| ... | @@ -12019,7 +12208,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12019,7 +12208,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12019 | seen_enum_fields, | 12208 | seen_enum_fields, |
| 12020 | &range_set, | 12209 | &range_set, |
| 12021 | item_ref, | 12210 | item_ref, |
| 12022 | operand_ty, | 12211 | cond_ty, |
| 12023 | block.src(.{ .switch_case_item = .{ | 12212 | block.src(.{ .switch_case_item = .{ |
| 12024 | .switch_node_offset = src_node_offset, | 12213 | .switch_node_offset = src_node_offset, |
| 12025 | .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, | 12214 | .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, |
| ... | @@ -12028,7 +12217,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12028,7 +12217,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12028 | )); | 12217 | )); |
| 12029 | } | 12218 | } |
| 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 | const all_tags_handled = for (seen_enum_fields) |seen_src| { | 12223 | const all_tags_handled = for (seen_enum_fields) |seen_src| { |
| ... | @@ -12036,7 +12225,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12036,7 +12225,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12036 | } else true; | 12225 | } else true; |
| 12037 | 12226 | ||
| 12038 | if (special_prong == .@"else") { | 12227 | if (special_prong == .@"else") { |
| 12039 | if (all_tags_handled and !operand_ty.isNonexhaustiveEnum(zcu)) return sema.fail( | 12228 | if (all_tags_handled and !cond_ty.isNonexhaustiveEnum(zcu)) return sema.fail( |
| 12040 | block, | 12229 | block, |
| 12041 | special_prong_src, | 12230 | special_prong_src, |
| 12042 | "unreachable else prong; all cases already handled", | 12231 | "unreachable else prong; all cases already handled", |
| ... | @@ -12053,9 +12242,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12053,9 +12242,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12053 | for (seen_enum_fields, 0..) |seen_src, i| { | 12242 | for (seen_enum_fields, 0..) |seen_src, i| { |
| 12054 | if (seen_src != null) continue; | 12243 | if (seen_src != null) continue; |
| 12055 | 12244 | ||
| 12056 | const field_name = operand_ty.enumFieldName(i, zcu); | 12245 | const field_name = cond_ty.enumFieldName(i, zcu); |
| 12057 | try sema.addFieldErrNote( | 12246 | try sema.addFieldErrNote( |
| 12058 | operand_ty, | 12247 | cond_ty, |
| 12059 | i, | 12248 | i, |
| 12060 | msg, | 12249 | msg, |
| 12061 | "unhandled enumeration value: '{}'", | 12250 | "unhandled enumeration value: '{}'", |
| ... | @@ -12063,15 +12252,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12063,15 +12252,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12063 | ); | 12252 | ); |
| 12064 | } | 12253 | } |
| 12065 | try sema.errNote( | 12254 | try sema.errNote( |
| 12066 | operand_ty.srcLoc(zcu), | 12255 | cond_ty.srcLoc(zcu), |
| 12067 | msg, | 12256 | msg, |
| 12068 | "enum '{}' declared here", | 12257 | "enum '{}' declared here", |
| 12069 | .{operand_ty.fmt(pt)}, | 12258 | .{cond_ty.fmt(pt)}, |
| 12070 | ); | 12259 | ); |
| 12071 | break :msg msg; | 12260 | break :msg msg; |
| 12072 | }; | 12261 | }; |
| 12073 | return sema.failWithOwnedErrorMsg(block, msg); | 12262 | return sema.failWithOwnedErrorMsg(block, msg); |
| 12074 | } else if (special_prong == .none and operand_ty.isNonexhaustiveEnum(zcu) and !union_originally) { | 12263 | } else if (special_prong == .none and cond_ty.isNonexhaustiveEnum(zcu) and !union_originally) { |
| 12075 | return sema.fail( | 12264 | return sema.fail( |
| 12076 | block, | 12265 | block, |
| 12077 | src, | 12266 | src, |
| ... | @@ -12085,7 +12274,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12085,7 +12274,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12085 | block, | 12274 | block, |
| 12086 | &seen_errors, | 12275 | &seen_errors, |
| 12087 | &case_vals, | 12276 | &case_vals, |
| 12088 | operand_ty, | 12277 | cond_ty, |
| 12089 | inst_data, | 12278 | inst_data, |
| 12090 | scalar_cases_len, | 12279 | scalar_cases_len, |
| 12091 | multi_cases_len, | 12280 | multi_cases_len, |
| ... | @@ -12106,7 +12295,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12106,7 +12295,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12106 | block, | 12295 | block, |
| 12107 | &range_set, | 12296 | &range_set, |
| 12108 | item_ref, | 12297 | item_ref, |
| 12109 | operand_ty, | 12298 | cond_ty, |
| 12110 | block.src(.{ .switch_case_item = .{ | 12299 | block.src(.{ .switch_case_item = .{ |
| 12111 | .switch_node_offset = src_node_offset, | 12300 | .switch_node_offset = src_node_offset, |
| 12112 | .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) }, | 12301 | .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) }, |
| ... | @@ -12133,7 +12322,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12133,7 +12322,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12133 | block, | 12322 | block, |
| 12134 | &range_set, | 12323 | &range_set, |
| 12135 | item_ref, | 12324 | item_ref, |
| 12136 | operand_ty, | 12325 | cond_ty, |
| 12137 | block.src(.{ .switch_case_item = .{ | 12326 | block.src(.{ .switch_case_item = .{ |
| 12138 | .switch_node_offset = src_node_offset, | 12327 | .switch_node_offset = src_node_offset, |
| 12139 | .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, | 12328 | .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, |
| ... | @@ -12155,7 +12344,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12155,7 +12344,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12155 | &range_set, | 12344 | &range_set, |
| 12156 | item_first, | 12345 | item_first, |
| 12157 | item_last, | 12346 | item_last, |
| 12158 | operand_ty, | 12347 | cond_ty, |
| 12159 | block.src(.{ .switch_case_item = .{ | 12348 | block.src(.{ .switch_case_item = .{ |
| 12160 | .switch_node_offset = src_node_offset, | 12349 | .switch_node_offset = src_node_offset, |
| 12161 | .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, | 12350 | .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, |
| ... | @@ -12171,9 +12360,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12171,9 +12360,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12171 | } | 12360 | } |
| 12172 | 12361 | ||
| 12173 | check_range: { | 12362 | check_range: { |
| 12174 | if (operand_ty.zigTypeTag(zcu) == .int) { | 12363 | if (cond_ty.zigTypeTag(zcu) == .int) { |
| 12175 | const min_int = try operand_ty.minInt(pt, operand_ty); | 12364 | const min_int = try cond_ty.minInt(pt, cond_ty); |
| 12176 | const max_int = try operand_ty.maxInt(pt, operand_ty); | 12365 | const max_int = try cond_ty.maxInt(pt, cond_ty); |
| 12177 | if (try range_set.spans(min_int.toIntern(), max_int.toIntern())) { | 12366 | if (try range_set.spans(min_int.toIntern(), max_int.toIntern())) { |
| 12178 | if (special_prong == .@"else") { | 12367 | if (special_prong == .@"else") { |
| 12179 | return sema.fail( | 12368 | return sema.fail( |
| ... | @@ -12246,7 +12435,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12246,7 +12435,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12246 | )); | 12435 | )); |
| 12247 | } | 12436 | } |
| 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 | switch (special_prong) { | 12441 | switch (special_prong) { |
| ... | @@ -12278,7 +12467,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12278,7 +12467,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12278 | block, | 12467 | block, |
| 12279 | src, | 12468 | src, |
| 12280 | "else prong required when switching on type '{}'", | 12469 | "else prong required when switching on type '{}'", |
| 12281 | .{operand_ty.fmt(pt)}, | 12470 | .{cond_ty.fmt(pt)}, |
| 12282 | ); | 12471 | ); |
| 12283 | } | 12472 | } |
| 12284 | 12473 | ||
| ... | @@ -12299,7 +12488,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12299,7 +12488,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12299 | block, | 12488 | block, |
| 12300 | &seen_values, | 12489 | &seen_values, |
| 12301 | item_ref, | 12490 | item_ref, |
| 12302 | operand_ty, | 12491 | cond_ty, |
| 12303 | block.src(.{ .switch_case_item = .{ | 12492 | block.src(.{ .switch_case_item = .{ |
| 12304 | .switch_node_offset = src_node_offset, | 12493 | .switch_node_offset = src_node_offset, |
| 12305 | .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) }, | 12494 | .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) }, |
| ... | @@ -12326,7 +12515,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12326,7 +12515,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12326 | block, | 12515 | block, |
| 12327 | &seen_values, | 12516 | &seen_values, |
| 12328 | item_ref, | 12517 | item_ref, |
| 12329 | operand_ty, | 12518 | cond_ty, |
| 12330 | block.src(.{ .switch_case_item = .{ | 12519 | block.src(.{ .switch_case_item = .{ |
| 12331 | .switch_node_offset = src_node_offset, | 12520 | .switch_node_offset = src_node_offset, |
| 12332 | .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, | 12521 | .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, |
| ... | @@ -12335,7 +12524,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12335,7 +12524,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12335 | )); | 12524 | )); |
| 12336 | } | 12525 | } |
| 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,16 +12543,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12354 | .comptime_float, | 12543 | .comptime_float, |
| 12355 | .float, | 12544 | .float, |
| 12356 | => return sema.fail(block, operand_src, "invalid switch operand type '{}'", .{ | 12545 | => return sema.fail(block, operand_src, "invalid switch operand type '{}'", .{ |
| 12357 | operand_ty.fmt(pt), | 12546 | raw_operand_ty.fmt(pt), |
| 12358 | }), | 12547 | }), |
| 12359 | } | 12548 | } |
| 12360 | 12549 | ||
| 12361 | const spa: SwitchProngAnalysis = .{ | 12550 | const spa: SwitchProngAnalysis = .{ |
| 12362 | .sema = sema, | 12551 | .sema = sema, |
| 12363 | .parent_block = block, | 12552 | .parent_block = block, |
| 12364 | .operand = raw_operand_val, | 12553 | .operand = operand, |
| 12365 | .operand_ptr = raw_operand_ptr, | ||
| 12366 | .cond = operand, | ||
| 12367 | .else_error_ty = else_error_ty, | 12554 | .else_error_ty = else_error_ty, |
| 12368 | .switch_block_inst = inst, | 12555 | .switch_block_inst = inst, |
| 12369 | .tag_capture_inst = tag_capture_inst, | 12556 | .tag_capture_inst = tag_capture_inst, |
| ... | @@ -12407,24 +12594,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12407,24 +12594,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12407 | defer child_block.instructions.deinit(gpa); | 12594 | defer child_block.instructions.deinit(gpa); |
| 12408 | defer merges.deinit(gpa); | 12595 | defer merges.deinit(gpa); |
| 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 | if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline) { | 12597 | if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline) { |
| 12429 | if (empty_enum) { | 12598 | if (empty_enum) { |
| 12430 | return .void_value; | 12599 | return .void_value; |
| ... | @@ -12432,54 +12601,90 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12432,54 +12601,90 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12432 | if (special_prong == .none) { | 12601 | if (special_prong == .none) { |
| 12433 | return sema.fail(block, src, "switch must handle all possibilities", .{}); | 12602 | return sema.fail(block, src, "switch must handle all possibilities", .{}); |
| 12434 | } | 12603 | } |
| 12435 | if (err_set and try sema.maybeErrorUnwrap(block, special.body, operand, operand_src, false)) { | 12604 | const init_cond = switch (operand) { |
| 12436 | return .unreachable_value; | 12605 | .simple => |s| s.cond, |
| 12437 | } | 12606 | .loop => |l| l.init_cond, |
| 12438 | if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and operand_ty.zigTypeTag(zcu) == .@"enum" and | 12607 | }; |
| 12439 | (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally)) | 12608 | if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and |
| 12609 | raw_operand_ty.zigTypeTag(zcu) == .@"enum" and !raw_operand_ty.isNonexhaustiveEnum(zcu)) | ||
| 12440 | { | 12610 | { |
| 12441 | try sema.zirDbgStmt(block, cond_dbg_node_index); | 12611 | try sema.zirDbgStmt(block, cond_dbg_node_index); |
| 12442 | const ok = try block.addUnOp(.is_named_enum_value, operand); | 12612 | const ok = try block.addUnOp(.is_named_enum_value, init_cond); |
| 12443 | try sema.addSafetyCheck(block, src, ok, .corrupt_switch); | 12613 | try sema.addSafetyCheck(block, src, ok, .corrupt_switch); |
| 12444 | } | 12614 | } |
| 12615 | if (err_set and try sema.maybeErrorUnwrap(block, special.body, init_cond, operand_src, false)) { | ||
| 12616 | return .unreachable_value; | ||
| 12617 | } | ||
| 12618 | } | ||
| 12445 | 12619 | ||
| 12446 | return spa.resolveProngComptime( | 12620 | switch (operand) { |
| 12447 | &child_block, | 12621 | .loop => {}, // always runtime; evaluation in comptime scope uses `simple` |
| 12448 | .special, | 12622 | .simple => |s| { |
| 12449 | special.body, | 12623 | if (try sema.resolveDefinedValue(&child_block, src, s.cond)) |cond_val| { |
| 12450 | special.capture, | 12624 | return resolveSwitchComptimeLoop( |
| 12451 | block.src(.{ .switch_capture = .{ | 12625 | sema, |
| 12452 | .switch_node_offset = src_node_offset, | 12626 | spa, |
| 12453 | .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, | 12627 | &child_block, |
| 12454 | } }), | 12628 | if (operand_is_ref) |
| 12455 | undefined, // case_vals may be undefined for special prongs | 12629 | sema.typeOf(s.by_ref) |
| 12456 | .none, | 12630 | else |
| 12457 | false, | 12631 | raw_operand_ty, |
| 12458 | merges, | 12632 | cond_ty, |
| 12459 | ); | 12633 | cond_val, |
| 12634 | src_node_offset, | ||
| 12635 | special, | ||
| 12636 | case_vals, | ||
| 12637 | scalar_cases_len, | ||
| 12638 | multi_cases_len, | ||
| 12639 | err_set, | ||
| 12640 | empty_enum, | ||
| 12641 | operand_is_ref, | ||
| 12642 | ); | ||
| 12643 | } | ||
| 12644 | |||
| 12645 | if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline and !extra.data.bits.has_continue) { | ||
| 12646 | return spa.resolveProngComptime( | ||
| 12647 | &child_block, | ||
| 12648 | .special, | ||
| 12649 | special.body, | ||
| 12650 | special.capture, | ||
| 12651 | block.src(.{ .switch_capture = .{ | ||
| 12652 | .switch_node_offset = src_node_offset, | ||
| 12653 | .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, | ||
| 12654 | } }), | ||
| 12655 | undefined, // case_vals may be undefined for special prongs | ||
| 12656 | .none, | ||
| 12657 | false, | ||
| 12658 | merges, | ||
| 12659 | ); | ||
| 12660 | } | ||
| 12661 | }, | ||
| 12460 | } | 12662 | } |
| 12461 | 12663 | ||
| 12462 | if (child_block.is_comptime) { | 12664 | if (child_block.is_comptime) { |
| 12463 | _ = try sema.resolveConstDefinedValue(&child_block, operand_src, operand, .{ | 12665 | _ = try sema.resolveConstDefinedValue(&child_block, operand_src, operand.simple.cond, .{ |
| 12464 | .needed_comptime_reason = "condition in comptime switch must be comptime-known", | 12666 | .needed_comptime_reason = "condition in comptime switch must be comptime-known", |
| 12465 | .block_comptime_reason = child_block.comptime_reason, | 12667 | .block_comptime_reason = child_block.comptime_reason, |
| 12466 | }); | 12668 | }); |
| 12467 | unreachable; | 12669 | unreachable; |
| 12468 | } | 12670 | } |
| 12469 | 12671 | ||
| 12470 | _ = try sema.analyzeSwitchRuntimeBlock( | 12672 | const air_switch_ref = try sema.analyzeSwitchRuntimeBlock( |
| 12471 | spa, | 12673 | spa, |
| 12472 | &child_block, | 12674 | &child_block, |
| 12473 | src, | 12675 | src, |
| 12474 | operand, | 12676 | switch (operand) { |
| 12475 | operand_ty, | 12677 | .simple => |s| s.cond, |
| 12678 | .loop => |l| l.init_cond, | ||
| 12679 | }, | ||
| 12680 | cond_ty, | ||
| 12476 | operand_src, | 12681 | operand_src, |
| 12477 | case_vals, | 12682 | case_vals, |
| 12478 | special, | 12683 | special, |
| 12479 | scalar_cases_len, | 12684 | scalar_cases_len, |
| 12480 | multi_cases_len, | 12685 | multi_cases_len, |
| 12481 | union_originally, | 12686 | union_originally, |
| 12482 | maybe_union_ty, | 12687 | raw_operand_ty, |
| 12483 | err_set, | 12688 | err_set, |
| 12484 | src_node_offset, | 12689 | src_node_offset, |
| 12485 | special_prong_src, | 12690 | special_prong_src, |
| ... | @@ -12492,6 +12697,67 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -12492,6 +12697,67 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 12492 | false, | 12697 | false, |
| 12493 | ); | 12698 | ); |
| 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 | return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false); | 12761 | return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false); |
| 12496 | } | 12762 | } |
| 12497 | 12763 | ||
| ... | @@ -13123,7 +13389,7 @@ fn analyzeSwitchRuntimeBlock( | ... | @@ -13123,7 +13389,7 @@ fn analyzeSwitchRuntimeBlock( |
| 13123 | sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_body)); | 13389 | sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_body)); |
| 13124 | 13390 | ||
| 13125 | return try child_block.addInst(.{ | 13391 | return try child_block.addInst(.{ |
| 13126 | .tag = .switch_br, | 13392 | .tag = if (spa.operand == .loop) .loop_switch_br else .switch_br, |
| 13127 | .data = .{ .pl_op = .{ | 13393 | .data = .{ .pl_op = .{ |
| 13128 | .operand = operand, | 13394 | .operand = operand, |
| 13129 | .payload = payload_index, | 13395 | .payload = payload_index, |
| ... | @@ -13131,6 +13397,77 @@ fn analyzeSwitchRuntimeBlock( | ... | @@ -13131,6 +13397,77 @@ fn analyzeSwitchRuntimeBlock( |
| 13131 | }); | 13397 | }); |
| 13132 | } | 13398 | } |
| 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 | fn resolveSwitchComptime( | 13471 | fn resolveSwitchComptime( |
| 13135 | sema: *Sema, | 13472 | sema: *Sema, |
| 13136 | spa: SwitchProngAnalysis, | 13473 | spa: SwitchProngAnalysis, |
| ... | @@ -13148,6 +13485,7 @@ fn resolveSwitchComptime( | ... | @@ -13148,6 +13485,7 @@ fn resolveSwitchComptime( |
| 13148 | ) CompileError!Air.Inst.Ref { | 13485 | ) CompileError!Air.Inst.Ref { |
| 13149 | const merges = &child_block.label.?.merges; | 13486 | const merges = &child_block.label.?.merges; |
| 13150 | const resolved_operand_val = try sema.resolveLazyValue(operand_val); | 13487 | const resolved_operand_val = try sema.resolveLazyValue(operand_val); |
| 13488 | |||
| 13151 | var extra_index: usize = special.end; | 13489 | var extra_index: usize = special.end; |
| 13152 | { | 13490 | { |
| 13153 | var scalar_i: usize = 0; | 13491 | var scalar_i: usize = 0; |
src/Value.zig+1| ... | @@ -292,6 +292,7 @@ pub fn getUnsignedIntInner( | ... | @@ -292,6 +292,7 @@ pub fn getUnsignedIntInner( |
| 292 | .none => 0, | 292 | .none => 0, |
| 293 | else => |payload| Value.fromInterned(payload).getUnsignedIntInner(strat, zcu, tid), | 293 | else => |payload| Value.fromInterned(payload).getUnsignedIntInner(strat, zcu, tid), |
| 294 | }, | 294 | }, |
| 295 | .enum_tag => |enum_tag| return Value.fromInterned(enum_tag.int).getUnsignedIntInner(strat, zcu, tid), | ||
| 295 | else => null, | 296 | else => null, |
| 296 | }, | 297 | }, |
| 297 | }; | 298 | }; |
src/arch/aarch64/CodeGen.zig+2| ... | @@ -735,6 +735,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -735,6 +735,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 735 | .block => try self.airBlock(inst), | 735 | .block => try self.airBlock(inst), |
| 736 | .br => try self.airBr(inst), | 736 | .br => try self.airBr(inst), |
| 737 | .repeat => return self.fail("TODO implement `repeat`", .{}), | 737 | .repeat => return self.fail("TODO implement `repeat`", .{}), |
| 738 | .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}), | ||
| 738 | .trap => try self.airTrap(), | 739 | .trap => try self.airTrap(), |
| 739 | .breakpoint => try self.airBreakpoint(), | 740 | .breakpoint => try self.airBreakpoint(), |
| 740 | .ret_addr => try self.airRetAddr(inst), | 741 | .ret_addr => try self.airRetAddr(inst), |
| ... | @@ -825,6 +826,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -825,6 +826,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 825 | .field_parent_ptr => try self.airFieldParentPtr(inst), | 826 | .field_parent_ptr => try self.airFieldParentPtr(inst), |
| 826 | 827 | ||
| 827 | .switch_br => try self.airSwitch(inst), | 828 | .switch_br => try self.airSwitch(inst), |
| 829 | .loop_switch_br => return self.fail("TODO implement `loop_switch_br`", .{}), | ||
| 828 | .slice_ptr => try self.airSlicePtr(inst), | 830 | .slice_ptr => try self.airSlicePtr(inst), |
| 829 | .slice_len => try self.airSliceLen(inst), | 831 | .slice_len => try self.airSliceLen(inst), |
| 830 | 832 |
src/arch/arm/CodeGen.zig+2| ... | @@ -722,6 +722,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -722,6 +722,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 722 | .block => try self.airBlock(inst), | 722 | .block => try self.airBlock(inst), |
| 723 | .br => try self.airBr(inst), | 723 | .br => try self.airBr(inst), |
| 724 | .repeat => return self.fail("TODO implement `repeat`", .{}), | 724 | .repeat => return self.fail("TODO implement `repeat`", .{}), |
| 725 | .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}), | ||
| 725 | .trap => try self.airTrap(), | 726 | .trap => try self.airTrap(), |
| 726 | .breakpoint => try self.airBreakpoint(), | 727 | .breakpoint => try self.airBreakpoint(), |
| 727 | .ret_addr => try self.airRetAddr(inst), | 728 | .ret_addr => try self.airRetAddr(inst), |
| ... | @@ -812,6 +813,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -812,6 +813,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 812 | .field_parent_ptr => try self.airFieldParentPtr(inst), | 813 | .field_parent_ptr => try self.airFieldParentPtr(inst), |
| 813 | 814 | ||
| 814 | .switch_br => try self.airSwitch(inst), | 815 | .switch_br => try self.airSwitch(inst), |
| 816 | .loop_switch_br => return self.fail("TODO implement `loop_switch_br`", .{}), | ||
| 815 | .slice_ptr => try self.airSlicePtr(inst), | 817 | .slice_ptr => try self.airSlicePtr(inst), |
| 816 | .slice_len => try self.airSliceLen(inst), | 818 | .slice_len => try self.airSliceLen(inst), |
| 817 | 819 |
src/arch/riscv64/CodeGen.zig+2| ... | @@ -1580,6 +1580,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -1580,6 +1580,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { |
| 1580 | .block => try func.airBlock(inst), | 1580 | .block => try func.airBlock(inst), |
| 1581 | .br => try func.airBr(inst), | 1581 | .br => try func.airBr(inst), |
| 1582 | .repeat => return func.fail("TODO implement `repeat`", .{}), | 1582 | .repeat => return func.fail("TODO implement `repeat`", .{}), |
| 1583 | .switch_dispatch => return func.fail("TODO implement `switch_dispatch`", .{}), | ||
| 1583 | .trap => try func.airTrap(), | 1584 | .trap => try func.airTrap(), |
| 1584 | .breakpoint => try func.airBreakpoint(), | 1585 | .breakpoint => try func.airBreakpoint(), |
| 1585 | .ret_addr => try func.airRetAddr(inst), | 1586 | .ret_addr => try func.airRetAddr(inst), |
| ... | @@ -1669,6 +1670,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -1669,6 +1670,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { |
| 1669 | .field_parent_ptr => try func.airFieldParentPtr(inst), | 1670 | .field_parent_ptr => try func.airFieldParentPtr(inst), |
| 1670 | 1671 | ||
| 1671 | .switch_br => try func.airSwitchBr(inst), | 1672 | .switch_br => try func.airSwitchBr(inst), |
| 1673 | .loop_switch_br => return func.fail("TODO implement `loop_switch_br`", .{}), | ||
| 1672 | 1674 | ||
| 1673 | .ptr_slice_len_ptr => try func.airPtrSliceLenPtr(inst), | 1675 | .ptr_slice_len_ptr => try func.airPtrSliceLenPtr(inst), |
| 1674 | .ptr_slice_ptr_ptr => try func.airPtrSlicePtrPtr(inst), | 1676 | .ptr_slice_ptr_ptr => try func.airPtrSlicePtrPtr(inst), |
src/arch/sparc64/CodeGen.zig+2| ... | @@ -577,6 +577,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -577,6 +577,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 577 | .block => try self.airBlock(inst), | 577 | .block => try self.airBlock(inst), |
| 578 | .br => try self.airBr(inst), | 578 | .br => try self.airBr(inst), |
| 579 | .repeat => return self.fail("TODO implement `repeat`", .{}), | 579 | .repeat => return self.fail("TODO implement `repeat`", .{}), |
| 580 | .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}), | ||
| 580 | .trap => try self.airTrap(), | 581 | .trap => try self.airTrap(), |
| 581 | .breakpoint => try self.airBreakpoint(), | 582 | .breakpoint => try self.airBreakpoint(), |
| 582 | .ret_addr => @panic("TODO try self.airRetAddr(inst)"), | 583 | .ret_addr => @panic("TODO try self.airRetAddr(inst)"), |
| ... | @@ -667,6 +668,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -667,6 +668,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 667 | .field_parent_ptr => @panic("TODO try self.airFieldParentPtr(inst)"), | 668 | .field_parent_ptr => @panic("TODO try self.airFieldParentPtr(inst)"), |
| 668 | 669 | ||
| 669 | .switch_br => try self.airSwitch(inst), | 670 | .switch_br => try self.airSwitch(inst), |
| 671 | .loop_switch_br => return self.fail("TODO implement `loop_switch_br`", .{}), | ||
| 670 | .slice_ptr => try self.airSlicePtr(inst), | 672 | .slice_ptr => try self.airSlicePtr(inst), |
| 671 | .slice_len => try self.airSliceLen(inst), | 673 | .slice_len => try self.airSliceLen(inst), |
| 672 | 674 |
src/arch/wasm/CodeGen.zig+2| ... | @@ -1904,6 +1904,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -1904,6 +1904,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 1904 | .breakpoint => func.airBreakpoint(inst), | 1904 | .breakpoint => func.airBreakpoint(inst), |
| 1905 | .br => func.airBr(inst), | 1905 | .br => func.airBr(inst), |
| 1906 | .repeat => return func.fail("TODO implement `repeat`", .{}), | 1906 | .repeat => return func.fail("TODO implement `repeat`", .{}), |
| 1907 | .switch_dispatch => return func.fail("TODO implement `switch_dispatch`", .{}), | ||
| 1907 | .int_from_bool => func.airIntFromBool(inst), | 1908 | .int_from_bool => func.airIntFromBool(inst), |
| 1908 | .cond_br => func.airCondBr(inst), | 1909 | .cond_br => func.airCondBr(inst), |
| 1909 | .intcast => func.airIntcast(inst), | 1910 | .intcast => func.airIntcast(inst), |
| ... | @@ -1985,6 +1986,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { | ... | @@ -1985,6 +1986,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 1985 | .field_parent_ptr => func.airFieldParentPtr(inst), | 1986 | .field_parent_ptr => func.airFieldParentPtr(inst), |
| 1986 | 1987 | ||
| 1987 | .switch_br => func.airSwitchBr(inst), | 1988 | .switch_br => func.airSwitchBr(inst), |
| 1989 | .loop_switch_br => return func.fail("TODO implement `loop_switch_br`", .{}), | ||
| 1988 | .trunc => func.airTrunc(inst), | 1990 | .trunc => func.airTrunc(inst), |
| 1989 | .unreach => func.airUnreachable(inst), | 1991 | .unreach => func.airUnreachable(inst), |
| 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,6 +2248,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 2248 | .block => try self.airBlock(inst), | 2248 | .block => try self.airBlock(inst), |
| 2249 | .br => try self.airBr(inst), | 2249 | .br => try self.airBr(inst), |
| 2250 | .repeat => return self.fail("TODO implement `repeat`", .{}), | 2250 | .repeat => return self.fail("TODO implement `repeat`", .{}), |
| 2251 | .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}), | ||
| 2251 | .trap => try self.airTrap(), | 2252 | .trap => try self.airTrap(), |
| 2252 | .breakpoint => try self.airBreakpoint(), | 2253 | .breakpoint => try self.airBreakpoint(), |
| 2253 | .ret_addr => try self.airRetAddr(inst), | 2254 | .ret_addr => try self.airRetAddr(inst), |
| ... | @@ -2336,6 +2337,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -2336,6 +2337,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 2336 | .field_parent_ptr => try self.airFieldParentPtr(inst), | 2337 | .field_parent_ptr => try self.airFieldParentPtr(inst), |
| 2337 | 2338 | ||
| 2338 | .switch_br => try self.airSwitchBr(inst), | 2339 | .switch_br => try self.airSwitchBr(inst), |
| 2340 | .loop_switch_br => return self.fail("TODO implement `loop_switch_br`", .{}), | ||
| 2339 | .slice_ptr => try self.airSlicePtr(inst), | 2341 | .slice_ptr => try self.airSlicePtr(inst), |
| 2340 | .slice_len => try self.airSliceLen(inst), | 2342 | .slice_len => try self.airSliceLen(inst), |
| 2341 | 2343 |
src/codegen/c.zig+105-22| ... | @@ -321,6 +321,9 @@ pub const Function = struct { | ... | @@ -321,6 +321,9 @@ pub const Function = struct { |
| 321 | /// by type alignment. | 321 | /// by type alignment. |
| 322 | /// The value is whether the alloc needs to be emitted in the header. | 322 | /// The value is whether the alloc needs to be emitted in the header. |
| 323 | allocs: std.AutoArrayHashMapUnmanaged(LocalIndex, bool) = .{}, | 323 | allocs: std.AutoArrayHashMapUnmanaged(LocalIndex, bool) = .{}, |
| 324 | /// Maps from `loop_switch_br` instructions to the allocated local used | ||
| 325 | /// for the switch cond. Dispatches should set this local to the new cond. | ||
| 326 | loop_switch_conds: std.AutoHashMapUnmanaged(Air.Inst.Index, LocalIndex) = .{}, | ||
| 324 | 327 | ||
| 325 | fn resolveInst(f: *Function, ref: Air.Inst.Ref) !CValue { | 328 | fn resolveInst(f: *Function, ref: Air.Inst.Ref) !CValue { |
| 326 | const gop = try f.value_map.getOrPut(ref); | 329 | const gop = try f.value_map.getOrPut(ref); |
| ... | @@ -531,6 +534,7 @@ pub const Function = struct { | ... | @@ -531,6 +534,7 @@ pub const Function = struct { |
| 531 | f.blocks.deinit(gpa); | 534 | f.blocks.deinit(gpa); |
| 532 | f.value_map.deinit(); | 535 | f.value_map.deinit(); |
| 533 | f.lazy_fns.deinit(gpa); | 536 | f.lazy_fns.deinit(gpa); |
| 537 | f.loop_switch_conds.deinit(gpa); | ||
| 534 | } | 538 | } |
| 535 | 539 | ||
| 536 | fn typeOf(f: *Function, inst: Air.Inst.Ref) Type { | 540 | fn typeOf(f: *Function, inst: Air.Inst.Ref) Type { |
| ... | @@ -3376,16 +3380,18 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, | ... | @@ -3376,16 +3380,18 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, |
| 3376 | => unreachable, | 3380 | => unreachable, |
| 3377 | 3381 | ||
| 3378 | // Instructions that are known to always be `noreturn` based on their tag. | 3382 | // Instructions that are known to always be `noreturn` based on their tag. |
| 3379 | .br => return airBr(f, inst), | 3383 | .br => return airBr(f, inst), |
| 3380 | .repeat => return airRepeat(f, inst), | 3384 | .repeat => return airRepeat(f, inst), |
| 3381 | .cond_br => return airCondBr(f, inst), | 3385 | .switch_dispatch => return airSwitchDispatch(f, inst), |
| 3382 | .switch_br => return airSwitchBr(f, inst), | 3386 | .cond_br => return airCondBr(f, inst), |
| 3383 | .loop => return airLoop(f, inst), | 3387 | .switch_br => return airSwitchBr(f, inst, false), |
| 3384 | .ret => return airRet(f, inst, false), | 3388 | .loop_switch_br => return airSwitchBr(f, inst, true), |
| 3385 | .ret_safe => return airRet(f, inst, false), // TODO | 3389 | .loop => return airLoop(f, inst), |
| 3386 | .ret_load => return airRet(f, inst, true), | 3390 | .ret => return airRet(f, inst, false), |
| 3387 | .trap => return airTrap(f, f.object.writer()), | 3391 | .ret_safe => return airRet(f, inst, false), // TODO |
| 3388 | .unreach => return airUnreach(f), | 3392 | .ret_load => return airRet(f, inst, true), |
| 3393 | .trap => return airTrap(f, f.object.writer()), | ||
| 3394 | .unreach => return airUnreach(f), | ||
| 3389 | 3395 | ||
| 3390 | // Instructions which may be `noreturn`. | 3396 | // Instructions which may be `noreturn`. |
| 3391 | .block => res: { | 3397 | .block => res: { |
| ... | @@ -4786,6 +4792,46 @@ fn airRepeat(f: *Function, inst: Air.Inst.Index) !void { | ... | @@ -4786,6 +4792,46 @@ fn airRepeat(f: *Function, inst: Air.Inst.Index) !void { |
| 4786 | try writer.print("goto zig_loop_{d};\n", .{@intFromEnum(repeat.loop_inst)}); | 4792 | try writer.print("goto zig_loop_{d};\n", .{@intFromEnum(repeat.loop_inst)}); |
| 4787 | } | 4793 | } |
| 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 | fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue { | 4835 | fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue { |
| 4790 | const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; | 4836 | const ty_op = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 4791 | const inst_ty = f.typeOfIndex(inst); | 4837 | const inst_ty = f.typeOfIndex(inst); |
| ... | @@ -5004,15 +5050,34 @@ fn airCondBr(f: *Function, inst: Air.Inst.Index) !void { | ... | @@ -5004,15 +5050,34 @@ fn airCondBr(f: *Function, inst: Air.Inst.Index) !void { |
| 5004 | try genBodyInner(f, else_body); | 5050 | try genBodyInner(f, else_body); |
| 5005 | } | 5051 | } |
| 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 | const pt = f.object.dg.pt; | 5054 | const pt = f.object.dg.pt; |
| 5009 | const zcu = pt.zcu; | 5055 | const zcu = pt.zcu; |
| 5056 | const gpa = f.object.dg.gpa; | ||
| 5010 | const switch_br = f.air.unwrapSwitch(inst); | 5057 | const switch_br = f.air.unwrapSwitch(inst); |
| 5011 | const condition = try f.resolveInst(switch_br.operand); | 5058 | const init_condition = try f.resolveInst(switch_br.operand); |
| 5012 | try reap(f, inst, &.{switch_br.operand}); | 5059 | try reap(f, inst, &.{switch_br.operand}); |
| 5013 | const condition_ty = f.typeOf(switch_br.operand); | 5060 | const condition_ty = f.typeOf(switch_br.operand); |
| 5014 | const writer = f.object.writer(); | 5061 | const writer = f.object.writer(); |
| 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 | try writer.writeAll("switch ("); | 5081 | try writer.writeAll("switch ("); |
| 5017 | 5082 | ||
| 5018 | const lowered_condition_ty = if (condition_ty.toIntern() == .bool_type) | 5083 | const lowered_condition_ty = if (condition_ty.toIntern() == .bool_type) |
| ... | @@ -5030,7 +5095,6 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void { | ... | @@ -5030,7 +5095,6 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void { |
| 5030 | try writer.writeAll(") {"); | 5095 | try writer.writeAll(") {"); |
| 5031 | f.object.indent_writer.pushIndent(); | 5096 | f.object.indent_writer.pushIndent(); |
| 5032 | 5097 | ||
| 5033 | const gpa = f.object.dg.gpa; | ||
| 5034 | const liveness = try f.liveness.getSwitchBr(gpa, inst, switch_br.cases_len + 1); | 5098 | const liveness = try f.liveness.getSwitchBr(gpa, inst, switch_br.cases_len + 1); |
| 5035 | defer gpa.free(liveness.deaths); | 5099 | defer gpa.free(liveness.deaths); |
| 5036 | 5100 | ||
| ... | @@ -5045,9 +5109,15 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void { | ... | @@ -5045,9 +5109,15 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void { |
| 5045 | try f.object.indent_writer.insertNewline(); | 5109 | try f.object.indent_writer.insertNewline(); |
| 5046 | try writer.writeAll("case "); | 5110 | try writer.writeAll("case "); |
| 5047 | const item_value = try f.air.value(item, pt); | 5111 | const item_value = try f.air.value(item, pt); |
| 5048 | if (item_value.?.getUnsignedInt(zcu)) |item_int| try writer.print("{}\n", .{ | 5112 | // If `item_value` is a pointer with a known integer address, print the address |
| 5049 | try f.fmtIntLiteral(try pt.intValue(lowered_condition_ty, item_int)), | 5113 | // with no cast to avoid a warning. |
| 5050 | }) else { | 5114 | write_val: { |
| 5115 | if (condition_ty.isPtrAtRuntime(zcu)) { | ||
| 5116 | if (item_value.?.getUnsignedInt(zcu)) |item_int| { | ||
| 5117 | try writer.print("{}", .{try f.fmtIntLiteral(try pt.intValue(lowered_condition_ty, item_int))}); | ||
| 5118 | break :write_val; | ||
| 5119 | } | ||
| 5120 | } | ||
| 5051 | if (condition_ty.isPtrAtRuntime(zcu)) { | 5121 | if (condition_ty.isPtrAtRuntime(zcu)) { |
| 5052 | try writer.writeByte('('); | 5122 | try writer.writeByte('('); |
| 5053 | try f.renderType(writer, Type.usize); | 5123 | try f.renderType(writer, Type.usize); |
| ... | @@ -5057,9 +5127,14 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void { | ... | @@ -5057,9 +5127,14 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void { |
| 5057 | } | 5127 | } |
| 5058 | try writer.writeByte(':'); | 5128 | try writer.writeByte(':'); |
| 5059 | } | 5129 | } |
| 5060 | try writer.writeByte(' '); | 5130 | try writer.writeAll(" {\n"); |
| 5061 | 5131 | f.object.indent_writer.pushIndent(); | |
| 5062 | try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, false); | 5132 | if (is_dispatch_loop) { |
| 5133 | try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx }); | ||
| 5134 | } | ||
| 5135 | try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true); | ||
| 5136 | f.object.indent_writer.popIndent(); | ||
| 5137 | try writer.writeByte('}'); | ||
| 5063 | 5138 | ||
| 5064 | // The case body must be noreturn so we don't need to insert a break. | 5139 | // The case body must be noreturn so we don't need to insert a break. |
| 5065 | } | 5140 | } |
| ... | @@ -5095,11 +5170,19 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void { | ... | @@ -5095,11 +5170,19 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !void { |
| 5095 | try f.object.dg.renderValue(writer, (try f.air.value(range[1], pt)).?, .Other); | 5170 | try f.object.dg.renderValue(writer, (try f.air.value(range[1], pt)).?, .Other); |
| 5096 | try writer.writeByte(')'); | 5171 | try writer.writeByte(')'); |
| 5097 | } | 5172 | } |
| 5098 | try writer.writeAll(") "); | 5173 | try writer.writeAll(") {\n"); |
| 5099 | try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, false); | 5174 | f.object.indent_writer.pushIndent(); |
| 5175 | if (is_dispatch_loop) { | ||
| 5176 | try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx }); | ||
| 5177 | } | ||
| 5178 | try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true); | ||
| 5179 | f.object.indent_writer.popIndent(); | ||
| 5180 | try writer.writeByte('}'); | ||
| 5100 | } | 5181 | } |
| 5101 | } | 5182 | } |
| 5102 | 5183 | if (is_dispatch_loop) { | |
| 5184 | try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), switch_br.cases_len }); | ||
| 5185 | } | ||
| 5103 | if (else_body.len > 0) { | 5186 | if (else_body.len > 0) { |
| 5104 | // Note that this must be the last case, so we do not need to use `genBodyResolveState` since | 5187 | // Note that this must be the last case, so we do not need to use `genBodyResolveState` since |
| 5105 | // the parent block will do it (because the case body is noreturn). | 5188 | // the parent block will do it (because the case body is noreturn). |
src/codegen/llvm.zig+404-84| ... | @@ -1721,6 +1721,7 @@ pub const Object = struct { | ... | @@ -1721,6 +1721,7 @@ pub const Object = struct { |
| 1721 | .func_inst_table = .{}, | 1721 | .func_inst_table = .{}, |
| 1722 | .blocks = .{}, | 1722 | .blocks = .{}, |
| 1723 | .loops = .{}, | 1723 | .loops = .{}, |
| 1724 | .switch_dispatch_info = .{}, | ||
| 1724 | .sync_scope = if (owner_mod.single_threaded) .singlethread else .system, | 1725 | .sync_scope = if (owner_mod.single_threaded) .singlethread else .system, |
| 1725 | .file = file, | 1726 | .file = file, |
| 1726 | .scope = subprogram, | 1727 | .scope = subprogram, |
| ... | @@ -4845,6 +4846,10 @@ pub const FuncGen = struct { | ... | @@ -4845,6 +4846,10 @@ pub const FuncGen = struct { |
| 4845 | /// Maps `loop` instructions to the bb to branch to to repeat the loop. | 4846 | /// Maps `loop` instructions to the bb to branch to to repeat the loop. |
| 4846 | loops: std.AutoHashMapUnmanaged(Air.Inst.Index, Builder.Function.Block.Index), | 4847 | loops: std.AutoHashMapUnmanaged(Air.Inst.Index, Builder.Function.Block.Index), |
| 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 | sync_scope: Builder.SyncScope, | 4853 | sync_scope: Builder.SyncScope, |
| 4849 | 4854 | ||
| 4850 | const Fuzz = struct { | 4855 | const Fuzz = struct { |
| ... | @@ -4857,6 +4862,33 @@ pub const FuncGen = struct { | ... | @@ -4857,6 +4862,33 @@ pub const FuncGen = struct { |
| 4857 | } | 4862 | } |
| 4858 | }; | 4863 | }; |
| 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 | const BreakList = union { | 4892 | const BreakList = union { |
| 4861 | list: std.MultiArrayList(struct { | 4893 | list: std.MultiArrayList(struct { |
| 4862 | bb: Builder.Function.Block.Index, | 4894 | bb: Builder.Function.Block.Index, |
| ... | @@ -4872,6 +4904,11 @@ pub const FuncGen = struct { | ... | @@ -4872,6 +4904,11 @@ pub const FuncGen = struct { |
| 4872 | self.func_inst_table.deinit(gpa); | 4904 | self.func_inst_table.deinit(gpa); |
| 4873 | self.blocks.deinit(gpa); | 4905 | self.blocks.deinit(gpa); |
| 4874 | self.loops.deinit(gpa); | 4906 | self.loops.deinit(gpa); |
| 4907 | var it = self.switch_dispatch_info.valueIterator(); | ||
| 4908 | while (it.next()) |info| { | ||
| 4909 | self.gpa.free(info.case_blocks); | ||
| 4910 | } | ||
| 4911 | self.switch_dispatch_info.deinit(gpa); | ||
| 4875 | } | 4912 | } |
| 4876 | 4913 | ||
| 4877 | fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error { | 4914 | fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error { |
| ... | @@ -5182,16 +5219,18 @@ pub const FuncGen = struct { | ... | @@ -5182,16 +5219,18 @@ pub const FuncGen = struct { |
| 5182 | .work_group_id => try self.airWorkGroupId(inst), | 5219 | .work_group_id => try self.airWorkGroupId(inst), |
| 5183 | 5220 | ||
| 5184 | // Instructions that are known to always be `noreturn` based on their tag. | 5221 | // Instructions that are known to always be `noreturn` based on their tag. |
| 5185 | .br => return self.airBr(inst), | 5222 | .br => return self.airBr(inst), |
| 5186 | .repeat => return self.airRepeat(inst), | 5223 | .repeat => return self.airRepeat(inst), |
| 5187 | .cond_br => return self.airCondBr(inst), | 5224 | .switch_dispatch => return self.airSwitchDispatch(inst), |
| 5188 | .switch_br => return self.airSwitchBr(inst), | 5225 | .cond_br => return self.airCondBr(inst), |
| 5189 | .loop => return self.airLoop(inst), | 5226 | .switch_br => return self.airSwitchBr(inst, false), |
| 5190 | .ret => return self.airRet(inst, false), | 5227 | .loop_switch_br => return self.airSwitchBr(inst, true), |
| 5191 | .ret_safe => return self.airRet(inst, true), | 5228 | .loop => return self.airLoop(inst), |
| 5192 | .ret_load => return self.airRetLoad(inst), | 5229 | .ret => return self.airRet(inst, false), |
| 5193 | .trap => return self.airTrap(inst), | 5230 | .ret_safe => return self.airRet(inst, true), |
| 5194 | .unreach => return self.airUnreach(inst), | 5231 | .ret_load => return self.airRetLoad(inst), |
| 5232 | .trap => return self.airTrap(inst), | ||
| 5233 | .unreach => return self.airUnreach(inst), | ||
| 5195 | 5234 | ||
| 5196 | // Instructions which may be `noreturn`. | 5235 | // Instructions which may be `noreturn`. |
| 5197 | .block => res: { | 5236 | .block => res: { |
| ... | @@ -6093,6 +6132,202 @@ pub const FuncGen = struct { | ... | @@ -6093,6 +6132,202 @@ pub const FuncGen = struct { |
| 6093 | _ = try self.wip.br(loop_bb); | 6132 | _ = try self.wip.br(loop_bb); |
| 6094 | } | 6133 | } |
| 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 | fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !void { | 6331 | fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !void { |
| 6097 | const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; | 6332 | const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; |
| 6098 | const cond = try self.resolveInst(pl_op.operand); | 6333 | const cond = try self.resolveInst(pl_op.operand); |
| ... | @@ -6257,36 +6492,123 @@ pub const FuncGen = struct { | ... | @@ -6257,36 +6492,123 @@ pub const FuncGen = struct { |
| 6257 | return fg.wip.extractValue(err_union, &.{offset}, ""); | 6492 | return fg.wip.extractValue(err_union, &.{offset}, ""); |
| 6258 | } | 6493 | } |
| 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 | const o = self.ng.object; | 6496 | const o = self.ng.object; |
| 6497 | const zcu = o.pt.zcu; | ||
| 6262 | 6498 | ||
| 6263 | const switch_br = self.air.unwrapSwitch(inst); | 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 | 6531 | const cond_ty = self.typeOf(switch_br.operand); |
| 6268 | // for any range cases. It's just the `else` of the LLVM switch. | 6532 | switch (cond_ty.zigTypeTag(zcu)) { |
| 6269 | const llvm_else_block = try self.wip.block(1, "Default"); | 6533 | .bool, .pointer => break :jmp_table null, |
| 6534 | .@"enum", .int, .error_set => {}, | ||
| 6535 | else => unreachable, | ||
| 6536 | } | ||
| 6270 | 6537 | ||
| 6271 | const case_blocks = try self.gpa.alloc(Builder.Function.Block.Index, switch_br.cases_len); | 6538 | if (cond_ty.intInfo(zcu).signedness == .signed) break :jmp_table null; |
| 6272 | defer self.gpa.free(case_blocks); | ||
| 6273 | // We set incoming as 0 for now, and increment it as we construct the switch. | ||
| 6274 | for (case_blocks) |*b| b.* = try self.wip.block(0, "Case"); | ||
| 6275 | 6539 | ||
| 6276 | const llvm_usize = try o.lowerType(Type.usize); | 6540 | // Don't worry about the size of the type -- it's irrelevant, because the prong values could be fairly dense. |
| 6277 | const cond_int = if (cond.typeOfWip(&self.wip).isPointer(&o.builder)) | 6541 | // If they are, then we will construct a jump table. |
| 6278 | try self.wip.cast(.ptrtoint, cond, llvm_usize, "") | 6542 | const min, const max = self.switchCaseItemRange(switch_br); |
| 6279 | else | 6543 | const min_int = min.getUnsignedInt(zcu) orelse break :jmp_table null; |
| 6280 | cond; | 6544 | const max_int = max.getUnsignedInt(zcu) orelse break :jmp_table null; |
| 6545 | const table_len = max_int - min_int + 1; | ||
| 6546 | if (table_len > max_table_len) break :jmp_table null; | ||
| 6547 | |||
| 6548 | const table_elems = try self.gpa.alloc(Builder.Constant, @intCast(table_len)); | ||
| 6549 | defer self.gpa.free(table_elems); | ||
| 6281 | 6550 | ||
| 6282 | const llvm_cases_len = llvm_cases_len: { | 6551 | // Set them all to the `else` branch, then iterate over the AIR switch |
| 6283 | var len: u32 = 0; | 6552 | // and replace all values which correspond to other prongs. |
| 6553 | @memset(table_elems, try o.builder.blockAddrConst( | ||
| 6554 | self.wip.function, | ||
| 6555 | case_blocks[case_blocks.len - 1], | ||
| 6556 | )); | ||
| 6557 | var item_count: u32 = 0; | ||
| 6284 | var it = switch_br.iterateCases(); | 6558 | var it = switch_br.iterateCases(); |
| 6285 | while (it.next()) |case| len += @intCast(case.items.len); | 6559 | while (it.next()) |case| { |
| 6286 | break :llvm_cases_len len; | 6560 | const case_block = case_blocks[case.idx]; |
| 6561 | const case_block_addr = try o.builder.blockAddrConst( | ||
| 6562 | self.wip.function, | ||
| 6563 | case_block, | ||
| 6564 | ); | ||
| 6565 | for (case.items) |item| { | ||
| 6566 | const val = Value.fromInterned(item.toInterned().?); | ||
| 6567 | const table_idx = val.toUnsignedInt(zcu) - min_int; | ||
| 6568 | table_elems[@intCast(table_idx)] = case_block_addr; | ||
| 6569 | item_count += 1; | ||
| 6570 | } | ||
| 6571 | for (case.ranges) |range| { | ||
| 6572 | const low = Value.fromInterned(range[0].toInterned().?); | ||
| 6573 | const high = Value.fromInterned(range[1].toInterned().?); | ||
| 6574 | const low_idx = low.toUnsignedInt(zcu) - min_int; | ||
| 6575 | const high_idx = high.toUnsignedInt(zcu) - min_int; | ||
| 6576 | @memset(table_elems[@intCast(low_idx)..@intCast(high_idx + 1)], case_block_addr); | ||
| 6577 | item_count += @intCast(high_idx + 1 - low_idx); | ||
| 6578 | } | ||
| 6579 | } | ||
| 6580 | |||
| 6581 | const table_llvm_ty = try o.builder.arrayType(table_elems.len, .ptr); | ||
| 6582 | const table_val = try o.builder.arrayConst(table_llvm_ty, table_elems); | ||
| 6583 | |||
| 6584 | const table_variable = try o.builder.addVariable( | ||
| 6585 | try o.builder.strtabStringFmt("__jmptab_{d}", .{@intFromEnum(inst)}), | ||
| 6586 | table_llvm_ty, | ||
| 6587 | .default, | ||
| 6588 | ); | ||
| 6589 | try table_variable.setInitializer(table_val, &o.builder); | ||
| 6590 | table_variable.setLinkage(.internal, &o.builder); | ||
| 6591 | table_variable.setUnnamedAddr(.unnamed_addr, &o.builder); | ||
| 6592 | |||
| 6593 | const table_includes_else = item_count != table_len; | ||
| 6594 | |||
| 6595 | break :jmp_table .{ | ||
| 6596 | .min = try o.lowerValue(min.toIntern()), | ||
| 6597 | .max = try o.lowerValue(max.toIntern()), | ||
| 6598 | .in_bounds_hint = if (table_includes_else) .none else switch (switch_br.getElseHint()) { | ||
| 6599 | .none, .cold => .none, | ||
| 6600 | .unpredictable => .unpredictable, | ||
| 6601 | .likely => .likely, | ||
| 6602 | .unlikely => .unlikely, | ||
| 6603 | }, | ||
| 6604 | .table = table_variable.toConst(&o.builder), | ||
| 6605 | .table_includes_else = table_includes_else, | ||
| 6606 | }; | ||
| 6287 | }; | 6607 | }; |
| 6288 | 6608 | ||
| 6289 | const weights: Builder.Function.Instruction.BrCond.Weights = weights: { | 6609 | const weights: Builder.Function.Instruction.BrCond.Weights = weights: { |
| 6610 | if (jmp_table != null) break :weights .none; // not used | ||
| 6611 | |||
| 6290 | // First pass. If any weights are `.unpredictable`, unpredictable. | 6612 | // First pass. If any weights are `.unpredictable`, unpredictable. |
| 6291 | // If all are `.none` or `.cold`, none. | 6613 | // If all are `.none` or `.cold`, none. |
| 6292 | var any_likely = false; | 6614 | var any_likely = false; |
| ... | @@ -6304,6 +6626,13 @@ pub const FuncGen = struct { | ... | @@ -6304,6 +6626,13 @@ pub const FuncGen = struct { |
| 6304 | } | 6626 | } |
| 6305 | if (!any_likely) break :weights .none; | 6627 | if (!any_likely) break :weights .none; |
| 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 | var weights = try self.gpa.alloc(Builder.Metadata, llvm_cases_len + 1); | 6636 | var weights = try self.gpa.alloc(Builder.Metadata, llvm_cases_len + 1); |
| 6308 | defer self.gpa.free(weights); | 6637 | defer self.gpa.free(weights); |
| 6309 | 6638 | ||
| ... | @@ -6336,75 +6665,66 @@ pub const FuncGen = struct { | ... | @@ -6336,75 +6665,66 @@ pub const FuncGen = struct { |
| 6336 | break :weights @enumFromInt(@intFromEnum(tuple)); | 6665 | break :weights @enumFromInt(@intFromEnum(tuple)); |
| 6337 | }; | 6666 | }; |
| 6338 | 6667 | ||
| 6339 | var wip_switch = try self.wip.@"switch"(cond_int, llvm_else_block, llvm_cases_len, weights); | 6668 | const dispatch_info: SwitchDispatchInfo = .{ |
| 6340 | defer wip_switch.finish(&self.wip); | 6669 | .case_blocks = case_blocks, |
| 6670 | .switch_weights = weights, | ||
| 6671 | .jmp_table = jmp_table, | ||
| 6672 | }; | ||
| 6673 | |||
| 6674 | if (is_dispatch_loop) { | ||
| 6675 | try self.switch_dispatch_info.putNoClobber(self.gpa, inst, dispatch_info); | ||
| 6676 | } | ||
| 6677 | defer if (is_dispatch_loop) { | ||
| 6678 | assert(self.switch_dispatch_info.remove(inst)); | ||
| 6679 | }; | ||
| 6680 | |||
| 6681 | // Generate the initial dispatch. | ||
| 6682 | // If this is a simple `switch_br`, this is the only dispatch. | ||
| 6683 | try self.lowerSwitchDispatch(inst, switch_br.operand, dispatch_info); | ||
| 6341 | 6684 | ||
| 6685 | // Iterate the cases and generate their bodies. | ||
| 6342 | var it = switch_br.iterateCases(); | 6686 | var it = switch_br.iterateCases(); |
| 6343 | var any_ranges = false; | ||
| 6344 | while (it.next()) |case| { | 6687 | while (it.next()) |case| { |
| 6345 | if (case.ranges.len > 0) any_ranges = true; | ||
| 6346 | const case_block = case_blocks[case.idx]; | 6688 | const case_block = case_blocks[case.idx]; |
| 6347 | case_block.ptr(&self.wip).incoming += @intCast(case.items.len); | ||
| 6348 | // Handle scalar items, and generate the block. | ||
| 6349 | // We'll generate conditionals for the ranges later on. | ||
| 6350 | for (case.items) |item| { | ||
| 6351 | const llvm_item = (try self.resolveInst(item)).toConst().?; | ||
| 6352 | const llvm_int_item = if (llvm_item.typeOf(&o.builder).isPointer(&o.builder)) | ||
| 6353 | try o.builder.castConst(.ptrtoint, llvm_item, llvm_usize) | ||
| 6354 | else | ||
| 6355 | llvm_item; | ||
| 6356 | try wip_switch.addCase(llvm_int_item, case_block, &self.wip); | ||
| 6357 | } | ||
| 6358 | self.wip.cursor = .{ .block = case_block }; | 6689 | self.wip.cursor = .{ .block = case_block }; |
| 6359 | if (switch_br.getHint(case.idx) == .cold) _ = try self.wip.callIntrinsicAssumeCold(); | 6690 | if (switch_br.getHint(case.idx) == .cold) _ = try self.wip.callIntrinsicAssumeCold(); |
| 6360 | try self.genBodyDebugScope(null, case.body, .poi); | 6691 | try self.genBodyDebugScope(null, case.body, .none); |
| 6361 | } | 6692 | } |
| 6362 | 6693 | self.wip.cursor = .{ .block = case_blocks[case_blocks.len - 1] }; | |
| 6363 | const else_body = it.elseBody(); | 6694 | const else_body = it.elseBody(); |
| 6364 | self.wip.cursor = .{ .block = llvm_else_block }; | ||
| 6365 | if (any_ranges) { | ||
| 6366 | const cond_ty = self.typeOf(switch_br.operand); | ||
| 6367 | // Add conditionals for the ranges, directing to the relevant bb. | ||
| 6368 | // We don't need to consider `cold` branch hints since that information is stored | ||
| 6369 | // in the target bb body, but we do care about likely/unlikely/unpredictable. | ||
| 6370 | it = switch_br.iterateCases(); | ||
| 6371 | while (it.next()) |case| { | ||
| 6372 | if (case.ranges.len == 0) continue; | ||
| 6373 | const case_block = case_blocks[case.idx]; | ||
| 6374 | const hint = switch_br.getHint(case.idx); | ||
| 6375 | case_block.ptr(&self.wip).incoming += 1; | ||
| 6376 | const next_else_block = try self.wip.block(1, "Default"); | ||
| 6377 | var range_cond: ?Builder.Value = null; | ||
| 6378 | for (case.ranges) |range| { | ||
| 6379 | const llvm_min = try self.resolveInst(range[0]); | ||
| 6380 | const llvm_max = try self.resolveInst(range[1]); | ||
| 6381 | const cond_part = try self.wip.bin( | ||
| 6382 | .@"and", | ||
| 6383 | try self.cmp(.normal, .gte, cond_ty, cond, llvm_min), | ||
| 6384 | try self.cmp(.normal, .lte, cond_ty, cond, llvm_max), | ||
| 6385 | "", | ||
| 6386 | ); | ||
| 6387 | if (range_cond) |prev| { | ||
| 6388 | range_cond = try self.wip.bin(.@"or", prev, cond_part, ""); | ||
| 6389 | } else range_cond = cond_part; | ||
| 6390 | } | ||
| 6391 | _ = try self.wip.brCond(range_cond.?, case_block, next_else_block, switch (hint) { | ||
| 6392 | .none, .cold => .none, | ||
| 6393 | .unpredictable => .unpredictable, | ||
| 6394 | .likely => .then_likely, | ||
| 6395 | .unlikely => .else_likely, | ||
| 6396 | }); | ||
| 6397 | self.wip.cursor = .{ .block = next_else_block }; | ||
| 6398 | } | ||
| 6399 | } | ||
| 6400 | if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold(); | 6695 | if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold(); |
| 6401 | if (else_body.len != 0) { | 6696 | if (else_body.len > 0) { |
| 6402 | try self.genBodyDebugScope(null, else_body, .poi); | 6697 | try self.genBodyDebugScope(null, it.elseBody(), .none); |
| 6403 | } else { | 6698 | } else { |
| 6404 | _ = try self.wip.@"unreachable"(); | 6699 | _ = try self.wip.@"unreachable"(); |
| 6405 | } | 6700 | } |
| 6701 | } | ||
| 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 | fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !void { | 6730 | fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !void { |
src/print_air.zig+2-1| ... | @@ -296,11 +296,12 @@ const Writer = struct { | ... | @@ -296,11 +296,12 @@ const Writer = struct { |
| 296 | .aggregate_init => try w.writeAggregateInit(s, inst), | 296 | .aggregate_init => try w.writeAggregateInit(s, inst), |
| 297 | .union_init => try w.writeUnionInit(s, inst), | 297 | .union_init => try w.writeUnionInit(s, inst), |
| 298 | .br => try w.writeBr(s, inst), | 298 | .br => try w.writeBr(s, inst), |
| 299 | .switch_dispatch => try w.writeBr(s, inst), | ||
| 299 | .repeat => try w.writeRepeat(s, inst), | 300 | .repeat => try w.writeRepeat(s, inst), |
| 300 | .cond_br => try w.writeCondBr(s, inst), | 301 | .cond_br => try w.writeCondBr(s, inst), |
| 301 | .@"try", .try_cold => try w.writeTry(s, inst), | 302 | .@"try", .try_cold => try w.writeTry(s, inst), |
| 302 | .try_ptr, .try_ptr_cold => try w.writeTryPtr(s, inst), | 303 | .try_ptr, .try_ptr_cold => try w.writeTryPtr(s, inst), |
| 303 | .switch_br => try w.writeSwitchBr(s, inst), | 304 | .loop_switch_br, .switch_br => try w.writeSwitchBr(s, inst), |
| 304 | .cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst), | 305 | .cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst), |
| 305 | .fence => try w.writeFence(s, inst), | 306 | .fence => try w.writeFence(s, inst), |
| 306 | .atomic_load => try w.writeAtomicLoad(s, inst), | 307 | .atomic_load => try w.writeAtomicLoad(s, inst), |
src/print_zir.zig+1| ... | @@ -302,6 +302,7 @@ const Writer = struct { | ... | @@ -302,6 +302,7 @@ const Writer = struct { |
| 302 | 302 | ||
| 303 | .@"break", | 303 | .@"break", |
| 304 | .break_inline, | 304 | .break_inline, |
| 305 | .switch_continue, | ||
| 305 | => try self.writeBreak(stream, inst), | 306 | => try self.writeBreak(stream, inst), |
| 306 | 307 | ||
| 307 | .slice_start => try self.writeSliceStart(stream, inst), | 308 | .slice_start => try self.writeSliceStart(stream, inst), |
test/behavior.zig+1| ... | @@ -88,6 +88,7 @@ test { | ... | @@ -88,6 +88,7 @@ test { |
| 88 | _ = @import("behavior/struct_contains_null_ptr_itself.zig"); | 88 | _ = @import("behavior/struct_contains_null_ptr_itself.zig"); |
| 89 | _ = @import("behavior/struct_contains_slice_of_itself.zig"); | 89 | _ = @import("behavior/struct_contains_slice_of_itself.zig"); |
| 90 | _ = @import("behavior/switch.zig"); | 90 | _ = @import("behavior/switch.zig"); |
| 91 | _ = @import("behavior/switch_loop.zig"); | ||
| 91 | _ = @import("behavior/switch_prong_err_enum.zig"); | 92 | _ = @import("behavior/switch_prong_err_enum.zig"); |
| 92 | _ = @import("behavior/switch_prong_implicit_cast.zig"); | 93 | _ = @import("behavior/switch_prong_implicit_cast.zig"); |
| 93 | _ = @import("behavior/switch_on_captured_error.zig"); | 94 | _ = @import("behavior/switch_on_captured_error.zig"); |
test/behavior/switch_loop.zig created+205| ... | @@ -0,0 +1,205 @@ | ||
| 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 | } | ||