| author | |
| committer | |
| log | 4168b01e7a6fcae6f541d098491a4e1e00041f1c |
| tree | db12efd67abff9faea42acc70c12204148cd7bc2 |
| parent | a5a012e8599e57da3e80ff770f9de492037e4f4a |
| signature |
4 files changed, 344 insertions(+), 91 deletions(-)
src/arch/aarch64/CodeGen.zig+166-29| ... | ... | @@ -177,7 +177,7 @@ const StackAllocation = struct { |
| 177 | 177 | }; |
| 178 | 178 | |
| 179 | 179 | const BlockData = struct { |
| 180 | relocs: std.ArrayListUnmanaged(Reloc), | |
| 180 | relocs: std.ArrayListUnmanaged(Mir.Inst.Index), | |
| 181 | 181 | /// The first break instruction encounters `null` here and chooses a |
| 182 | 182 | /// machine code value for the block result, populating this field. |
| 183 | 183 | /// Following break instructions encounter that value and use it for |
| ... | ... | @@ -185,18 +185,6 @@ const BlockData = struct { |
| 185 | 185 | mcv: MCValue, |
| 186 | 186 | }; |
| 187 | 187 | |
| 188 | const Reloc = union(enum) { | |
| 189 | /// The value is an offset into the `Function` `code` from the beginning. | |
| 190 | /// To perform the reloc, write 32-bit signed little-endian integer | |
| 191 | /// which is a relative jump, based on the address following the reloc. | |
| 192 | rel32: usize, | |
| 193 | /// A branch in the ARM instruction set | |
| 194 | arm_branch: struct { | |
| 195 | pos: usize, | |
| 196 | cond: @import("../arm/bits.zig").Condition, | |
| 197 | }, | |
| 198 | }; | |
| 199 | ||
| 200 | 188 | const BigTomb = struct { |
| 201 | 189 | function: *Self, |
| 202 | 190 | inst: Air.Inst.Index, |
| ... | ... | @@ -426,6 +414,12 @@ fn gen(self: *Self) !void { |
| 426 | 414 | }); |
| 427 | 415 | } |
| 428 | 416 | |
| 417 | // add sp, sp, #stack_size | |
| 418 | _ = try self.addInst(.{ | |
| 419 | .tag = .add_immediate, | |
| 420 | .data = .{ .rr_imm12_sh = .{ .rd = .xzr, .rn = .xzr, .imm12 = @intCast(u12, aligned_stack_end) } }, | |
| 421 | }); | |
| 422 | ||
| 429 | 423 | // ldp fp, lr, [sp], #16 |
| 430 | 424 | _ = try self.addInst(.{ |
| 431 | 425 | .tag = .ldp, |
| ... | ... | @@ -437,12 +431,6 @@ fn gen(self: *Self) !void { |
| 437 | 431 | } }, |
| 438 | 432 | }); |
| 439 | 433 | |
| 440 | // add sp, sp, #stack_size | |
| 441 | _ = try self.addInst(.{ | |
| 442 | .tag = .add_immediate, | |
| 443 | .data = .{ .rr_imm12_sh = .{ .rd = .xzr, .rn = .xzr, .imm12 = @intCast(u12, aligned_stack_end) } }, | |
| 444 | }); | |
| 445 | ||
| 446 | 434 | // ret lr |
| 447 | 435 | _ = try self.addInst(.{ |
| 448 | 436 | .tag = .ret, |
| ... | ... | @@ -1358,7 +1346,9 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void { |
| 1358 | 1346 | const stack_offset = try self.allocMem(inst, abi_size, abi_align); |
| 1359 | 1347 | try self.genSetStack(ty, stack_offset, MCValue{ .register = reg }); |
| 1360 | 1348 | |
| 1361 | break :blk MCValue{ .stack_offset = stack_offset }; | |
| 1349 | // TODO correct loading and storing from memory | |
| 1350 | // break :blk MCValue{ .stack_offset = stack_offset }; | |
| 1351 | break :blk result; | |
| 1362 | 1352 | }, |
| 1363 | 1353 | else => result, |
| 1364 | 1354 | }; |
| ... | ... | @@ -1734,9 +1724,153 @@ fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void { |
| 1734 | 1724 | } |
| 1735 | 1725 | |
| 1736 | 1726 | fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 1737 | _ = inst; | |
| 1727 | const pl_op = self.air.instructions.items(.data)[inst].pl_op; | |
| 1728 | const cond = try self.resolveInst(pl_op.operand); | |
| 1729 | const extra = self.air.extraData(Air.CondBr, pl_op.payload); | |
| 1730 | const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len]; | |
| 1731 | const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; | |
| 1732 | const liveness_condbr = self.liveness.getCondBr(inst); | |
| 1733 | ||
| 1734 | const reloc: Mir.Inst.Index = switch (cond) { | |
| 1735 | .compare_flags_signed, | |
| 1736 | .compare_flags_unsigned, | |
| 1737 | => try self.addInst(.{ | |
| 1738 | .tag = .b_cond, | |
| 1739 | .data = .{ | |
| 1740 | .inst_cond = .{ | |
| 1741 | .inst = undefined, // populated later through performReloc | |
| 1742 | .cond = switch (cond) { | |
| 1743 | .compare_flags_signed => |cmp_op| blk: { | |
| 1744 | // Here we map to the opposite condition because the jump is to the false branch. | |
| 1745 | const condition = Instruction.Condition.fromCompareOperatorSigned(cmp_op); | |
| 1746 | break :blk condition.negate(); | |
| 1747 | }, | |
| 1748 | .compare_flags_unsigned => |cmp_op| blk: { | |
| 1749 | // Here we map to the opposite condition because the jump is to the false branch. | |
| 1750 | const condition = Instruction.Condition.fromCompareOperatorUnsigned(cmp_op); | |
| 1751 | break :blk condition.negate(); | |
| 1752 | }, | |
| 1753 | else => unreachable, | |
| 1754 | }, | |
| 1755 | }, | |
| 1756 | }, | |
| 1757 | }), | |
| 1758 | else => return self.fail("TODO implement condr when condition is {s}", .{@tagName(cond)}), | |
| 1759 | }; | |
| 1760 | ||
| 1761 | // Capture the state of register and stack allocation state so that we can revert to it. | |
| 1762 | const parent_next_stack_offset = self.next_stack_offset; | |
| 1763 | const parent_free_registers = self.register_manager.free_registers; | |
| 1764 | var parent_stack = try self.stack.clone(self.gpa); | |
| 1765 | defer parent_stack.deinit(self.gpa); | |
| 1766 | const parent_registers = self.register_manager.registers; | |
| 1738 | 1767 | |
| 1739 | return self.fail("TODO implement condbr {}", .{self.target.cpu.arch}); | |
| 1768 | try self.branch_stack.append(.{}); | |
| 1769 | ||
| 1770 | try self.ensureProcessDeathCapacity(liveness_condbr.then_deaths.len); | |
| 1771 | for (liveness_condbr.then_deaths) |operand| { | |
| 1772 | self.processDeath(operand); | |
| 1773 | } | |
| 1774 | try self.genBody(then_body); | |
| 1775 | ||
| 1776 | // Revert to the previous register and stack allocation state. | |
| 1777 | ||
| 1778 | var saved_then_branch = self.branch_stack.pop(); | |
| 1779 | defer saved_then_branch.deinit(self.gpa); | |
| 1780 | ||
| 1781 | self.register_manager.registers = parent_registers; | |
| 1782 | ||
| 1783 | self.stack.deinit(self.gpa); | |
| 1784 | self.stack = parent_stack; | |
| 1785 | parent_stack = .{}; | |
| 1786 | ||
| 1787 | self.next_stack_offset = parent_next_stack_offset; | |
| 1788 | self.register_manager.free_registers = parent_free_registers; | |
| 1789 | ||
| 1790 | try self.performReloc(reloc); | |
| 1791 | const else_branch = self.branch_stack.addOneAssumeCapacity(); | |
| 1792 | else_branch.* = .{}; | |
| 1793 | ||
| 1794 | try self.ensureProcessDeathCapacity(liveness_condbr.else_deaths.len); | |
| 1795 | for (liveness_condbr.else_deaths) |operand| { | |
| 1796 | self.processDeath(operand); | |
| 1797 | } | |
| 1798 | try self.genBody(else_body); | |
| 1799 | ||
| 1800 | // At this point, each branch will possibly have conflicting values for where | |
| 1801 | // each instruction is stored. They agree, however, on which instructions are alive/dead. | |
| 1802 | // We use the first ("then") branch as canonical, and here emit | |
| 1803 | // instructions into the second ("else") branch to make it conform. | |
| 1804 | // We continue respect the data structure semantic guarantees of the else_branch so | |
| 1805 | // that we can use all the code emitting abstractions. This is why at the bottom we | |
| 1806 | // assert that parent_branch.free_registers equals the saved_then_branch.free_registers | |
| 1807 | // rather than assigning it. | |
| 1808 | const parent_branch = &self.branch_stack.items[self.branch_stack.items.len - 2]; | |
| 1809 | try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, else_branch.inst_table.count()); | |
| 1810 | ||
| 1811 | const else_slice = else_branch.inst_table.entries.slice(); | |
| 1812 | const else_keys = else_slice.items(.key); | |
| 1813 | const else_values = else_slice.items(.value); | |
| 1814 | for (else_keys) |else_key, else_idx| { | |
| 1815 | const else_value = else_values[else_idx]; | |
| 1816 | const canon_mcv = if (saved_then_branch.inst_table.fetchSwapRemove(else_key)) |then_entry| blk: { | |
| 1817 | // The instruction's MCValue is overridden in both branches. | |
| 1818 | parent_branch.inst_table.putAssumeCapacity(else_key, then_entry.value); | |
| 1819 | if (else_value == .dead) { | |
| 1820 | assert(then_entry.value == .dead); | |
| 1821 | continue; | |
| 1822 | } | |
| 1823 | break :blk then_entry.value; | |
| 1824 | } else blk: { | |
| 1825 | if (else_value == .dead) | |
| 1826 | continue; | |
| 1827 | // The instruction is only overridden in the else branch. | |
| 1828 | var i: usize = self.branch_stack.items.len - 2; | |
| 1829 | while (true) { | |
| 1830 | i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead? | |
| 1831 | if (self.branch_stack.items[i].inst_table.get(else_key)) |mcv| { | |
| 1832 | assert(mcv != .dead); | |
| 1833 | break :blk mcv; | |
| 1834 | } | |
| 1835 | } | |
| 1836 | }; | |
| 1837 | log.debug("consolidating else_entry {d} {}=>{}", .{ else_key, else_value, canon_mcv }); | |
| 1838 | // TODO make sure the destination stack offset / register does not already have something | |
| 1839 | // going on there. | |
| 1840 | try self.setRegOrMem(self.air.typeOfIndex(else_key), canon_mcv, else_value); | |
| 1841 | // TODO track the new register / stack allocation | |
| 1842 | } | |
| 1843 | try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, saved_then_branch.inst_table.count()); | |
| 1844 | const then_slice = saved_then_branch.inst_table.entries.slice(); | |
| 1845 | const then_keys = then_slice.items(.key); | |
| 1846 | const then_values = then_slice.items(.value); | |
| 1847 | for (then_keys) |then_key, then_idx| { | |
| 1848 | const then_value = then_values[then_idx]; | |
| 1849 | // We already deleted the items from this table that matched the else_branch. | |
| 1850 | // So these are all instructions that are only overridden in the then branch. | |
| 1851 | parent_branch.inst_table.putAssumeCapacity(then_key, then_value); | |
| 1852 | if (then_value == .dead) | |
| 1853 | continue; | |
| 1854 | const parent_mcv = blk: { | |
| 1855 | var i: usize = self.branch_stack.items.len - 2; | |
| 1856 | while (true) { | |
| 1857 | i -= 1; | |
| 1858 | if (self.branch_stack.items[i].inst_table.get(then_key)) |mcv| { | |
| 1859 | assert(mcv != .dead); | |
| 1860 | break :blk mcv; | |
| 1861 | } | |
| 1862 | } | |
| 1863 | }; | |
| 1864 | log.debug("consolidating then_entry {d} {}=>{}", .{ then_key, parent_mcv, then_value }); | |
| 1865 | // TODO make sure the destination stack offset / register does not already have something | |
| 1866 | // going on there. | |
| 1867 | try self.setRegOrMem(self.air.typeOfIndex(then_key), parent_mcv, then_value); | |
| 1868 | // TODO track the new register / stack allocation | |
| 1869 | } | |
| 1870 | ||
| 1871 | self.branch_stack.pop().deinit(self.gpa); | |
| 1872 | ||
| 1873 | return self.finishAir(inst, .unreach, .{ pl_op.operand, .none, .none }); | |
| 1740 | 1874 | } |
| 1741 | 1875 | |
| 1742 | 1876 | fn isNull(self: *Self, operand: MCValue) !MCValue { |
| ... | ... | @@ -1927,10 +2061,12 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { |
| 1927 | 2061 | return self.fail("TODO airSwitch for {}", .{self.target.cpu.arch}); |
| 1928 | 2062 | } |
| 1929 | 2063 | |
| 1930 | fn performReloc(self: *Self, reloc: Reloc) !void { | |
| 1931 | switch (reloc) { | |
| 1932 | .rel32 => return self.fail("TODO reloc.rel32 for {}", .{self.target.cpu.arch}), | |
| 1933 | .arm_branch => return self.fail("TODO reloc.arm_branch for {}", .{self.target.cpu.arch}), | |
| 2064 | fn performReloc(self: *Self, inst: Mir.Inst.Index) !void { | |
| 2065 | const tag = self.mir_instructions.items(.tag)[inst]; | |
| 2066 | switch (tag) { | |
| 2067 | .b_cond => self.mir_instructions.items(.data)[inst].inst_cond.inst = @intCast(Air.Inst.Index, self.mir_instructions.len), | |
| 2068 | .b => self.mir_instructions.items(.data)[inst].inst = @intCast(Air.Inst.Index, self.mir_instructions.len), | |
| 2069 | else => unreachable, | |
| 1934 | 2070 | } |
| 1935 | 2071 | } |
| 1936 | 2072 | |
| ... | ... | @@ -1970,7 +2106,10 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { |
| 1970 | 2106 | // Emit a jump with a relocation. It will be patched up after the block ends. |
| 1971 | 2107 | try block_data.relocs.ensureUnusedCapacity(self.gpa, 1); |
| 1972 | 2108 | |
| 1973 | return self.fail("TODO implement brvoid for {}", .{self.target.cpu.arch}); | |
| 2109 | block_data.relocs.appendAssumeCapacity(try self.addInst(.{ | |
| 2110 | .tag = .b, | |
| 2111 | .data = .{ .inst = undefined }, // populated later through performReloc | |
| 2112 | })); | |
| 1974 | 2113 | } |
| 1975 | 2114 | |
| 1976 | 2115 | fn airAsm(self: *Self, inst: Air.Inst.Index) !void { |
| ... | ... | @@ -2117,8 +2256,6 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro |
| 2117 | 2256 | return self.fail("TODO implement set stack variable from embedded_in_code", .{}); |
| 2118 | 2257 | }, |
| 2119 | 2258 | .register => |reg| { |
| 2120 | _ = reg; | |
| 2121 | ||
| 2122 | 2259 | const abi_size = ty.abiSize(self.target.*); |
| 2123 | 2260 | const adj_off = stack_offset + abi_size; |
| 2124 | 2261 |
src/arch/aarch64/Emit.zig+138-60| ... | ... | @@ -14,6 +14,7 @@ const DW = std.dwarf; |
| 14 | 14 | const leb128 = std.leb; |
| 15 | 15 | const Instruction = bits.Instruction; |
| 16 | 16 | const Register = bits.Register; |
| 17 | const log = std.log.scoped(.aarch64_emit); | |
| 17 | 18 | const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; |
| 18 | 19 | |
| 19 | 20 | mir: Mir, |
| ... | ... | @@ -47,9 +48,16 @@ const InnerError = error{ |
| 47 | 48 | }; |
| 48 | 49 | |
| 49 | 50 | const BranchType = enum { |
| 51 | b_cond, | |
| 50 | 52 | unconditional_branch_immediate, |
| 51 | 53 | |
| 52 | const default = BranchType.unconditional_branch_immediate; | |
| 54 | fn default(tag: Mir.Inst.Tag) BranchType { | |
| 55 | return switch (tag) { | |
| 56 | .b, .bl => .unconditional_branch_immediate, | |
| 57 | .b_cond => .b_cond, | |
| 58 | else => unreachable, | |
| 59 | }; | |
| 60 | } | |
| 53 | 61 | }; |
| 54 | 62 | |
| 55 | 63 | pub fn emitMir( |
| ... | ... | @@ -68,6 +76,8 @@ pub fn emitMir( |
| 68 | 76 | .cmp_immediate => try emit.mirAddSubtractImmediate(inst), |
| 69 | 77 | .sub_immediate => try emit.mirAddSubtractImmediate(inst), |
| 70 | 78 | |
| 79 | .b_cond => try emit.mirConditionalBranchImmediate(inst), | |
| 80 | ||
| 71 | 81 | .b => try emit.mirBranch(inst), |
| 72 | 82 | .bl => try emit.mirBranch(inst), |
| 73 | 83 | |
| ... | ... | @@ -112,29 +122,50 @@ pub fn emitMir( |
| 112 | 122 | } |
| 113 | 123 | |
| 114 | 124 | pub fn deinit(emit: *Emit) void { |
| 125 | var iter = emit.branch_forward_origins.valueIterator(); | |
| 126 | while (iter.next()) |origin_list| { | |
| 127 | origin_list.deinit(emit.bin_file.allocator); | |
| 128 | } | |
| 129 | ||
| 115 | 130 | emit.branch_types.deinit(emit.bin_file.allocator); |
| 116 | 131 | emit.branch_forward_origins.deinit(emit.bin_file.allocator); |
| 117 | 132 | emit.code_offset_mapping.deinit(emit.bin_file.allocator); |
| 118 | 133 | emit.* = undefined; |
| 119 | 134 | } |
| 120 | 135 | |
| 121 | fn optimalBranchType(emit: *Emit, offset: i64) !BranchType { | |
| 136 | fn optimalBranchType(emit: *Emit, tag: Mir.Inst.Tag, offset: i64) !BranchType { | |
| 122 | 137 | assert(offset & 0b11 == 0); |
| 123 | 138 | |
| 124 | // TODO handle conditional branches | |
| 125 | if (std.math.cast(i26, offset >> 2)) |_| { | |
| 126 | return BranchType.unconditional_branch_immediate; | |
| 127 | } else |_| { | |
| 128 | return emit.fail("TODO support branches larger than +-128 MiB", .{}); | |
| 139 | switch (tag) { | |
| 140 | .b, .bl => { | |
| 141 | if (std.math.cast(i26, offset >> 2)) |_| { | |
| 142 | return BranchType.unconditional_branch_immediate; | |
| 143 | } else |_| { | |
| 144 | return emit.fail("TODO support branches larger than +-128 MiB", .{}); | |
| 145 | } | |
| 146 | }, | |
| 147 | .b_cond => { | |
| 148 | if (std.math.cast(i19, offset >> 2)) |_| { | |
| 149 | return BranchType.b_cond; | |
| 150 | } else |_| { | |
| 151 | return emit.fail("TODO support conditional branches larger than +-1 MiB", .{}); | |
| 152 | } | |
| 153 | }, | |
| 154 | else => unreachable, | |
| 129 | 155 | } |
| 130 | 156 | } |
| 131 | 157 | |
| 132 | 158 | fn instructionSize(emit: *Emit, inst: Mir.Inst.Index) usize { |
| 133 | 159 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 134 | switch (tag) { | |
| 135 | .b, .bl => switch (emit.branch_types.get(inst).?) { | |
| 160 | ||
| 161 | if (isBranch(tag)) { | |
| 162 | switch (emit.branch_types.get(inst).?) { | |
| 136 | 163 | .unconditional_branch_immediate => return 4, |
| 137 | }, | |
| 164 | .b_cond => return 4, | |
| 165 | } | |
| 166 | } | |
| 167 | ||
| 168 | switch (tag) { | |
| 138 | 169 | .load_memory => { |
| 139 | 170 | if (emit.bin_file.options.pie) { |
| 140 | 171 | // adrp, ldr |
| ... | ... | @@ -151,10 +182,32 @@ fn instructionSize(emit: *Emit, inst: Mir.Inst.Index) usize { |
| 151 | 182 | return 5 * 4; |
| 152 | 183 | } |
| 153 | 184 | }, |
| 185 | .call_extern => return 4, | |
| 186 | .dbg_line, | |
| 187 | .dbg_epilogue_begin, | |
| 188 | .dbg_prologue_end, | |
| 189 | => return 0, | |
| 154 | 190 | else => return 4, |
| 155 | 191 | } |
| 156 | 192 | } |
| 157 | 193 | |
| 194 | fn isBranch(tag: Mir.Inst.Tag) bool { | |
| 195 | return switch (tag) { | |
| 196 | .b, .bl, .b_cond => true, | |
| 197 | else => false, | |
| 198 | }; | |
| 199 | } | |
| 200 | ||
| 201 | fn branchTarget(emit: *Emit, inst: Mir.Inst.Index) Mir.Inst.Index { | |
| 202 | const tag = emit.mir.instructions.items(.tag)[inst]; | |
| 203 | ||
| 204 | switch (tag) { | |
| 205 | .b, .bl => return emit.mir.instructions.items(.data)[inst].inst, | |
| 206 | .b_cond => return emit.mir.instructions.items(.data)[inst].inst_cond.inst, | |
| 207 | else => unreachable, | |
| 208 | } | |
| 209 | } | |
| 210 | ||
| 158 | 211 | fn lowerBranches(emit: *Emit) !void { |
| 159 | 212 | const mir_tags = emit.mir.instructions.items(.tag); |
| 160 | 213 | const allocator = emit.bin_file.allocator; |
| ... | ... | @@ -167,41 +220,38 @@ fn lowerBranches(emit: *Emit) !void { |
| 167 | 220 | // generating MIR |
| 168 | 221 | for (mir_tags) |tag, index| { |
| 169 | 222 | const inst = @intCast(u32, index); |
| 170 | switch (tag) { | |
| 171 | .b, .bl => { | |
| 172 | const target_inst = emit.mir.instructions.items(.data)[inst].inst; | |
| 173 | ||
| 174 | // Remember this branch instruction | |
| 175 | try emit.branch_types.put(allocator, inst, BranchType.default); | |
| 176 | ||
| 177 | // Forward branches require some extra stuff: We only | |
| 178 | // know their offset once we arrive at the target | |
| 179 | // instruction. Therefore, we need to be able to | |
| 180 | // access the branch instruction when we visit the | |
| 181 | // target instruction in order to manipulate its type | |
| 182 | // etc. | |
| 183 | if (target_inst > inst) { | |
| 184 | // Remember the branch instruction index | |
| 185 | try emit.code_offset_mapping.put(allocator, inst, 0); | |
| 186 | ||
| 187 | if (emit.branch_forward_origins.getPtr(target_inst)) |origin_list| { | |
| 188 | try origin_list.append(allocator, inst); | |
| 189 | } else { | |
| 190 | var origin_list: std.ArrayListUnmanaged(Mir.Inst.Index) = .{}; | |
| 191 | try origin_list.append(allocator, inst); | |
| 192 | try emit.branch_forward_origins.put(allocator, target_inst, origin_list); | |
| 193 | } | |
| 223 | if (isBranch(tag)) { | |
| 224 | const target_inst = emit.branchTarget(inst); | |
| 225 | ||
| 226 | // Remember this branch instruction | |
| 227 | try emit.branch_types.put(allocator, inst, BranchType.default(tag)); | |
| 228 | ||
| 229 | // Forward branches require some extra stuff: We only | |
| 230 | // know their offset once we arrive at the target | |
| 231 | // instruction. Therefore, we need to be able to | |
| 232 | // access the branch instruction when we visit the | |
| 233 | // target instruction in order to manipulate its type | |
| 234 | // etc. | |
| 235 | if (target_inst > inst) { | |
| 236 | // Remember the branch instruction index | |
| 237 | try emit.code_offset_mapping.put(allocator, inst, 0); | |
| 238 | ||
| 239 | if (emit.branch_forward_origins.getPtr(target_inst)) |origin_list| { | |
| 240 | try origin_list.append(allocator, inst); | |
| 241 | } else { | |
| 242 | var origin_list: std.ArrayListUnmanaged(Mir.Inst.Index) = .{}; | |
| 243 | try origin_list.append(allocator, inst); | |
| 244 | try emit.branch_forward_origins.put(allocator, target_inst, origin_list); | |
| 194 | 245 | } |
| 246 | } | |
| 195 | 247 | |
| 196 | // Remember the target instruction index so that we | |
| 197 | // update the real code offset in all future passes | |
| 198 | // | |
| 199 | // putNoClobber may not be used as the put operation | |
| 200 | // may clobber the entry when multiple branches branch | |
| 201 | // to the same target instruction | |
| 202 | try emit.code_offset_mapping.put(allocator, target_inst, 0); | |
| 203 | }, | |
| 204 | else => {}, // not a branch | |
| 248 | // Remember the target instruction index so that we | |
| 249 | // update the real code offset in all future passes | |
| 250 | // | |
| 251 | // putNoClobber may not be used as the put operation | |
| 252 | // may clobber the entry when multiple branches branch | |
| 253 | // to the same target instruction | |
| 254 | try emit.code_offset_mapping.put(allocator, target_inst, 0); | |
| 205 | 255 | } |
| 206 | 256 | } |
| 207 | 257 | |
| ... | ... | @@ -225,21 +275,20 @@ fn lowerBranches(emit: *Emit) !void { |
| 225 | 275 | |
| 226 | 276 | // If this instruction is a backward branch, calculate the |
| 227 | 277 | // offset, which may potentially update the branch type |
| 228 | switch (tag) { | |
| 229 | .b, .bl => { | |
| 230 | const target_inst = emit.mir.instructions.items(.data)[inst].inst; | |
| 231 | if (target_inst < inst) { | |
| 232 | const target_offset = emit.code_offset_mapping.get(target_inst).?; | |
| 233 | const offset = @intCast(i64, target_offset) - @intCast(i64, current_code_offset + 8); | |
| 234 | const branch_type = emit.branch_types.getPtr(inst).?; | |
| 235 | const optimal_branch_type = try emit.optimalBranchType(offset); | |
| 236 | if (branch_type.* != optimal_branch_type) { | |
| 237 | branch_type.* = optimal_branch_type; | |
| 238 | all_branches_lowered = false; | |
| 239 | } | |
| 278 | if (isBranch(tag)) { | |
| 279 | const target_inst = emit.branchTarget(inst); | |
| 280 | if (target_inst < inst) { | |
| 281 | const target_offset = emit.code_offset_mapping.get(target_inst).?; | |
| 282 | const offset = @intCast(i64, target_offset) - @intCast(i64, current_code_offset); | |
| 283 | const branch_type = emit.branch_types.getPtr(inst).?; | |
| 284 | const optimal_branch_type = try emit.optimalBranchType(tag, offset); | |
| 285 | if (branch_type.* != optimal_branch_type) { | |
| 286 | branch_type.* = optimal_branch_type; | |
| 287 | all_branches_lowered = false; | |
| 240 | 288 | } |
| 241 | }, | |
| 242 | else => {}, | |
| 289 | ||
| 290 | log.debug("lowerBranches: branch {} has offset {}", .{ inst, offset }); | |
| 291 | } | |
| 243 | 292 | } |
| 244 | 293 | |
| 245 | 294 | // If this instruction is the target of one or more |
| ... | ... | @@ -247,14 +296,17 @@ fn lowerBranches(emit: *Emit) !void { |
| 247 | 296 | // potentially update the branch type |
| 248 | 297 | if (emit.branch_forward_origins.get(inst)) |origin_list| { |
| 249 | 298 | for (origin_list.items) |forward_branch_inst| { |
| 299 | const branch_tag = emit.mir.instructions.items(.tag)[forward_branch_inst]; | |
| 250 | 300 | const forward_branch_inst_offset = emit.code_offset_mapping.get(forward_branch_inst).?; |
| 251 | const offset = @intCast(i64, forward_branch_inst_offset) - @intCast(i64, current_code_offset + 8); | |
| 301 | const offset = @intCast(i64, current_code_offset) - @intCast(i64, forward_branch_inst_offset); | |
| 252 | 302 | const branch_type = emit.branch_types.getPtr(forward_branch_inst).?; |
| 253 | const optimal_branch_type = try emit.optimalBranchType(offset); | |
| 303 | const optimal_branch_type = try emit.optimalBranchType(branch_tag, offset); | |
| 254 | 304 | if (branch_type.* != optimal_branch_type) { |
| 255 | 305 | branch_type.* = optimal_branch_type; |
| 256 | 306 | all_branches_lowered = false; |
| 257 | 307 | } |
| 308 | ||
| 309 | log.debug("lowerBranches: branch {} has offset {}", .{ forward_branch_inst, offset }); | |
| 258 | 310 | } |
| 259 | 311 | } |
| 260 | 312 | |
| ... | ... | @@ -368,12 +420,37 @@ fn mirAddSubtractImmediate(emit: *Emit, inst: Mir.Inst.Index) !void { |
| 368 | 420 | } |
| 369 | 421 | } |
| 370 | 422 | |
| 423 | fn mirConditionalBranchImmediate(emit: *Emit, inst: Mir.Inst.Index) !void { | |
| 424 | const tag = emit.mir.instructions.items(.tag)[inst]; | |
| 425 | const inst_cond = emit.mir.instructions.items(.data)[inst].inst_cond; | |
| 426 | ||
| 427 | const offset = @intCast(i64, emit.code_offset_mapping.get(inst_cond.inst).?) - @intCast(i64, emit.code.items.len); | |
| 428 | const branch_type = emit.branch_types.get(inst).?; | |
| 429 | log.debug("mirConditionalBranchImmediate: {} offset={}", .{ inst, offset }); | |
| 430 | ||
| 431 | switch (branch_type) { | |
| 432 | .b_cond => switch (tag) { | |
| 433 | .b_cond => try emit.writeInstruction(Instruction.bCond(inst_cond.cond, @intCast(i21, offset))), | |
| 434 | else => unreachable, | |
| 435 | }, | |
| 436 | else => unreachable, | |
| 437 | } | |
| 438 | } | |
| 439 | ||
| 371 | 440 | fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void { |
| 372 | 441 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 373 | 442 | const target_inst = emit.mir.instructions.items(.data)[inst].inst; |
| 374 | 443 | |
| 375 | const offset = @intCast(i64, emit.code_offset_mapping.get(target_inst).?) - @intCast(i64, emit.code.items.len + 8); | |
| 444 | log.debug("branch {}(tag: {}) -> {}(tag: {})", .{ | |
| 445 | inst, | |
| 446 | tag, | |
| 447 | target_inst, | |
| 448 | emit.mir.instructions.items(.tag)[target_inst], | |
| 449 | }); | |
| 450 | ||
| 451 | const offset = @intCast(i64, emit.code_offset_mapping.get(target_inst).?) - @intCast(i64, emit.code.items.len); | |
| 376 | 452 | const branch_type = emit.branch_types.get(inst).?; |
| 453 | log.debug("mirBranch: {} offset={}", .{ inst, offset }); | |
| 377 | 454 | |
| 378 | 455 | switch (branch_type) { |
| 379 | 456 | .unconditional_branch_immediate => switch (tag) { |
| ... | ... | @@ -381,6 +458,7 @@ fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void { |
| 381 | 458 | .bl => try emit.writeInstruction(Instruction.bl(@intCast(i28, offset))), |
| 382 | 459 | else => unreachable, |
| 383 | 460 | }, |
| 461 | else => unreachable, | |
| 384 | 462 | } |
| 385 | 463 | } |
| 386 | 464 |
src/arch/aarch64/Mir.zig+11-2| ... | ... | @@ -26,6 +26,8 @@ pub const Inst = struct { |
| 26 | 26 | pub const Tag = enum(u16) { |
| 27 | 27 | /// Add (immediate) |
| 28 | 28 | add_immediate, |
| 29 | /// Branch conditionally | |
| 30 | b_cond, | |
| 29 | 31 | /// Branch |
| 30 | 32 | b, |
| 31 | 33 | /// Branch with Link |
| ... | ... | @@ -48,7 +50,7 @@ pub const Inst = struct { |
| 48 | 50 | dbg_epilogue_begin, |
| 49 | 51 | /// Pseudo-instruction: Update debug line |
| 50 | 52 | dbg_line, |
| 51 | /// Psuedo-instruction: Load memory | |
| 53 | /// Pseudo-instruction: Load memory | |
| 52 | 54 | /// |
| 53 | 55 | /// Payload is `LoadMemory` |
| 54 | 56 | load_memory, |
| ... | ... | @@ -103,7 +105,7 @@ pub const Inst = struct { |
| 103 | 105 | /// |
| 104 | 106 | /// Used by e.g. nop |
| 105 | 107 | nop: void, |
| 106 | /// Another instruction. | |
| 108 | /// Another instruction | |
| 107 | 109 | /// |
| 108 | 110 | /// Used by e.g. b |
| 109 | 111 | inst: Index, |
| ... | ... | @@ -123,6 +125,13 @@ pub const Inst = struct { |
| 123 | 125 | /// |
| 124 | 126 | /// Used by e.g. blr |
| 125 | 127 | reg: Register, |
| 128 | /// Another instruction and a condition | |
| 129 | /// | |
| 130 | /// Used by e.g. b_cond | |
| 131 | inst_cond: struct { | |
| 132 | inst: Index, | |
| 133 | cond: bits.Instruction.Condition, | |
| 134 | }, | |
| 126 | 135 | /// A register, an unsigned 16-bit immediate, and an optional shift |
| 127 | 136 | /// |
| 128 | 137 | /// Used by e.g. movz |
test/stage2/aarch64.zig+29| ... | ... | @@ -68,4 +68,33 @@ pub fn addCases(ctx: *TestContext) !void { |
| 68 | 68 | "", |
| 69 | 69 | ); |
| 70 | 70 | } |
| 71 | ||
| 72 | { | |
| 73 | var case = ctx.exe("conditional branches", linux_aarch64); | |
| 74 | ||
| 75 | case.addCompareOutput( | |
| 76 | \\pub fn main() void { | |
| 77 | \\ foo(123); | |
| 78 | \\} | |
| 79 | \\ | |
| 80 | \\fn foo(x: u64) void { | |
| 81 | \\ if (x > 42) { | |
| 82 | \\ print(); | |
| 83 | \\ } | |
| 84 | \\} | |
| 85 | \\ | |
| 86 | \\fn print() void { | |
| 87 | \\ asm volatile ("svc #0" | |
| 88 | \\ : | |
| 89 | \\ : [number] "{x8}" (64), | |
| 90 | \\ [arg1] "{x0}" (1), | |
| 91 | \\ [arg2] "{x1}" (@ptrToInt("Hello, World!\n")), | |
| 92 | \\ [arg3] "{x2}" ("Hello, World!\n".len), | |
| 93 | \\ : "memory", "cc" | |
| 94 | \\ ); | |
| 95 | \\} | |
| 96 | , | |
| 97 | "Hello, World!\n", | |
| 98 | ); | |
| 99 | } | |
| 71 | 100 | } |