| author | |
| committer | |
| log | 588171c30b34426fbb07645aa2625e989f369eec |
| tree | 254ff4567279c496dd61a4a790c64c64a8d7e763 |
| parent | 06bb360dd296288db33844d682188e33116d7ab6 |
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 | 671 | }; |
| 672 | 672 | |
| 673 | 673 | pub const Merges = struct { |
| 674 | results: ArrayListUnmanaged(*Inst), | |
| 675 | 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 | }; |
| 677 | 683 | |
| 678 | 684 | /// For debugging purposes. |
| 679 | 685 | pub fn dump(self: *Block, mod: Module) void { |
| 680 | 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 | }; |
| 683 | 705 | |
| 684 | 706 | /// This is a temporary structure, references to it are valid only |
| ... | ... | @@ -2107,7 +2129,7 @@ pub fn addBr( |
| 2107 | 2129 | src: usize, |
| 2108 | 2130 | target_block: *Inst.Block, |
| 2109 | 2131 | operand: *Inst, |
| 2110 | ) !*Inst { | |
| 2132 | ) !*Inst.Br { | |
| 2111 | 2133 | const inst = try scope_block.arena.create(Inst.Br); |
| 2112 | 2134 | inst.* = .{ |
| 2113 | 2135 | .base = .{ |
| ... | ... | @@ -2119,7 +2141,7 @@ pub fn addBr( |
| 2119 | 2141 | .block = target_block, |
| 2120 | 2142 | }; |
| 2121 | 2143 | try scope_block.instructions.append(self.gpa, &inst.base); |
| 2122 | return &inst.base; | |
| 2144 | return inst; | |
| 2123 | 2145 | } |
| 2124 | 2146 | |
| 2125 | 2147 | pub fn addCondBr( |
src/codegen.zig+21-10| ... | ... | @@ -844,6 +844,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 844 | 844 | .bit_or => return self.genBitOr(inst.castTag(.bit_or).?), |
| 845 | 845 | .block => return self.genBlock(inst.castTag(.block).?), |
| 846 | 846 | .br => return self.genBr(inst.castTag(.br).?), |
| 847 | .br_block_flat => return self.genBrBlockFlat(inst.castTag(.br_block_flat).?), | |
| 847 | 848 | .breakpoint => return self.genBreakpoint(inst.src), |
| 848 | 849 | .brvoid => return self.genBrVoid(inst.castTag(.brvoid).?), |
| 849 | 850 | .bool_and => return self.genBoolOp(inst.castTag(.bool_and).?), |
| ... | ... | @@ -2441,17 +2442,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2441 | 2442 | } |
| 2442 | 2443 | } |
| 2443 | 2444 | |
| 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 | 2451 | fn genBr(self: *Self, inst: *ir.Inst.Br) !MCValue { |
| 2445 | if (inst.operand.ty.hasCodeGenBits()) { | |
| 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); | |
| 2452 | return self.br(inst.base.src, inst.block, inst.operand); | |
| 2455 | 2453 | } |
| 2456 | 2454 | |
| 2457 | 2455 | fn genBrVoid(self: *Self, inst: *ir.Inst.BrVoid) !MCValue { |
| ... | ... | @@ -2478,6 +2476,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2478 | 2476 | } |
| 2479 | 2477 | } |
| 2480 | 2478 | |
| 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 | 2492 | fn brVoid(self: *Self, src: usize, block: *ir.Inst.Block) !MCValue { |
| 2482 | 2493 | // Emit a jump with a relocation. It will be patched up after the block ends. |
| 2483 | 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 | 61 | bit_or, |
| 62 | 62 | block, |
| 63 | 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 | 71 | breakpoint, |
| 65 | 72 | brvoid, |
| 66 | 73 | call, |
| ... | ... | @@ -158,6 +165,7 @@ pub const Inst = struct { |
| 158 | 165 | .assembly => Assembly, |
| 159 | 166 | .block => Block, |
| 160 | 167 | .br => Br, |
| 168 | .br_block_flat => BrBlockFlat, | |
| 161 | 169 | .brvoid => BrVoid, |
| 162 | 170 | .call => Call, |
| 163 | 171 | .condbr => CondBr, |
| ... | ... | @@ -252,6 +260,7 @@ pub const Inst = struct { |
| 252 | 260 | return switch (base.tag) { |
| 253 | 261 | .br => base.castTag(.br).?.block, |
| 254 | 262 | .brvoid => base.castTag(.brvoid).?.block, |
| 263 | .br_block_flat => base.castTag(.br_block_flat).?.block, | |
| 255 | 264 | else => null, |
| 256 | 265 | }; |
| 257 | 266 | } |
| ... | ... | @@ -355,6 +364,27 @@ pub const Inst = struct { |
| 355 | 364 | } |
| 356 | 365 | }; |
| 357 | 366 | |
| 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 | 388 | pub const Br = struct { |
| 359 | 389 | pub const base_tag = Tag.br; |
| 360 | 390 | |
| ... | ... | @@ -363,7 +393,7 @@ pub const Inst = struct { |
| 363 | 393 | operand: *Inst, |
| 364 | 394 | |
| 365 | 395 | pub fn operandCount(self: *const Br) usize { |
| 366 | return 0; | |
| 396 | return 1; | |
| 367 | 397 | } |
| 368 | 398 | pub fn getOperand(self: *const Br, index: usize) ?*Inst { |
| 369 | 399 | if (index == 0) |
src/zir.zig+26-2| ... | ... | @@ -1634,6 +1634,12 @@ const DumpTzir = struct { |
| 1634 | 1634 | try dtz.findConst(br.operand); |
| 1635 | 1635 | }, |
| 1636 | 1636 | |
| 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 | 1643 | .brvoid => { |
| 1638 | 1644 | const brvoid = inst.castTag(.brvoid).?; |
| 1639 | 1645 | try dtz.findConst(&brvoid.block.base); |
| ... | ... | @@ -1779,6 +1785,24 @@ const DumpTzir = struct { |
| 1779 | 1785 | } |
| 1780 | 1786 | }, |
| 1781 | 1787 | |
| 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 | 1806 | .brvoid => { |
| 1783 | 1807 | const brvoid = inst.castTag(.brvoid).?; |
| 1784 | 1808 | const kinky = try dtz.writeInst(writer, &brvoid.block.base); |
| ... | ... | @@ -1792,7 +1816,7 @@ const DumpTzir = struct { |
| 1792 | 1816 | .block => { |
| 1793 | 1817 | const block = inst.castTag(.block).?; |
| 1794 | 1818 | |
| 1795 | try writer.writeAll("\n"); | |
| 1819 | try writer.writeAll("{\n"); | |
| 1796 | 1820 | |
| 1797 | 1821 | const old_indent = dtz.indent; |
| 1798 | 1822 | dtz.indent += 2; |
| ... | ... | @@ -1800,7 +1824,7 @@ const DumpTzir = struct { |
| 1800 | 1824 | dtz.indent = old_indent; |
| 1801 | 1825 | |
| 1802 | 1826 | try writer.writeByteNTimes(' ', dtz.indent); |
| 1803 | try writer.writeAll(")\n"); | |
| 1827 | try writer.writeAll("})\n"); | |
| 1804 | 1828 | }, |
| 1805 | 1829 | |
| 1806 | 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 | 664 | defer tracy.end(); |
| 665 | 665 | const parent_block = scope.cast(Scope.Block).?; |
| 666 | 666 | |
| 667 | var child_block: Scope.Block = .{ | |
| 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 | }; | |
| 667 | var child_block = parent_block.makeSubBlock(); | |
| 680 | 668 | defer child_block.instructions.deinit(mod.gpa); |
| 669 | child_block.is_comptime = child_block.is_comptime or is_comptime; | |
| 681 | 670 | |
| 682 | 671 | try analyzeBody(mod, &child_block, inst.positionals.body); |
| 683 | 672 | |
| ... | ... | @@ -728,6 +717,7 @@ fn zirBlock( |
| 728 | 717 | .zir_block = inst, |
| 729 | 718 | .merges = .{ |
| 730 | 719 | .results = .{}, |
| 720 | .br_list = .{}, | |
| 731 | 721 | .block_inst = block_inst, |
| 732 | 722 | }, |
| 733 | 723 | }), |
| ... | ... | @@ -739,6 +729,7 @@ fn zirBlock( |
| 739 | 729 | |
| 740 | 730 | defer child_block.instructions.deinit(mod.gpa); |
| 741 | 731 | defer merges.results.deinit(mod.gpa); |
| 732 | defer merges.br_list.deinit(mod.gpa); | |
| 742 | 733 | |
| 743 | 734 | try analyzeBody(mod, &child_block, inst.positionals.body); |
| 744 | 735 | |
| ... | ... | @@ -772,22 +763,53 @@ fn analyzeBlockBody( |
| 772 | 763 | const last_inst = child_block.instructions.items[last_inst_index]; |
| 773 | 764 | if (last_inst.breakBlock()) |br_block| { |
| 774 | 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. | |
| 776 | // Here we omit the break instruction. | |
| 766 | // No need for a block instruction. We can put the new instructions directly | |
| 767 | // into the parent block. Here we omit the break instruction. | |
| 777 | 768 | const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items[0..last_inst_index]); |
| 778 | 769 | try parent_block.instructions.appendSlice(mod.gpa, copied_instructions); |
| 779 | 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. | |
| 784 | assert(!child_block.is_comptime); // We should have already got a compile error in the condbr condition. | |
| 774 | // It is impossible to have the number of results be > 1 in a comptime scope. | |
| 775 | assert(!child_block.is_comptime); // Should already got a compile error in the condbr condition. | |
| 785 | 776 | |
| 786 | 777 | // Need to set the type and emit the Block instruction. This allows machine code generation |
| 787 | 778 | // to emit a jump instruction to after the block when it encounters the break. |
| 788 | 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); | |
| 790 | merges.block_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) }; | |
| 780 | const resolved_ty = try mod.resolvePeerTypes(scope, merges.results.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 | 813 | return &merges.block_inst.base; |
| 792 | 814 | } |
| 793 | 815 | |
| ... | ... | @@ -827,9 +849,28 @@ fn analyzeBreak( |
| 827 | 849 | while (opt_block) |block| { |
| 828 | 850 | if (block.label) |*label| { |
| 829 | 851 | if (label.zir_block == zir_block) { |
| 830 | try label.merges.results.append(mod.gpa, operand); | |
| 831 | 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 | 876 | opt_block = block.parent; |
| ... | ... | @@ -980,6 +1021,7 @@ fn zirCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst { |
| 980 | 1021 | .casted_args = casted_args, |
| 981 | 1022 | .merges = .{ |
| 982 | 1023 | .results = .{}, |
| 1024 | .br_list = .{}, | |
| 983 | 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 | 1046 | |
| 1005 | 1047 | defer child_block.instructions.deinit(mod.gpa); |
| 1006 | 1048 | defer merges.results.deinit(mod.gpa); |
| 1049 | defer merges.br_list.deinit(mod.gpa); | |
| 1007 | 1050 | |
| 1008 | 1051 | try mod.emitBackwardBranch(&child_block, inst.base.src); |
| 1009 | 1052 | |
| ... | ... | @@ -2194,7 +2237,8 @@ fn zirReturn(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst |
| 2194 | 2237 | if (b.inlining) |inlining| { |
| 2195 | 2238 | // We are inlining a function call; rewrite the `ret` as a `break`. |
| 2196 | 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 | } |
| 2199 | 2243 | |
| 2200 | 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 | 2252 | // We are inlining a function call; rewrite the `retvoid` as a `breakvoid`. |
| 2209 | 2253 | const void_inst = try mod.constVoid(scope, inst.base.src); |
| 2210 | 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 | } |
| 2213 | 2258 | |
| 2214 | 2259 | if (b.func) |func| { |