| ... | ... | @@ -29,16 +29,38 @@ prev_di_column: u32, |
| 29 | 29 | /// Relative to the beginning of `code`. |
| 30 | 30 | prev_di_pc: usize, |
| 31 | 31 | |
| 32 | /// The branch type of every branch |
| 33 | branch_types: std.AutoHashMapUnmanaged(Mir.Inst.Index, BranchType) = .{}, |
| 34 | /// For every forward branch, maps the target instruction to a list of |
| 35 | /// branches which branch to this target instruction |
| 36 | branch_forward_origins: std.AutoHashMapUnmanaged(Mir.Inst.Index, std.ArrayListUnmanaged(Mir.Inst.Index)) = .{}, |
| 37 | /// For backward branches: stores the code offset of the target |
| 38 | /// instruction |
| 39 | /// |
| 40 | /// For forward branches: stores the code offset of the branch |
| 41 | /// instruction |
| 42 | code_offset_mapping: std.AutoHashMapUnmanaged(Mir.Inst.Index, usize) = .{}, |
| 43 | |
| 32 | 44 | const InnerError = error{ |
| 33 | 45 | OutOfMemory, |
| 34 | 46 | EmitFail, |
| 35 | 47 | }; |
| 36 | 48 | |
| 49 | const BranchType = enum { |
| 50 | unconditional_branch_immediate, |
| 51 | |
| 52 | const default = BranchType.unconditional_branch_immediate; |
| 53 | }; |
| 54 | |
| 37 | 55 | pub fn emitMir( |
| 38 | 56 | emit: *Emit, |
| 39 | 57 | ) !void { |
| 40 | 58 | const mir_tags = emit.mir.instructions.items(.tag); |
| 41 | 59 | |
| 60 | // Find smallest lowerings for branch instructions |
| 61 | try emit.lowerBranches(); |
| 62 | |
| 63 | // Emit machine code |
| 42 | 64 | for (mir_tags) |tag, index| { |
| 43 | 65 | const inst = @intCast(u32, index); |
| 44 | 66 | switch (tag) { |
| ... | ... | @@ -84,6 +106,159 @@ pub fn emitMir( |
| 84 | 106 | } |
| 85 | 107 | } |
| 86 | 108 | |
| 109 | pub fn deinit(emit: *Emit) void { |
| 110 | emit.branch_types.deinit(emit.bin_file.allocator); |
| 111 | emit.branch_forward_origins.deinit(emit.bin_file.allocator); |
| 112 | emit.code_offset_mapping.deinit(emit.bin_file.allocator); |
| 113 | emit.* = undefined; |
| 114 | } |
| 115 | |
| 116 | fn optimalBranchType(emit: *Emit, offset: i64) !BranchType { |
| 117 | assert(offset & 0b11 == 0); |
| 118 | |
| 119 | // TODO handle conditional branches |
| 120 | if (std.math.cast(i26, offset >> 2)) |_| { |
| 121 | return BranchType.unconditional_branch_immediate; |
| 122 | } else |_| { |
| 123 | return emit.fail("TODO support branches larger than +-128 MiB", .{}); |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | fn instructionSize(emit: *Emit, inst: Mir.Inst.Index) usize { |
| 128 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 129 | switch (tag) { |
| 130 | .b, .bl => switch (emit.branch_types.get(inst).?) { |
| 131 | .unconditional_branch_immediate => return 4, |
| 132 | }, |
| 133 | .load_memory => { |
| 134 | if (emit.bin_file.options.pie) { |
| 135 | // adrp, ldr |
| 136 | return 2 * 4; |
| 137 | } else { |
| 138 | const payload = emit.mir.instructions.items(.data)[inst].payload; |
| 139 | const load_memory = emit.mir.extraData(Mir.LoadMemory, payload).data; |
| 140 | const addr = load_memory.addr; |
| 141 | |
| 142 | // movz, [movk, ...], ldr |
| 143 | if (addr <= math.maxInt(u16)) return 2 * 4; |
| 144 | if (addr <= math.maxInt(u32)) return 3 * 4; |
| 145 | if (addr <= math.maxInt(u48)) return 4 * 4; |
| 146 | return 5 * 4; |
| 147 | } |
| 148 | }, |
| 149 | else => return 4, |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | fn lowerBranches(emit: *Emit) !void { |
| 154 | const mir_tags = emit.mir.instructions.items(.tag); |
| 155 | const allocator = emit.bin_file.allocator; |
| 156 | |
| 157 | // First pass: Note down all branches and their target |
| 158 | // instructions, i.e. populate branch_types, |
| 159 | // branch_forward_origins, and code_offset_mapping |
| 160 | // |
| 161 | // TODO optimization opportunity: do this in codegen while |
| 162 | // generating MIR |
| 163 | for (mir_tags) |tag, index| { |
| 164 | const inst = @intCast(u32, index); |
| 165 | switch (tag) { |
| 166 | .b, .bl => { |
| 167 | const target_inst = emit.mir.instructions.items(.data)[inst].inst; |
| 168 | |
| 169 | // Remember this branch instruction |
| 170 | try emit.branch_types.put(allocator, inst, BranchType.default); |
| 171 | |
| 172 | // Forward branches require some extra stuff: We only |
| 173 | // know their offset once we arrive at the target |
| 174 | // instruction. Therefore, we need to be able to |
| 175 | // access the branch instruction when we visit the |
| 176 | // target instruction in order to manipulate its type |
| 177 | // etc. |
| 178 | if (target_inst > inst) { |
| 179 | // Remember the branch instruction index |
| 180 | try emit.code_offset_mapping.put(allocator, inst, 0); |
| 181 | |
| 182 | if (emit.branch_forward_origins.getPtr(target_inst)) |origin_list| { |
| 183 | try origin_list.append(allocator, inst); |
| 184 | } else { |
| 185 | var origin_list: std.ArrayListUnmanaged(Mir.Inst.Index) = .{}; |
| 186 | try origin_list.append(allocator, inst); |
| 187 | try emit.branch_forward_origins.put(allocator, target_inst, origin_list); |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | // Remember the target instruction index so that we |
| 192 | // update the real code offset in all future passes |
| 193 | // |
| 194 | // putNoClobber may not be used as the put operation |
| 195 | // may clobber the entry when multiple branches branch |
| 196 | // to the same target instruction |
| 197 | try emit.code_offset_mapping.put(allocator, target_inst, 0); |
| 198 | }, |
| 199 | else => {}, // not a branch |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | // Further passes: Until all branches are lowered, interate |
| 204 | // through all instructions and calculate new offsets and |
| 205 | // potentially new branch types |
| 206 | var all_branches_lowered = false; |
| 207 | while (!all_branches_lowered) { |
| 208 | all_branches_lowered = true; |
| 209 | var current_code_offset: usize = 0; |
| 210 | |
| 211 | for (mir_tags) |tag, index| { |
| 212 | const inst = @intCast(u32, index); |
| 213 | |
| 214 | // If this instruction contained in the code offset |
| 215 | // mapping (when it is a target of a branch or if it is a |
| 216 | // forward branch), update the code offset |
| 217 | if (emit.code_offset_mapping.getPtr(inst)) |offset| { |
| 218 | offset.* = current_code_offset; |
| 219 | } |
| 220 | |
| 221 | // If this instruction is a backward branch, calculate the |
| 222 | // offset, which may potentially update the branch type |
| 223 | switch (tag) { |
| 224 | .b, .bl => { |
| 225 | const target_inst = emit.mir.instructions.items(.data)[inst].inst; |
| 226 | if (target_inst < inst) { |
| 227 | const target_offset = emit.code_offset_mapping.get(target_inst).?; |
| 228 | const offset = @intCast(i64, target_offset) - @intCast(i64, current_code_offset + 8); |
| 229 | const branch_type = emit.branch_types.getPtr(inst).?; |
| 230 | const optimal_branch_type = try emit.optimalBranchType(offset); |
| 231 | if (branch_type.* != optimal_branch_type) { |
| 232 | branch_type.* = optimal_branch_type; |
| 233 | all_branches_lowered = false; |
| 234 | } |
| 235 | } |
| 236 | }, |
| 237 | else => {}, |
| 238 | } |
| 239 | |
| 240 | // If this instruction is the target of one or more |
| 241 | // forward branches, calculate the offset, which may |
| 242 | // potentially update the branch type |
| 243 | if (emit.branch_forward_origins.get(inst)) |origin_list| { |
| 244 | for (origin_list.items) |forward_branch_inst| { |
| 245 | const forward_branch_inst_offset = emit.code_offset_mapping.get(forward_branch_inst).?; |
| 246 | const offset = @intCast(i64, forward_branch_inst_offset) - @intCast(i64, current_code_offset + 8); |
| 247 | const branch_type = emit.branch_types.getPtr(forward_branch_inst).?; |
| 248 | const optimal_branch_type = try emit.optimalBranchType(offset); |
| 249 | if (branch_type.* != optimal_branch_type) { |
| 250 | branch_type.* = optimal_branch_type; |
| 251 | all_branches_lowered = false; |
| 252 | } |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | // Increment code offset |
| 257 | current_code_offset += emit.instructionSize(inst); |
| 258 | } |
| 259 | } |
| 260 | } |
| 261 | |
| 87 | 262 | fn writeInstruction(emit: *Emit, instruction: Instruction) !void { |
| 88 | 263 | const endian = emit.target.cpu.arch.endian(); |
| 89 | 264 | 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 { |
| 185 | 360 | fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void { |
| 186 | 361 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 187 | 362 | const target_inst = emit.mir.instructions.items(.data)[inst].inst; |
| 188 | | _ = tag; |
| 189 | | _ = target_inst; |
| 190 | 363 | |
| 191 | | switch (tag) { |
| 192 | | .b => return emit.fail("Implement mirBranch", .{}), |
| 193 | | .bl => return emit.fail("Implement mirBranch", .{}), |
| 194 | | else => unreachable, |
| 364 | const offset = @intCast(i64, emit.code_offset_mapping.get(target_inst).?) - @intCast(i64, emit.code.items.len + 8); |
| 365 | const branch_type = emit.branch_types.get(inst).?; |
| 366 | |
| 367 | switch (branch_type) { |
| 368 | .unconditional_branch_immediate => switch (tag) { |
| 369 | .b => try emit.writeInstruction(Instruction.b(@intCast(i28, offset))), |
| 370 | .bl => try emit.writeInstruction(Instruction.bl(@intCast(i28, offset))), |
| 371 | else => unreachable, |
| 372 | }, |
| 195 | 373 | } |
| 196 | 374 | } |
| 197 | 375 | |