Commit 4dd65316b7

Andrew Kelley <andrew@ziglang.org>
2022-03-29 03:28:08
AstGen: coerce break operands of labeled blocks
Similar code was already in place for conditional branches. This updates AstGen to do the same for labeled blocks. It takes advantage of the `store_to_block_ptr` instructions by mutating them in place to become `as` instructions, coercing the break operands before they are returned from the block.
1 parent 8577434
Changed files (2)
src
test
behavior
src/AstGen.zig
@@ -2015,6 +2015,7 @@ fn labeledBlockExpr(
     }
 
     const zir_datas = gz.astgen.instructions.items(.data);
+    const zir_tags = gz.astgen.instructions.items(.tag);
     const strat = rl.strategy(&block_scope);
     switch (strat.tag) {
         .break_void => {
@@ -2029,6 +2030,31 @@ fn labeledBlockExpr(
         },
         .break_operand => {
             // All break operands are values that did not use the result location pointer.
+            // The break instructions need to have their operands coerced if the
+            // block's result location is a `ty`. In this case we overwrite the
+            // `store_to_block_ptr` instruction with an `as` instruction and repurpose
+            // it as the break operand.
+            // This corresponds to similar code in `setCondBrPayloadElideBlockStorePtr`.
+            if (block_scope.rl_ty_inst != .none) {
+                for (block_scope.labeled_breaks.items) |br| {
+                    // We expect the `store_to_block_ptr` to be created between 1-3 instructions
+                    // prior to the break.
+                    var search_index = br -| 3;
+                    while (search_index < br) : (search_index += 1) {
+                        if (zir_tags[search_index] == .store_to_block_ptr and
+                            zir_datas[search_index].bin.lhs == block_scope.rl_ptr)
+                        {
+                            zir_tags[search_index] = .as;
+                            zir_datas[search_index].bin = .{
+                                .lhs = block_scope.rl_ty_inst,
+                                .rhs = zir_datas[br].@"break".operand,
+                            };
+                            zir_datas[br].@"break".operand = indexToRef(search_index);
+                            break;
+                        }
+                    } else unreachable;
+                }
+            }
             try block_scope.setBlockBody(block_inst);
             const block_ref = indexToRef(block_inst);
             switch (rl) {
@@ -5366,6 +5392,7 @@ fn setCondBrPayloadElideBlockStorePtr(
     // switch's result location is a `ty`. In this case we overwrite the
     // `store_to_block_ptr` instruction with an `as` instruction and repurpose
     // it as the break operand.
+    // This corresponds to similar code in `labeledBlockExpr`.
     for (then_body) |src_inst| {
         if (zir_tags[src_inst] == .store_to_block_ptr and
             zir_datas[src_inst].bin.lhs == block_ptr)
test/behavior/basic.zig
@@ -875,3 +875,15 @@ test "catch in block has correct result location" {
     };
     try expect(config_h_text == 1);
 }
+
+test "labeled block with runtime branch forwards its result location type to break statements" {
+    const E = enum { a, b };
+    var a = false;
+    const e: E = blk: {
+        if (a) {
+            break :blk .a;
+        }
+        break :blk .b;
+    };
+    try expect(e == .b);
+}