authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-02-27 02:00:55+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-02-29 23:38:17+00:00
logf6abf022b790847e6145569241e4e5685abf359c
tree3058edcd61dcbe4d531c7705da8d3e0566967710
parentf0a4bb6bd15b8a605e450af3359fe1622302463a
signaturelock-open Commit is signed but in an unrecognized format.

AstGen: elide block instruction when already in empty body

In the code `if (cond) { ... }`, the "then body" of the `if` is technically a block. However, we don't need to emit a real ZIR `block` corresponding to it, because we are already within a condbr body; we have a separate gz, and appropriate scoping for allocs and debug variables. In this case, and many like it, we can trivially elide the block here, instead emitting the block statements directly into the current `GenZir`. This results in a significant decrease in ZIR bytes for real code.

1 files changed, 81 insertions(+), 29 deletions(-)

lib/std/zig/AstGen.zig+81-29
......@@ -1232,7 +1232,7 @@ fn suspendExpr(
12321232 suspend_scope.suspend_node = node;
12331233 defer suspend_scope.unstack();
12341234
1235 const body_result = try expr(&suspend_scope, &suspend_scope.base, .{ .rl = .none }, body_node);
1235 const body_result = try fullBodyExpr(&suspend_scope, &suspend_scope.base, .{ .rl = .none }, body_node);
12361236 if (!gz.refIsNoReturn(body_result)) {
12371237 _ = try suspend_scope.addBreak(.break_inline, suspend_inst, .void_value);
12381238 }
......@@ -1353,7 +1353,7 @@ fn fnProtoExpr(
13531353 assert(param_type_node != 0);
13541354 var param_gz = block_scope.makeSubBlock(scope);
13551355 defer param_gz.unstack();
1356 const param_type = try expr(&param_gz, scope, coerced_type_ri, param_type_node);
1356 const param_type = try fullBodyExpr(&param_gz, scope, coerced_type_ri, param_type_node);
13571357 const param_inst_expected: Zir.Inst.Index = @enumFromInt(astgen.instructions.len + 1);
13581358 _ = try param_gz.addBreakWithSrcNode(.break_inline, param_inst_expected, param_type, param_type_node);
13591359 const main_tokens = tree.nodes.items(.main_token);
......@@ -2060,7 +2060,7 @@ fn comptimeExpr(
20602060 else
20612061 .none,
20622062 };
2063 const block_result = try expr(&block_scope, scope, ty_only_ri, node);
2063 const block_result = try fullBodyExpr(&block_scope, scope, ty_only_ri, node);
20642064 if (!gz.refIsNoReturn(block_result)) {
20652065 _ = try block_scope.addBreak(.@"break", block_inst, block_result);
20662066 }
......@@ -2291,6 +2291,53 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
22912291 }
22922292}
22932293
2294/// Similar to `expr`, but intended for use when `gz` corresponds to a body
2295/// which will contain only this node's code. Differs from `expr` in that if the
2296/// root expression is an unlabeled block, does not emit an actual block.
2297/// Instead, the block contents are emitted directly into `gz`.
2298fn fullBodyExpr(
2299 gz: *GenZir,
2300 scope: *Scope,
2301 ri: ResultInfo,
2302 node: Ast.Node.Index,
2303) InnerError!Zir.Inst.Ref {
2304 const tree = gz.astgen.tree;
2305 const node_tags = tree.nodes.items(.tag);
2306 const node_datas = tree.nodes.items(.data);
2307 const main_tokens = tree.nodes.items(.main_token);
2308 const token_tags = tree.tokens.items(.tag);
2309 var stmt_buf: [2]Ast.Node.Index = undefined;
2310 const statements: []const Ast.Node.Index = switch (node_tags[node]) {
2311 else => return expr(gz, scope, ri, node),
2312 .block_two, .block_two_semicolon => if (node_datas[node].lhs == 0) s: {
2313 break :s &.{};
2314 } else if (node_datas[node].rhs == 0) s: {
2315 stmt_buf[0] = node_datas[node].lhs;
2316 break :s stmt_buf[0..1];
2317 } else s: {
2318 stmt_buf[0] = node_datas[node].lhs;
2319 stmt_buf[1] = node_datas[node].rhs;
2320 break :s stmt_buf[0..2];
2321 },
2322 .block, .block_semicolon => tree.extra_data[node_datas[node].lhs..node_datas[node].rhs],
2323 };
2324
2325 const lbrace = main_tokens[node];
2326 if (token_tags[lbrace - 1] == .colon and
2327 token_tags[lbrace - 2] == .identifier)
2328 {
2329 // Labeled blocks are tricky - forwarding result location information properly is non-trivial,
2330 // plus if this block is exited with a `break_inline` we aren't allowed multiple breaks. This
2331 // case is rare, so just treat it as a normal expression and create a nested block.
2332 return expr(gz, scope, ri, node);
2333 }
2334
2335 var sub_gz = gz.makeSubBlock(scope);
2336 try blockExprStmts(&sub_gz, &sub_gz.base, statements);
2337
2338 return rvalue(gz, ri, .void_value, node);
2339}
2340
22942341fn blockExpr(
22952342 gz: *GenZir,
22962343 scope: *Scope,
......@@ -4102,7 +4149,7 @@ fn fnDecl(
41024149 assert(param_type_node != 0);
41034150 var param_gz = decl_gz.makeSubBlock(scope);
41044151 defer param_gz.unstack();
4105 const param_type = try expr(&param_gz, params_scope, coerced_type_ri, param_type_node);
4152 const param_type = try fullBodyExpr(&param_gz, params_scope, coerced_type_ri, param_type_node);
41064153 const param_inst_expected: Zir.Inst.Index = @enumFromInt(astgen.instructions.len + 1);
41074154 _ = try param_gz.addBreakWithSrcNode(.break_inline, param_inst_expected, param_type, param_type_node);
41084155
......@@ -4220,7 +4267,7 @@ fn fnDecl(
42204267 var ret_gz = decl_gz.makeSubBlock(params_scope);
42214268 defer ret_gz.unstack();
42224269 const ret_ref: Zir.Inst.Ref = inst: {
4223 const inst = try expr(&ret_gz, params_scope, coerced_type_ri, fn_proto.ast.return_type);
4270 const inst = try fullBodyExpr(&ret_gz, params_scope, coerced_type_ri, fn_proto.ast.return_type);
42244271 if (ret_gz.instructionsSlice().len == 0) {
42254272 // In this case we will send a len=0 body which can be encoded more efficiently.
42264273 break :inst inst;
......@@ -4285,7 +4332,7 @@ fn fnDecl(
42854332 const lbrace_line = astgen.source_line - decl_gz.decl_line;
42864333 const lbrace_column = astgen.source_column;
42874334
4288 _ = try expr(&fn_gz, params_scope, .{ .rl = .none }, body_node);
4335 _ = try fullBodyExpr(&fn_gz, params_scope, .{ .rl = .none }, body_node);
42894336 try checkUsed(gz, &fn_gz.base, params_scope);
42904337
42914338 if (!fn_gz.endsWithNoReturn()) {
......@@ -4471,19 +4518,19 @@ fn globalVarDecl(
44714518
44724519 var align_gz = block_scope.makeSubBlock(scope);
44734520 if (var_decl.ast.align_node != 0) {
4474 const align_inst = try expr(&align_gz, &align_gz.base, coerced_align_ri, var_decl.ast.align_node);
4521 const align_inst = try fullBodyExpr(&align_gz, &align_gz.base, coerced_align_ri, var_decl.ast.align_node);
44754522 _ = try align_gz.addBreakWithSrcNode(.break_inline, decl_inst, align_inst, node);
44764523 }
44774524
44784525 var linksection_gz = align_gz.makeSubBlock(scope);
44794526 if (var_decl.ast.section_node != 0) {
4480 const linksection_inst = try expr(&linksection_gz, &linksection_gz.base, coerced_linksection_ri, var_decl.ast.section_node);
4527 const linksection_inst = try fullBodyExpr(&linksection_gz, &linksection_gz.base, coerced_linksection_ri, var_decl.ast.section_node);
44814528 _ = try linksection_gz.addBreakWithSrcNode(.break_inline, decl_inst, linksection_inst, node);
44824529 }
44834530
44844531 var addrspace_gz = linksection_gz.makeSubBlock(scope);
44854532 if (var_decl.ast.addrspace_node != 0) {
4486 const addrspace_inst = try expr(&addrspace_gz, &addrspace_gz.base, coerced_addrspace_ri, var_decl.ast.addrspace_node);
4533 const addrspace_inst = try fullBodyExpr(&addrspace_gz, &addrspace_gz.base, coerced_addrspace_ri, var_decl.ast.addrspace_node);
44874534 _ = try addrspace_gz.addBreakWithSrcNode(.break_inline, decl_inst, addrspace_inst, node);
44884535 }
44894536
......@@ -4532,7 +4579,7 @@ fn comptimeDecl(
45324579 };
45334580 defer decl_block.unstack();
45344581
4535 const block_result = try expr(&decl_block, &decl_block.base, .{ .rl = .none }, body_node);
4582 const block_result = try fullBodyExpr(&decl_block, &decl_block.base, .{ .rl = .none }, body_node);
45364583 if (decl_block.isEmpty() or !decl_block.refIsNoReturn(block_result)) {
45374584 _ = try decl_block.addBreak(.break_inline, decl_inst, .void_value);
45384585 }
......@@ -4734,7 +4781,7 @@ fn testDecl(
47344781 const lbrace_line = astgen.source_line - decl_block.decl_line;
47354782 const lbrace_column = astgen.source_column;
47364783
4737 const block_result = try expr(&fn_block, &fn_block.base, .{ .rl = .none }, body_node);
4784 const block_result = try fullBodyExpr(&fn_block, &fn_block.base, .{ .rl = .none }, body_node);
47384785 if (fn_block.isEmpty() or !fn_block.refIsNoReturn(block_result)) {
47394786
47404787 // As our last action before the return, "pop" the error trace if needed
......@@ -5981,7 +6028,7 @@ fn orelseCatchExpr(
59816028 break :blk &err_val_scope.base;
59826029 };
59836030
5984 const else_result = try expr(&else_scope, else_sub_scope, block_scope.break_result_info, rhs);
6031 const else_result = try fullBodyExpr(&else_scope, else_sub_scope, block_scope.break_result_info, rhs);
59856032 if (!else_scope.endsWithNoReturn()) {
59866033 // As our last action before the break, "pop" the error trace if needed
59876034 if (do_err_trace)
......@@ -6149,7 +6196,7 @@ fn boolBinOp(
61496196
61506197 var rhs_scope = gz.makeSubBlock(scope);
61516198 defer rhs_scope.unstack();
6152 const rhs = try expr(&rhs_scope, &rhs_scope.base, coerced_bool_ri, node_datas[node].rhs);
6199 const rhs = try fullBodyExpr(&rhs_scope, &rhs_scope.base, coerced_bool_ri, node_datas[node].rhs);
61536200 if (!gz.refIsNoReturn(rhs)) {
61546201 _ = try rhs_scope.addBreakWithSrcNode(.break_inline, bool_br, rhs, node_datas[node].rhs);
61556202 }
......@@ -6293,7 +6340,7 @@ fn ifExpr(
62936340 }
62946341 };
62956342
6296 const then_result = try expr(&then_scope, then_sub_scope, block_scope.break_result_info, then_node);
6343 const then_result = try fullBodyExpr(&then_scope, then_sub_scope, block_scope.break_result_info, then_node);
62976344 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
62986345 if (!then_scope.endsWithNoReturn()) {
62996346 _ = try then_scope.addBreakWithSrcNode(.@"break", block, then_result, then_node);
......@@ -6335,7 +6382,7 @@ fn ifExpr(
63356382 break :s &else_scope.base;
63366383 }
63376384 };
6338 const else_result = try expr(&else_scope, sub_scope, block_scope.break_result_info, else_node);
6385 const else_result = try fullBodyExpr(&else_scope, sub_scope, block_scope.break_result_info, else_node);
63396386 if (!else_scope.endsWithNoReturn()) {
63406387 // As our last action before the break, "pop" the error trace if needed
63416388 if (do_err_trace)
......@@ -6444,7 +6491,7 @@ fn whileExpr(
64446491 } = c: {
64456492 if (while_full.error_token) |_| {
64466493 const cond_ri: ResultInfo = .{ .rl = if (payload_is_ref) .ref else .none };
6447 const err_union = try expr(&cond_scope, &cond_scope.base, cond_ri, while_full.ast.cond_expr);
6494 const err_union = try fullBodyExpr(&cond_scope, &cond_scope.base, cond_ri, while_full.ast.cond_expr);
64486495 const tag: Zir.Inst.Tag = if (payload_is_ref) .is_non_err_ptr else .is_non_err;
64496496 break :c .{
64506497 .inst = err_union,
......@@ -6452,14 +6499,14 @@ fn whileExpr(
64526499 };
64536500 } else if (while_full.payload_token) |_| {
64546501 const cond_ri: ResultInfo = .{ .rl = if (payload_is_ref) .ref else .none };
6455 const optional = try expr(&cond_scope, &cond_scope.base, cond_ri, while_full.ast.cond_expr);
6502 const optional = try fullBodyExpr(&cond_scope, &cond_scope.base, cond_ri, while_full.ast.cond_expr);
64566503 const tag: Zir.Inst.Tag = if (payload_is_ref) .is_non_null_ptr else .is_non_null;
64576504 break :c .{
64586505 .inst = optional,
64596506 .bool_bit = try cond_scope.addUnNode(tag, optional, while_full.ast.cond_expr),
64606507 };
64616508 } else {
6462 const cond = try expr(&cond_scope, &cond_scope.base, coerced_bool_ri, while_full.ast.cond_expr);
6509 const cond = try fullBodyExpr(&cond_scope, &cond_scope.base, coerced_bool_ri, while_full.ast.cond_expr);
64636510 break :c .{
64646511 .inst = cond,
64656512 .bool_bit = cond,
......@@ -6582,7 +6629,11 @@ fn whileExpr(
65826629 }
65836630
65846631 continue_scope.instructions_top = continue_scope.instructions.items.len;
6585 _ = try unusedResultExpr(&continue_scope, &continue_scope.base, then_node);
6632 {
6633 try emitDbgNode(&continue_scope, then_node);
6634 const unused_result = try fullBodyExpr(&continue_scope, &continue_scope.base, .{ .rl = .none }, then_node);
6635 _ = try addEnsureResult(&continue_scope, unused_result, then_node);
6636 }
65866637 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
65876638 const break_tag: Zir.Inst.Tag = if (is_inline) .break_inline else .@"break";
65886639 if (!continue_scope.endsWithNoReturn()) {
......@@ -6626,7 +6677,7 @@ fn whileExpr(
66266677 // control flow apply to outer loops; not this one.
66276678 loop_scope.continue_block = .none;
66286679 loop_scope.break_block = .none;
6629 const else_result = try expr(&else_scope, sub_scope, loop_scope.break_result_info, else_node);
6680 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node);
66306681 if (is_statement) {
66316682 _ = try addEnsureResult(&else_scope, else_result, else_node);
66326683 }
......@@ -6894,7 +6945,7 @@ fn forExpr(
68946945 break :blk capture_sub_scope;
68956946 };
68966947
6897 const then_result = try expr(&then_scope, then_sub_scope, .{ .rl = .none }, then_node);
6948 const then_result = try fullBodyExpr(&then_scope, then_sub_scope, .{ .rl = .none }, then_node);
68986949 _ = try addEnsureResult(&then_scope, then_result, then_node);
68996950
69006951 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
......@@ -6913,7 +6964,7 @@ fn forExpr(
69136964 // control flow apply to outer loops; not this one.
69146965 loop_scope.continue_block = .none;
69156966 loop_scope.break_block = .none;
6916 const else_result = try expr(&else_scope, sub_scope, loop_scope.break_result_info, else_node);
6967 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node);
69176968 if (is_statement) {
69186969 _ = try addEnsureResult(&else_scope, else_result, else_node);
69196970 }
......@@ -7388,7 +7439,7 @@ fn switchExprErrUnion(
73887439 }
73897440
73907441 const target_expr_node = case.ast.target_expr;
7391 const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node);
7442 const case_result = try fullBodyExpr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node);
73927443 // check capture_scope, not err_scope to avoid false positive unused error capture
73937444 try checkUsed(parent_gz, &case_scope.base, err_scope.parent);
73947445 const uses_err = err_scope.used != 0 or err_scope.discarded != 0;
......@@ -7849,7 +7900,7 @@ fn switchExpr(
78497900 try case_scope.addDbgVar(.dbg_var_val, dbg_var_tag_name, dbg_var_tag_inst);
78507901 }
78517902 const target_expr_node = case.ast.target_expr;
7852 const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node);
7903 const case_result = try fullBodyExpr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node);
78537904 try checkUsed(parent_gz, &case_scope.base, sub_scope);
78547905 if (!parent_gz.refIsNoReturn(case_result)) {
78557906 _ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node);
......@@ -9752,7 +9803,7 @@ fn cImport(
97529803 defer block_scope.unstack();
97539804
97549805 const block_inst = try gz.makeBlockInst(.c_import, node);
9755 const block_result = try expr(&block_scope, &block_scope.base, .{ .rl = .none }, body_node);
9806 const block_result = try fullBodyExpr(&block_scope, &block_scope.base, .{ .rl = .none }, body_node);
97569807 _ = try gz.addUnNode(.ensure_result_used, block_result, node);
97579808 if (!gz.refIsNoReturn(block_result)) {
97589809 _ = try block_scope.addBreak(.break_inline, block_inst, .void_value);
......@@ -9835,7 +9886,7 @@ fn callExpr(
98359886 defer arg_block.unstack();
98369887
98379888 // `call_inst` is reused to provide the param type.
9838 const arg_ref = try expr(&arg_block, &arg_block.base, .{ .rl = .{ .coerced_ty = call_inst }, .ctx = .fn_arg }, param_node);
9889 const arg_ref = try fullBodyExpr(&arg_block, &arg_block.base, .{ .rl = .{ .coerced_ty = call_inst }, .ctx = .fn_arg }, param_node);
98399890 _ = try arg_block.addBreakWithSrcNode(.break_inline, call_index, arg_ref, param_node);
98409891
98419892 const body = arg_block.instructionsSlice();
......@@ -10871,11 +10922,11 @@ fn rvalueInner(
1087110922 .ty => |ty_inst| {
1087210923 // Quickly eliminate some common, unnecessary type coercion.
1087310924 const as_ty = @as(u64, @intFromEnum(Zir.Inst.Ref.type_type)) << 32;
10874 const as_comptime_int = @as(u64, @intFromEnum(Zir.Inst.Ref.comptime_int_type)) << 32;
1087510925 const as_bool = @as(u64, @intFromEnum(Zir.Inst.Ref.bool_type)) << 32;
10926 const as_void = @as(u64, @intFromEnum(Zir.Inst.Ref.void_type)) << 32;
10927 const as_comptime_int = @as(u64, @intFromEnum(Zir.Inst.Ref.comptime_int_type)) << 32;
1087610928 const as_usize = @as(u64, @intFromEnum(Zir.Inst.Ref.usize_type)) << 32;
1087710929 const as_u8 = @as(u64, @intFromEnum(Zir.Inst.Ref.u8_type)) << 32;
10878 const as_void = @as(u64, @intFromEnum(Zir.Inst.Ref.void_type)) << 32;
1087910930 switch ((@as(u64, @intFromEnum(ty_inst)) << 32) | @as(u64, @intFromEnum(result))) {
1088010931 as_ty | @intFromEnum(Zir.Inst.Ref.u1_type),
1088110932 as_ty | @intFromEnum(Zir.Inst.Ref.u8_type),
......@@ -11694,7 +11745,8 @@ const GenZir = struct {
1169411745 /// Whether we're in an expression within a `@TypeOf` operand. In this case, closure of runtime
1169511746 /// variables is permitted where it is usually not.
1169611747 is_typeof: bool = false,
11697 /// This is set to true for inline loops; false otherwise.
11748 /// This is set to true for a `GenZir` of a `block_inline`, indicating that
11749 /// exits from this block should use `break_inline` rather than `break`.
1169811750 is_inline: bool = false,
1169911751 c_import: bool = false,
1170011752 /// How decls created in this scope should be named.