From 002fbb0af043d90b0ab7d2f2804effc6fa2d690c Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Mon, 1 Nov 2021 15:48:01 +0100 Subject: [PATCH] stage2 AArch64: implement unconditional branches --- src/arch/aarch64/CodeGen.zig | 2 + src/arch/aarch64/Emit.zig | 190 +++++++++++++++++++++++++++++++++-- 2 files changed, 186 insertions(+), 6 deletions(-) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 94f389c4a9..8fe03c9713 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -315,6 +315,8 @@ pub fn generate( .prev_di_line = module_fn.lbrace_line, .prev_di_column = module_fn.lbrace_column, }; + defer emit.deinit(); + emit.emitMir() catch |err| switch (err) { error.EmitFail => return FnResult{ .fail = emit.err_msg.? }, else => |e| return e, diff --git a/src/arch/aarch64/Emit.zig b/src/arch/aarch64/Emit.zig index f43210dc3c..665d529245 100644 --- a/src/arch/aarch64/Emit.zig +++ b/src/arch/aarch64/Emit.zig @@ -29,16 +29,38 @@ prev_di_column: u32, /// Relative to the beginning of `code`. prev_di_pc: usize, +/// The branch type of every branch +branch_types: std.AutoHashMapUnmanaged(Mir.Inst.Index, BranchType) = .{}, +/// For every forward branch, maps the target instruction to a list of +/// branches which branch to this target instruction +branch_forward_origins: std.AutoHashMapUnmanaged(Mir.Inst.Index, std.ArrayListUnmanaged(Mir.Inst.Index)) = .{}, +/// For backward branches: stores the code offset of the target +/// instruction +/// +/// For forward branches: stores the code offset of the branch +/// instruction +code_offset_mapping: std.AutoHashMapUnmanaged(Mir.Inst.Index, usize) = .{}, + const InnerError = error{ OutOfMemory, EmitFail, }; +const BranchType = enum { + unconditional_branch_immediate, + + const default = BranchType.unconditional_branch_immediate; +}; + pub fn emitMir( emit: *Emit, ) !void { const mir_tags = emit.mir.instructions.items(.tag); + // Find smallest lowerings for branch instructions + try emit.lowerBranches(); + + // Emit machine code for (mir_tags) |tag, index| { const inst = @intCast(u32, index); switch (tag) { @@ -84,6 +106,159 @@ pub fn emitMir( } } +pub fn deinit(emit: *Emit) void { + emit.branch_types.deinit(emit.bin_file.allocator); + emit.branch_forward_origins.deinit(emit.bin_file.allocator); + emit.code_offset_mapping.deinit(emit.bin_file.allocator); + emit.* = undefined; +} + +fn optimalBranchType(emit: *Emit, offset: i64) !BranchType { + assert(offset & 0b11 == 0); + + // TODO handle conditional branches + if (std.math.cast(i26, offset >> 2)) |_| { + return BranchType.unconditional_branch_immediate; + } else |_| { + return emit.fail("TODO support branches larger than +-128 MiB", .{}); + } +} + +fn instructionSize(emit: *Emit, inst: Mir.Inst.Index) usize { + const tag = emit.mir.instructions.items(.tag)[inst]; + switch (tag) { + .b, .bl => switch (emit.branch_types.get(inst).?) { + .unconditional_branch_immediate => return 4, + }, + .load_memory => { + if (emit.bin_file.options.pie) { + // adrp, ldr + return 2 * 4; + } else { + const payload = emit.mir.instructions.items(.data)[inst].payload; + const load_memory = emit.mir.extraData(Mir.LoadMemory, payload).data; + const addr = load_memory.addr; + + // movz, [movk, ...], ldr + if (addr <= math.maxInt(u16)) return 2 * 4; + if (addr <= math.maxInt(u32)) return 3 * 4; + if (addr <= math.maxInt(u48)) return 4 * 4; + return 5 * 4; + } + }, + else => return 4, + } +} + +fn lowerBranches(emit: *Emit) !void { + const mir_tags = emit.mir.instructions.items(.tag); + const allocator = emit.bin_file.allocator; + + // First pass: Note down all branches and their target + // instructions, i.e. populate branch_types, + // branch_forward_origins, and code_offset_mapping + // + // TODO optimization opportunity: do this in codegen while + // generating MIR + for (mir_tags) |tag, index| { + const inst = @intCast(u32, index); + switch (tag) { + .b, .bl => { + const target_inst = emit.mir.instructions.items(.data)[inst].inst; + + // Remember this branch instruction + try emit.branch_types.put(allocator, inst, BranchType.default); + + // Forward branches require some extra stuff: We only + // know their offset once we arrive at the target + // instruction. Therefore, we need to be able to + // access the branch instruction when we visit the + // target instruction in order to manipulate its type + // etc. + if (target_inst > inst) { + // Remember the branch instruction index + try emit.code_offset_mapping.put(allocator, inst, 0); + + if (emit.branch_forward_origins.getPtr(target_inst)) |origin_list| { + try origin_list.append(allocator, inst); + } else { + var origin_list: std.ArrayListUnmanaged(Mir.Inst.Index) = .{}; + try origin_list.append(allocator, inst); + try emit.branch_forward_origins.put(allocator, target_inst, origin_list); + } + } + + // Remember the target instruction index so that we + // update the real code offset in all future passes + // + // putNoClobber may not be used as the put operation + // may clobber the entry when multiple branches branch + // to the same target instruction + try emit.code_offset_mapping.put(allocator, target_inst, 0); + }, + else => {}, // not a branch + } + } + + // Further passes: Until all branches are lowered, interate + // through all instructions and calculate new offsets and + // potentially new branch types + var all_branches_lowered = false; + while (!all_branches_lowered) { + all_branches_lowered = true; + var current_code_offset: usize = 0; + + for (mir_tags) |tag, index| { + const inst = @intCast(u32, index); + + // If this instruction contained in the code offset + // mapping (when it is a target of a branch or if it is a + // forward branch), update the code offset + if (emit.code_offset_mapping.getPtr(inst)) |offset| { + offset.* = current_code_offset; + } + + // If this instruction is a backward branch, calculate the + // offset, which may potentially update the branch type + switch (tag) { + .b, .bl => { + const target_inst = emit.mir.instructions.items(.data)[inst].inst; + if (target_inst < inst) { + const target_offset = emit.code_offset_mapping.get(target_inst).?; + const offset = @intCast(i64, target_offset) - @intCast(i64, current_code_offset + 8); + const branch_type = emit.branch_types.getPtr(inst).?; + const optimal_branch_type = try emit.optimalBranchType(offset); + if (branch_type.* != optimal_branch_type) { + branch_type.* = optimal_branch_type; + all_branches_lowered = false; + } + } + }, + else => {}, + } + + // If this instruction is the target of one or more + // forward branches, calculate the offset, which may + // potentially update the branch type + if (emit.branch_forward_origins.get(inst)) |origin_list| { + for (origin_list.items) |forward_branch_inst| { + const forward_branch_inst_offset = emit.code_offset_mapping.get(forward_branch_inst).?; + const offset = @intCast(i64, forward_branch_inst_offset) - @intCast(i64, current_code_offset + 8); + const branch_type = emit.branch_types.getPtr(forward_branch_inst).?; + const optimal_branch_type = try emit.optimalBranchType(offset); + if (branch_type.* != optimal_branch_type) { + branch_type.* = optimal_branch_type; + all_branches_lowered = false; + } + } + } + + // Increment code offset + current_code_offset += emit.instructionSize(inst); + } + } +} + fn writeInstruction(emit: *Emit, instruction: Instruction) !void { const endian = emit.target.cpu.arch.endian(); std.mem.writeInt(u32, try emit.code.addManyAsArray(4), instruction.toU32(), endian); @@ -185,13 +360,16 @@ fn mirAddSubtractImmediate(emit: *Emit, inst: Mir.Inst.Index) !void { fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void { const tag = emit.mir.instructions.items(.tag)[inst]; const target_inst = emit.mir.instructions.items(.data)[inst].inst; - _ = tag; - _ = target_inst; - switch (tag) { - .b => return emit.fail("Implement mirBranch", .{}), - .bl => return emit.fail("Implement mirBranch", .{}), - else => unreachable, + const offset = @intCast(i64, emit.code_offset_mapping.get(target_inst).?) - @intCast(i64, emit.code.items.len + 8); + const branch_type = emit.branch_types.get(inst).?; + + switch (branch_type) { + .unconditional_branch_immediate => switch (tag) { + .b => try emit.writeInstruction(Instruction.b(@intCast(i28, offset))), + .bl => try emit.writeInstruction(Instruction.bl(@intCast(i28, offset))), + else => unreachable, + }, } }