authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-22 16:45:09-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-31 21:09:22-07:00
log588171c30b34426fbb07645aa2625e989f369eec
tree254ff4567279c496dd61a4a790c64c64a8d7e763
parent06bb360dd296288db33844d682188e33116d7ab6

sema: after block gets peer type resolved, insert type coercions

on the break instruction operands. This involves a new TZIR instruction, br_block_flat, which represents a break instruction where the operand is the result of a flat block. See the doc comments on the instructions for more details. How it works: when adding break instructions in semantic analysis, the underlying allocation is slightly padded so that it is the size of a br_block_flat instruction, which allows the break instruction to later be converted without removing instructions inside the parent body. The extra type coercion instructions go into the body of the br_block_flat, and backends are responsible for dispatching the instruction correctly (it should map to the same function calls for related instructions).

5 files changed, 171 insertions(+), 39 deletions(-)

src/Module.zig+25-3
...@@ -671,14 +671,36 @@ pub const Scope = struct {...@@ -671,14 +671,36 @@ pub const Scope = struct {
671 };671 };
672672
673 pub const Merges = struct {673 pub const Merges = struct {
674 results: ArrayListUnmanaged(*Inst),
675 block_inst: *Inst.Block,674 block_inst: *Inst.Block,
675 /// Separate array list from break_inst_list so that it can be passed directly
676 /// to resolvePeerTypes.
677 results: ArrayListUnmanaged(*Inst),
678 /// Keeps track of the break instructions so that the operand can be replaced
679 /// if we need to add type coercion at the end of block analysis.
680 /// Same indexes, capacity, length as `results`.
681 br_list: ArrayListUnmanaged(*Inst.Br),
676 };682 };
677683
678 /// For debugging purposes.684 /// For debugging purposes.
679 pub fn dump(self: *Block, mod: Module) void {685 pub fn dump(self: *Block, mod: Module) void {
680 zir.dumpBlock(mod, self);686 zir.dumpBlock(mod, self);
681 }687 }
688
689 pub fn makeSubBlock(parent: *Block) Block {
690 return .{
691 .parent = parent,
692 .inst_table = parent.inst_table,
693 .func = parent.func,
694 .owner_decl = parent.owner_decl,
695 .src_decl = parent.src_decl,
696 .instructions = .{},
697 .arena = parent.arena,
698 .label = null,
699 .inlining = parent.inlining,
700 .is_comptime = parent.is_comptime,
701 .branch_quota = parent.branch_quota,
702 };
703 }
682 };704 };
683705
684 /// This is a temporary structure, references to it are valid only706 /// This is a temporary structure, references to it are valid only
...@@ -2107,7 +2129,7 @@ pub fn addBr(...@@ -2107,7 +2129,7 @@ pub fn addBr(
2107 src: usize,2129 src: usize,
2108 target_block: *Inst.Block,2130 target_block: *Inst.Block,
2109 operand: *Inst,2131 operand: *Inst,
2110) !*Inst {2132) !*Inst.Br {
2111 const inst = try scope_block.arena.create(Inst.Br);2133 const inst = try scope_block.arena.create(Inst.Br);
2112 inst.* = .{2134 inst.* = .{
2113 .base = .{2135 .base = .{
...@@ -2119,7 +2141,7 @@ pub fn addBr(...@@ -2119,7 +2141,7 @@ pub fn addBr(
2119 .block = target_block,2141 .block = target_block,
2120 };2142 };
2121 try scope_block.instructions.append(self.gpa, &inst.base);2143 try scope_block.instructions.append(self.gpa, &inst.base);
2122 return &inst.base;2144 return inst;
2123}2145}
21242146
2125pub fn addCondBr(2147pub fn addCondBr(
src/codegen.zig+21-10
...@@ -844,6 +844,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -844,6 +844,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
844 .bit_or => return self.genBitOr(inst.castTag(.bit_or).?),844 .bit_or => return self.genBitOr(inst.castTag(.bit_or).?),
845 .block => return self.genBlock(inst.castTag(.block).?),845 .block => return self.genBlock(inst.castTag(.block).?),
846 .br => return self.genBr(inst.castTag(.br).?),846 .br => return self.genBr(inst.castTag(.br).?),
847 .br_block_flat => return self.genBrBlockFlat(inst.castTag(.br_block_flat).?),
847 .breakpoint => return self.genBreakpoint(inst.src),848 .breakpoint => return self.genBreakpoint(inst.src),
848 .brvoid => return self.genBrVoid(inst.castTag(.brvoid).?),849 .brvoid => return self.genBrVoid(inst.castTag(.brvoid).?),
849 .bool_and => return self.genBoolOp(inst.castTag(.bool_and).?),850 .bool_and => return self.genBoolOp(inst.castTag(.bool_and).?),
...@@ -2441,17 +2442,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2441,17 +2442,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2441 }2442 }
2442 }2443 }
24432444
2445 fn genBrBlockFlat(self: *Self, parent_inst: *ir.Inst.BrBlockFlat) !MCValue {
2446 try self.genBody(parent_inst.body);
2447 const last = parent_inst.body.instructions[parent_inst.body.instructions.len - 1];
2448 return self.br(parent_inst.base.src, parent_inst.block, last);
2449 }
2450
2444 fn genBr(self: *Self, inst: *ir.Inst.Br) !MCValue {2451 fn genBr(self: *Self, inst: *ir.Inst.Br) !MCValue {
2445 if (inst.operand.ty.hasCodeGenBits()) {2452 return self.br(inst.base.src, inst.block, inst.operand);
2446 const operand = try self.resolveInst(inst.operand);
2447 const block_mcv = @bitCast(MCValue, inst.block.codegen.mcv);
2448 if (block_mcv == .none) {
2449 inst.block.codegen.mcv = @bitCast(AnyMCValue, operand);
2450 } else {
2451 try self.setRegOrMem(inst.base.src, inst.block.base.ty, block_mcv, operand);
2452 }
2453 }
2454 return self.brVoid(inst.base.src, inst.block);
2455 }2453 }
24562454
2457 fn genBrVoid(self: *Self, inst: *ir.Inst.BrVoid) !MCValue {2455 fn genBrVoid(self: *Self, inst: *ir.Inst.BrVoid) !MCValue {
...@@ -2478,6 +2476,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2478,6 +2476,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2478 }2476 }
2479 }2477 }
24802478
2479 fn br(self: *Self, src: usize, block: *ir.Inst.Block, operand: *ir.Inst) !MCValue {
2480 if (operand.ty.hasCodeGenBits()) {
2481 const operand_mcv = try self.resolveInst(operand);
2482 const block_mcv = @bitCast(MCValue, block.codegen.mcv);
2483 if (block_mcv == .none) {
2484 block.codegen.mcv = @bitCast(AnyMCValue, operand_mcv);
2485 } else {
2486 try self.setRegOrMem(src, block.base.ty, block_mcv, operand_mcv);
2487 }
2488 }
2489 return self.brVoid(src, block);
2490 }
2491
2481 fn brVoid(self: *Self, src: usize, block: *ir.Inst.Block) !MCValue {2492 fn brVoid(self: *Self, src: usize, block: *ir.Inst.Block) !MCValue {
2482 // Emit a jump with a relocation. It will be patched up after the block ends.2493 // Emit a jump with a relocation. It will be patched up after the block ends.
2483 try block.codegen.relocs.ensureCapacity(self.gpa, block.codegen.relocs.items.len + 1);2494 try block.codegen.relocs.ensureCapacity(self.gpa, block.codegen.relocs.items.len + 1);
src/ir.zig+31-1
...@@ -61,6 +61,13 @@ pub const Inst = struct {...@@ -61,6 +61,13 @@ pub const Inst = struct {
61 bit_or,61 bit_or,
62 block,62 block,
63 br,63 br,
64 /// Same as `br` except the operand is a list of instructions to be treated as
65 /// a flat block; that is there is only 1 break instruction from the block, and
66 /// it is implied to be after the last instruction, and the last instruction is
67 /// the break operand.
68 /// This instruction exists for late-stage semantic analysis patch ups, to
69 /// replace one br operand with multiple instructions, without moving anything else around.
70 br_block_flat,
64 breakpoint,71 breakpoint,
65 brvoid,72 brvoid,
66 call,73 call,
...@@ -158,6 +165,7 @@ pub const Inst = struct {...@@ -158,6 +165,7 @@ pub const Inst = struct {
158 .assembly => Assembly,165 .assembly => Assembly,
159 .block => Block,166 .block => Block,
160 .br => Br,167 .br => Br,
168 .br_block_flat => BrBlockFlat,
161 .brvoid => BrVoid,169 .brvoid => BrVoid,
162 .call => Call,170 .call => Call,
163 .condbr => CondBr,171 .condbr => CondBr,
...@@ -252,6 +260,7 @@ pub const Inst = struct {...@@ -252,6 +260,7 @@ pub const Inst = struct {
252 return switch (base.tag) {260 return switch (base.tag) {
253 .br => base.castTag(.br).?.block,261 .br => base.castTag(.br).?.block,
254 .brvoid => base.castTag(.brvoid).?.block,262 .brvoid => base.castTag(.brvoid).?.block,
263 .br_block_flat => base.castTag(.br_block_flat).?.block,
255 else => null,264 else => null,
256 };265 };
257 }266 }
...@@ -355,6 +364,27 @@ pub const Inst = struct {...@@ -355,6 +364,27 @@ pub const Inst = struct {
355 }364 }
356 };365 };
357366
367 pub const convertable_br_size = std.math.max(@sizeOf(BrBlockFlat), @sizeOf(Br));
368 pub const convertable_br_align = std.math.max(@alignOf(BrBlockFlat), @alignOf(Br));
369 comptime {
370 assert(@byteOffsetOf(BrBlockFlat, "base") == @byteOffsetOf(Br, "base"));
371 }
372
373 pub const BrBlockFlat = struct {
374 pub const base_tag = Tag.br_block_flat;
375
376 base: Inst,
377 block: *Block,
378 body: Body,
379
380 pub fn operandCount(self: *const BrBlockFlat) usize {
381 return 0;
382 }
383 pub fn getOperand(self: *const BrBlockFlat, index: usize) ?*Inst {
384 return null;
385 }
386 };
387
358 pub const Br = struct {388 pub const Br = struct {
359 pub const base_tag = Tag.br;389 pub const base_tag = Tag.br;
360390
...@@ -363,7 +393,7 @@ pub const Inst = struct {...@@ -363,7 +393,7 @@ pub const Inst = struct {
363 operand: *Inst,393 operand: *Inst,
364394
365 pub fn operandCount(self: *const Br) usize {395 pub fn operandCount(self: *const Br) usize {
366 return 0;396 return 1;
367 }397 }
368 pub fn getOperand(self: *const Br, index: usize) ?*Inst {398 pub fn getOperand(self: *const Br, index: usize) ?*Inst {
369 if (index == 0)399 if (index == 0)
src/zir.zig+26-2
...@@ -1634,6 +1634,12 @@ const DumpTzir = struct {...@@ -1634,6 +1634,12 @@ const DumpTzir = struct {
1634 try dtz.findConst(br.operand);1634 try dtz.findConst(br.operand);
1635 },1635 },
16361636
1637 .br_block_flat => {
1638 const br_block_flat = inst.castTag(.br_block_flat).?;
1639 try dtz.findConst(&br_block_flat.block.base);
1640 try dtz.fetchInstsAndResolveConsts(br_block_flat.body);
1641 },
1642
1637 .brvoid => {1643 .brvoid => {
1638 const brvoid = inst.castTag(.brvoid).?;1644 const brvoid = inst.castTag(.brvoid).?;
1639 try dtz.findConst(&brvoid.block.base);1645 try dtz.findConst(&brvoid.block.base);
...@@ -1779,6 +1785,24 @@ const DumpTzir = struct {...@@ -1779,6 +1785,24 @@ const DumpTzir = struct {
1779 }1785 }
1780 },1786 },
17811787
1788 .br_block_flat => {
1789 const br_block_flat = inst.castTag(.br_block_flat).?;
1790 const block_kinky = try dtz.writeInst(writer, &br_block_flat.block.base);
1791 if (block_kinky != null) {
1792 try writer.writeAll(", { // Instruction does not dominate all uses!\n");
1793 } else {
1794 try writer.writeAll(", {\n");
1795 }
1796
1797 const old_indent = dtz.indent;
1798 dtz.indent += 2;
1799 try dtz.dumpBody(br_block_flat.body, writer);
1800 dtz.indent = old_indent;
1801
1802 try writer.writeByteNTimes(' ', dtz.indent);
1803 try writer.writeAll("})\n");
1804 },
1805
1782 .brvoid => {1806 .brvoid => {
1783 const brvoid = inst.castTag(.brvoid).?;1807 const brvoid = inst.castTag(.brvoid).?;
1784 const kinky = try dtz.writeInst(writer, &brvoid.block.base);1808 const kinky = try dtz.writeInst(writer, &brvoid.block.base);
...@@ -1792,7 +1816,7 @@ const DumpTzir = struct {...@@ -1792,7 +1816,7 @@ const DumpTzir = struct {
1792 .block => {1816 .block => {
1793 const block = inst.castTag(.block).?;1817 const block = inst.castTag(.block).?;
17941818
1795 try writer.writeAll("\n");1819 try writer.writeAll("{\n");
17961820
1797 const old_indent = dtz.indent;1821 const old_indent = dtz.indent;
1798 dtz.indent += 2;1822 dtz.indent += 2;
...@@ -1800,7 +1824,7 @@ const DumpTzir = struct {...@@ -1800,7 +1824,7 @@ const DumpTzir = struct {
1800 dtz.indent = old_indent;1824 dtz.indent = old_indent;
18011825
1802 try writer.writeByteNTimes(' ', dtz.indent);1826 try writer.writeByteNTimes(' ', dtz.indent);
1803 try writer.writeAll(")\n");1827 try writer.writeAll("})\n");
1804 },1828 },
18051829
1806 .condbr => {1830 .condbr => {
src/zir_sema.zig+68-23
...@@ -664,20 +664,9 @@ fn zirBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime:...@@ -664,20 +664,9 @@ fn zirBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime:
664 defer tracy.end();664 defer tracy.end();
665 const parent_block = scope.cast(Scope.Block).?;665 const parent_block = scope.cast(Scope.Block).?;
666666
667 var child_block: Scope.Block = .{667 var child_block = parent_block.makeSubBlock();
668 .parent = parent_block,
669 .inst_table = parent_block.inst_table,
670 .func = parent_block.func,
671 .owner_decl = parent_block.owner_decl,
672 .src_decl = parent_block.src_decl,
673 .instructions = .{},
674 .arena = parent_block.arena,
675 .label = null,
676 .inlining = parent_block.inlining,
677 .is_comptime = parent_block.is_comptime or is_comptime,
678 .branch_quota = parent_block.branch_quota,
679 };
680 defer child_block.instructions.deinit(mod.gpa);668 defer child_block.instructions.deinit(mod.gpa);
669 child_block.is_comptime = child_block.is_comptime or is_comptime;
681670
682 try analyzeBody(mod, &child_block, inst.positionals.body);671 try analyzeBody(mod, &child_block, inst.positionals.body);
683672
...@@ -728,6 +717,7 @@ fn zirBlock(...@@ -728,6 +717,7 @@ fn zirBlock(
728 .zir_block = inst,717 .zir_block = inst,
729 .merges = .{718 .merges = .{
730 .results = .{},719 .results = .{},
720 .br_list = .{},
731 .block_inst = block_inst,721 .block_inst = block_inst,
732 },722 },
733 }),723 }),
...@@ -739,6 +729,7 @@ fn zirBlock(...@@ -739,6 +729,7 @@ fn zirBlock(
739729
740 defer child_block.instructions.deinit(mod.gpa);730 defer child_block.instructions.deinit(mod.gpa);
741 defer merges.results.deinit(mod.gpa);731 defer merges.results.deinit(mod.gpa);
732 defer merges.br_list.deinit(mod.gpa);
742733
743 try analyzeBody(mod, &child_block, inst.positionals.body);734 try analyzeBody(mod, &child_block, inst.positionals.body);
744735
...@@ -772,22 +763,53 @@ fn analyzeBlockBody(...@@ -772,22 +763,53 @@ fn analyzeBlockBody(
772 const last_inst = child_block.instructions.items[last_inst_index];763 const last_inst = child_block.instructions.items[last_inst_index];
773 if (last_inst.breakBlock()) |br_block| {764 if (last_inst.breakBlock()) |br_block| {
774 if (br_block == merges.block_inst) {765 if (br_block == merges.block_inst) {
775 // No need for a block instruction. We can put the new instructions directly into the parent block.766 // No need for a block instruction. We can put the new instructions directly
776 // Here we omit the break instruction.767 // into the parent block. Here we omit the break instruction.
777 const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items[0..last_inst_index]);768 const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items[0..last_inst_index]);
778 try parent_block.instructions.appendSlice(mod.gpa, copied_instructions);769 try parent_block.instructions.appendSlice(mod.gpa, copied_instructions);
779 return merges.results.items[0];770 return merges.results.items[0];
780 }771 }
781 }772 }
782 }773 }
783 // It should be impossible to have the number of results be > 1 in a comptime scope.774 // It is impossible to have the number of results be > 1 in a comptime scope.
784 assert(!child_block.is_comptime); // We should have already got a compile error in the condbr condition.775 assert(!child_block.is_comptime); // Should already got a compile error in the condbr condition.
785776
786 // Need to set the type and emit the Block instruction. This allows machine code generation777 // Need to set the type and emit the Block instruction. This allows machine code generation
787 // to emit a jump instruction to after the block when it encounters the break.778 // to emit a jump instruction to after the block when it encounters the break.
788 try parent_block.instructions.append(mod.gpa, &merges.block_inst.base);779 try parent_block.instructions.append(mod.gpa, &merges.block_inst.base);
789 merges.block_inst.base.ty = try mod.resolvePeerTypes(scope, merges.results.items);780 const resolved_ty = try mod.resolvePeerTypes(scope, merges.results.items);
790 merges.block_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) };781 merges.block_inst.base.ty = resolved_ty;
782 merges.block_inst.body = .{
783 .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items),
784 };
785 // Now that the block has its type resolved, we need to go back into all the break
786 // instructions, and insert type coercion on the operands.
787 for (merges.br_list.items) |br| {
788 if (br.operand.ty.eql(resolved_ty)) {
789 // No type coercion needed.
790 continue;
791 }
792 var coerce_block = parent_block.makeSubBlock();
793 defer coerce_block.instructions.deinit(mod.gpa);
794 const coerced_operand = try mod.coerce(&coerce_block.base, resolved_ty, br.operand);
795 assert(coerce_block.instructions.items[coerce_block.instructions.items.len - 1] == coerced_operand);
796 // Here we depend on the br instruction having been over-allocated (if necessary)
797 // inide analyzeBreak so that it can be converted into a br_block_flat instruction.
798 const br_src = br.base.src;
799 const br_ty = br.base.ty;
800 const br_block_flat = @ptrCast(*Inst.BrBlockFlat, br);
801 br_block_flat.* = .{
802 .base = .{
803 .src = br_src,
804 .ty = br_ty,
805 .tag = .br_block_flat,
806 },
807 .block = merges.block_inst,
808 .body = .{
809 .instructions = try parent_block.arena.dupe(*Inst, coerce_block.instructions.items),
810 },
811 };
812 }
791 return &merges.block_inst.base;813 return &merges.block_inst.base;
792}814}
793815
...@@ -827,9 +849,28 @@ fn analyzeBreak(...@@ -827,9 +849,28 @@ fn analyzeBreak(
827 while (opt_block) |block| {849 while (opt_block) |block| {
828 if (block.label) |*label| {850 if (block.label) |*label| {
829 if (label.zir_block == zir_block) {851 if (label.zir_block == zir_block) {
830 try label.merges.results.append(mod.gpa, operand);
831 const b = try mod.requireFunctionBlock(scope, src);852 const b = try mod.requireFunctionBlock(scope, src);
832 return mod.addBr(b, src, label.merges.block_inst, operand);853 // Here we add a br instruction, but we over-allocate a little bit
854 // (if necessary) to make it possible to convert the instruction into
855 // a br_block_flat instruction later.
856 const br = @ptrCast(*Inst.Br, try b.arena.alignedAlloc(
857 u8,
858 Inst.convertable_br_align,
859 Inst.convertable_br_size,
860 ));
861 br.* = .{
862 .base = .{
863 .tag = .br,
864 .ty = Type.initTag(.noreturn),
865 .src = src,
866 },
867 .operand = operand,
868 .block = label.merges.block_inst,
869 };
870 try b.instructions.append(mod.gpa, &br.base);
871 try label.merges.results.append(mod.gpa, operand);
872 try label.merges.br_list.append(mod.gpa, br);
873 return &br.base;
833 }874 }
834 }875 }
835 opt_block = block.parent;876 opt_block = block.parent;
...@@ -980,6 +1021,7 @@ fn zirCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {...@@ -980,6 +1021,7 @@ fn zirCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {
980 .casted_args = casted_args,1021 .casted_args = casted_args,
981 .merges = .{1022 .merges = .{
982 .results = .{},1023 .results = .{},
1024 .br_list = .{},
983 .block_inst = block_inst,1025 .block_inst = block_inst,
984 },1026 },
985 };1027 };
...@@ -1004,6 +1046,7 @@ fn zirCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {...@@ -1004,6 +1046,7 @@ fn zirCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {
10041046
1005 defer child_block.instructions.deinit(mod.gpa);1047 defer child_block.instructions.deinit(mod.gpa);
1006 defer merges.results.deinit(mod.gpa);1048 defer merges.results.deinit(mod.gpa);
1049 defer merges.br_list.deinit(mod.gpa);
10071050
1008 try mod.emitBackwardBranch(&child_block, inst.base.src);1051 try mod.emitBackwardBranch(&child_block, inst.base.src);
10091052
...@@ -2194,7 +2237,8 @@ fn zirReturn(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst...@@ -2194,7 +2237,8 @@ fn zirReturn(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst
2194 if (b.inlining) |inlining| {2237 if (b.inlining) |inlining| {
2195 // We are inlining a function call; rewrite the `ret` as a `break`.2238 // We are inlining a function call; rewrite the `ret` as a `break`.
2196 try inlining.merges.results.append(mod.gpa, operand);2239 try inlining.merges.results.append(mod.gpa, operand);
2197 return mod.addBr(b, inst.base.src, inlining.merges.block_inst, operand);2240 const br = try mod.addBr(b, inst.base.src, inlining.merges.block_inst, operand);
2241 return &br.base;
2198 }2242 }
21992243
2200 return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand);2244 return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand);
...@@ -2208,7 +2252,8 @@ fn zirReturnVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*...@@ -2208,7 +2252,8 @@ fn zirReturnVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*
2208 // We are inlining a function call; rewrite the `retvoid` as a `breakvoid`.2252 // We are inlining a function call; rewrite the `retvoid` as a `breakvoid`.
2209 const void_inst = try mod.constVoid(scope, inst.base.src);2253 const void_inst = try mod.constVoid(scope, inst.base.src);
2210 try inlining.merges.results.append(mod.gpa, void_inst);2254 try inlining.merges.results.append(mod.gpa, void_inst);
2211 return mod.addBr(b, inst.base.src, inlining.merges.block_inst, void_inst);2255 const br = try mod.addBr(b, inst.base.src, inlining.merges.block_inst, void_inst);
2256 return &br.base;
2212 }2257 }
22132258
2214 if (b.func) |func| {2259 if (b.func) |func| {