| ... | @@ -29,16 +29,38 @@ prev_di_column: u32, | ... | @@ -29,16 +29,38 @@ prev_di_column: u32, |
| 29 | /// Relative to the beginning of `code`. | 29 | /// Relative to the beginning of `code`. |
| 30 | prev_di_pc: usize, | 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 | const InnerError = error{ | 44 | const InnerError = error{ |
| 33 | OutOfMemory, | 45 | OutOfMemory, |
| 34 | EmitFail, | 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 | pub fn emitMir( | 55 | pub fn emitMir( |
| 38 | emit: *Emit, | 56 | emit: *Emit, |
| 39 | ) !void { | 57 | ) !void { |
| 40 | const mir_tags = emit.mir.instructions.items(.tag); | 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 | for (mir_tags) |tag, index| { | 64 | for (mir_tags) |tag, index| { |
| 43 | const inst = @intCast(u32, index); | 65 | const inst = @intCast(u32, index); |
| 44 | switch (tag) { | 66 | switch (tag) { |
| ... | @@ -84,6 +106,159 @@ pub fn emitMir( | ... | @@ -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 | fn writeInstruction(emit: *Emit, instruction: Instruction) !void { | 262 | fn writeInstruction(emit: *Emit, instruction: Instruction) !void { |
| 88 | const endian = emit.target.cpu.arch.endian(); | 263 | const endian = emit.target.cpu.arch.endian(); |
| 89 | std.mem.writeInt(u32, try emit.code.addManyAsArray(4), instruction.toU32(), endian); | 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,13 +360,16 @@ fn mirAddSubtractImmediate(emit: *Emit, inst: Mir.Inst.Index) !void { |
| 185 | fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void { | 360 | fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void { |
| 186 | const tag = emit.mir.instructions.items(.tag)[inst]; | 361 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 187 | const target_inst = emit.mir.instructions.items(.data)[inst].inst; | 362 | const target_inst = emit.mir.instructions.items(.data)[inst].inst; |
| 188 | _ = tag; | | |
| 189 | _ = target_inst; | | |
| 190 | | 363 | |
| 191 | switch (tag) { | 364 | const offset = @intCast(i64, emit.code_offset_mapping.get(target_inst).?) - @intCast(i64, emit.code.items.len + 8); |
| 192 | .b => return emit.fail("Implement mirBranch", .{}), | 365 | const branch_type = emit.branch_types.get(inst).?; |
| 193 | .bl => return emit.fail("Implement mirBranch", .{}), | 366 | |
| 194 | else => unreachable, | 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 | |