authorgravatar for joachim.schmidt557@outlook.comJoachim Schmidt <joachim.schmidt557@outlook.com> 2021-11-10 19:47:56+01:00
committergravatar for joachim.schmidt557@outlook.comJoachim Schmidt <joachim.schmidt557@outlook.com> 2021-11-10 20:05:35+01:00
log4168b01e7a6fcae6f541d098491a4e1e00041f1c
treedb12efd67abff9faea42acc70c12204148cd7bc2
parenta5a012e8599e57da3e80ff770f9de492037e4f4a
signaturelock-open Commit is signed but in an unrecognized format.

stage2 AArch64: implement airCondBr


4 files changed, 344 insertions(+), 91 deletions(-)

src/arch/aarch64/CodeGen.zig+166-29
......@@ -177,7 +177,7 @@ const StackAllocation = struct {
177177};
178178
179179const BlockData = struct {
180 relocs: std.ArrayListUnmanaged(Reloc),
180 relocs: std.ArrayListUnmanaged(Mir.Inst.Index),
181181 /// The first break instruction encounters `null` here and chooses a
182182 /// machine code value for the block result, populating this field.
183183 /// Following break instructions encounter that value and use it for
......@@ -185,18 +185,6 @@ const BlockData = struct {
185185 mcv: MCValue,
186186};
187187
188const 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
200188const BigTomb = struct {
201189 function: *Self,
202190 inst: Air.Inst.Index,
......@@ -426,6 +414,12 @@ fn gen(self: *Self) !void {
426414 });
427415 }
428416
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
429423 // ldp fp, lr, [sp], #16
430424 _ = try self.addInst(.{
431425 .tag = .ldp,
......@@ -437,12 +431,6 @@ fn gen(self: *Self) !void {
437431 } },
438432 });
439433
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
446434 // ret lr
447435 _ = try self.addInst(.{
448436 .tag = .ret,
......@@ -1358,7 +1346,9 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {
13581346 const stack_offset = try self.allocMem(inst, abi_size, abi_align);
13591347 try self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
13601348
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;
13621352 },
13631353 else => result,
13641354 };
......@@ -1734,9 +1724,153 @@ fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void {
17341724}
17351725
17361726fn 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;
17381767
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 });
17401874}
17411875
17421876fn isNull(self: *Self, operand: MCValue) !MCValue {
......@@ -1927,10 +2061,12 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
19272061 return self.fail("TODO airSwitch for {}", .{self.target.cpu.arch});
19282062}
19292063
1930fn 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}),
2064fn 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,
19342070 }
19352071}
19362072
......@@ -1970,7 +2106,10 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void {
19702106 // Emit a jump with a relocation. It will be patched up after the block ends.
19712107 try block_data.relocs.ensureUnusedCapacity(self.gpa, 1);
19722108
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 }));
19742113}
19752114
19762115fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
......@@ -2117,8 +2256,6 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
21172256 return self.fail("TODO implement set stack variable from embedded_in_code", .{});
21182257 },
21192258 .register => |reg| {
2120 _ = reg;
2121
21222259 const abi_size = ty.abiSize(self.target.*);
21232260 const adj_off = stack_offset + abi_size;
21242261
src/arch/aarch64/Emit.zig+138-60
......@@ -14,6 +14,7 @@ const DW = std.dwarf;
1414const leb128 = std.leb;
1515const Instruction = bits.Instruction;
1616const Register = bits.Register;
17const log = std.log.scoped(.aarch64_emit);
1718const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
1819
1920mir: Mir,
......@@ -47,9 +48,16 @@ const InnerError = error{
4748};
4849
4950const BranchType = enum {
51 b_cond,
5052 unconditional_branch_immediate,
5153
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 }
5361};
5462
5563pub fn emitMir(
......@@ -68,6 +76,8 @@ pub fn emitMir(
6876 .cmp_immediate => try emit.mirAddSubtractImmediate(inst),
6977 .sub_immediate => try emit.mirAddSubtractImmediate(inst),
7078
79 .b_cond => try emit.mirConditionalBranchImmediate(inst),
80
7181 .b => try emit.mirBranch(inst),
7282 .bl => try emit.mirBranch(inst),
7383
......@@ -112,29 +122,50 @@ pub fn emitMir(
112122}
113123
114124pub 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
115130 emit.branch_types.deinit(emit.bin_file.allocator);
116131 emit.branch_forward_origins.deinit(emit.bin_file.allocator);
117132 emit.code_offset_mapping.deinit(emit.bin_file.allocator);
118133 emit.* = undefined;
119134}
120135
121fn optimalBranchType(emit: *Emit, offset: i64) !BranchType {
136fn optimalBranchType(emit: *Emit, tag: Mir.Inst.Tag, offset: i64) !BranchType {
122137 assert(offset & 0b11 == 0);
123138
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,
129155 }
130156}
131157
132158fn instructionSize(emit: *Emit, inst: Mir.Inst.Index) usize {
133159 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).?) {
136163 .unconditional_branch_immediate => return 4,
137 },
164 .b_cond => return 4,
165 }
166 }
167
168 switch (tag) {
138169 .load_memory => {
139170 if (emit.bin_file.options.pie) {
140171 // adrp, ldr
......@@ -151,10 +182,32 @@ fn instructionSize(emit: *Emit, inst: Mir.Inst.Index) usize {
151182 return 5 * 4;
152183 }
153184 },
185 .call_extern => return 4,
186 .dbg_line,
187 .dbg_epilogue_begin,
188 .dbg_prologue_end,
189 => return 0,
154190 else => return 4,
155191 }
156192}
157193
194fn isBranch(tag: Mir.Inst.Tag) bool {
195 return switch (tag) {
196 .b, .bl, .b_cond => true,
197 else => false,
198 };
199}
200
201fn 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
158211fn lowerBranches(emit: *Emit) !void {
159212 const mir_tags = emit.mir.instructions.items(.tag);
160213 const allocator = emit.bin_file.allocator;
......@@ -167,41 +220,38 @@ fn lowerBranches(emit: *Emit) !void {
167220 // generating MIR
168221 for (mir_tags) |tag, index| {
169222 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);
194245 }
246 }
195247
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);
205255 }
206256 }
207257
......@@ -225,21 +275,20 @@ fn lowerBranches(emit: *Emit) !void {
225275
226276 // If this instruction is a backward branch, calculate the
227277 // 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;
240288 }
241 },
242 else => {},
289
290 log.debug("lowerBranches: branch {} has offset {}", .{ inst, offset });
291 }
243292 }
244293
245294 // If this instruction is the target of one or more
......@@ -247,14 +296,17 @@ fn lowerBranches(emit: *Emit) !void {
247296 // potentially update the branch type
248297 if (emit.branch_forward_origins.get(inst)) |origin_list| {
249298 for (origin_list.items) |forward_branch_inst| {
299 const branch_tag = emit.mir.instructions.items(.tag)[forward_branch_inst];
250300 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);
252302 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);
254304 if (branch_type.* != optimal_branch_type) {
255305 branch_type.* = optimal_branch_type;
256306 all_branches_lowered = false;
257307 }
308
309 log.debug("lowerBranches: branch {} has offset {}", .{ forward_branch_inst, offset });
258310 }
259311 }
260312
......@@ -368,12 +420,37 @@ fn mirAddSubtractImmediate(emit: *Emit, inst: Mir.Inst.Index) !void {
368420 }
369421}
370422
423fn 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
371440fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void {
372441 const tag = emit.mir.instructions.items(.tag)[inst];
373442 const target_inst = emit.mir.instructions.items(.data)[inst].inst;
374443
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);
376452 const branch_type = emit.branch_types.get(inst).?;
453 log.debug("mirBranch: {} offset={}", .{ inst, offset });
377454
378455 switch (branch_type) {
379456 .unconditional_branch_immediate => switch (tag) {
......@@ -381,6 +458,7 @@ fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void {
381458 .bl => try emit.writeInstruction(Instruction.bl(@intCast(i28, offset))),
382459 else => unreachable,
383460 },
461 else => unreachable,
384462 }
385463}
386464
src/arch/aarch64/Mir.zig+11-2
......@@ -26,6 +26,8 @@ pub const Inst = struct {
2626 pub const Tag = enum(u16) {
2727 /// Add (immediate)
2828 add_immediate,
29 /// Branch conditionally
30 b_cond,
2931 /// Branch
3032 b,
3133 /// Branch with Link
......@@ -48,7 +50,7 @@ pub const Inst = struct {
4850 dbg_epilogue_begin,
4951 /// Pseudo-instruction: Update debug line
5052 dbg_line,
51 /// Psuedo-instruction: Load memory
53 /// Pseudo-instruction: Load memory
5254 ///
5355 /// Payload is `LoadMemory`
5456 load_memory,
......@@ -103,7 +105,7 @@ pub const Inst = struct {
103105 ///
104106 /// Used by e.g. nop
105107 nop: void,
106 /// Another instruction.
108 /// Another instruction
107109 ///
108110 /// Used by e.g. b
109111 inst: Index,
......@@ -123,6 +125,13 @@ pub const Inst = struct {
123125 ///
124126 /// Used by e.g. blr
125127 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 },
126135 /// A register, an unsigned 16-bit immediate, and an optional shift
127136 ///
128137 /// Used by e.g. movz
test/stage2/aarch64.zig+29
......@@ -68,4 +68,33 @@ pub fn addCases(ctx: *TestContext) !void {
6868 "",
6969 );
7070 }
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 }
71100}