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