| ... | ... | @@ -8,6 +8,7 @@ const link = @import("../../link.zig"); |
| 8 | 8 | const Module = @import("../../Module.zig"); |
| 9 | 9 | const ErrorMsg = Module.ErrorMsg; |
| 10 | 10 | const Liveness = @import("../../Liveness.zig"); |
| 11 | const log = std.log.scoped(.sparcv9_emit); |
| 11 | 12 | const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; |
| 12 | 13 | const DW = std.dwarf; |
| 13 | 14 | const leb128 = std.leb; |
| ... | ... | @@ -31,16 +32,42 @@ prev_di_column: u32, |
| 31 | 32 | /// Relative to the beginning of `code`. |
| 32 | 33 | prev_di_pc: usize, |
| 33 | 34 | |
| 35 | /// The branch type of every branch |
| 36 | branch_types: std.AutoHashMapUnmanaged(Mir.Inst.Index, BranchType) = .{}, |
| 37 | /// For every forward branch, maps the target instruction to a list of |
| 38 | /// branches which branch to this target instruction |
| 39 | branch_forward_origins: std.AutoHashMapUnmanaged(Mir.Inst.Index, std.ArrayListUnmanaged(Mir.Inst.Index)) = .{}, |
| 40 | /// For backward branches: stores the code offset of the target |
| 41 | /// instruction |
| 42 | /// |
| 43 | /// For forward branches: stores the code offset of the branch |
| 44 | /// instruction |
| 45 | code_offset_mapping: std.AutoHashMapUnmanaged(Mir.Inst.Index, usize) = .{}, |
| 46 | |
| 34 | 47 | const InnerError = error{ |
| 35 | 48 | OutOfMemory, |
| 36 | 49 | EmitFail, |
| 37 | 50 | }; |
| 38 | 51 | |
| 52 | const BranchType = enum { |
| 53 | bpcc, |
| 54 | fn default(tag: Mir.Inst.Tag) BranchType { |
| 55 | return switch (tag) { |
| 56 | .bpcc => .bpcc, |
| 57 | else => unreachable, |
| 58 | }; |
| 59 | } |
| 60 | }; |
| 61 | |
| 39 | 62 | pub fn emitMir( |
| 40 | 63 | emit: *Emit, |
| 41 | 64 | ) InnerError!void { |
| 42 | 65 | const mir_tags = emit.mir.instructions.items(.tag); |
| 43 | 66 | |
| 67 | // Convert absolute addresses into offsets and |
| 68 | // find smallest lowerings for branch instructions |
| 69 | try emit.lowerBranches(); |
| 70 | |
| 44 | 71 | // Emit machine code |
| 45 | 72 | for (mir_tags) |tag, index| { |
| 46 | 73 | const inst = @intCast(u32, index); |
| ... | ... | @@ -51,7 +78,7 @@ pub fn emitMir( |
| 51 | 78 | |
| 52 | 79 | .add => try emit.mirArithmetic3Op(inst), |
| 53 | 80 | |
| 54 | | .bpcc => @panic("TODO implement sparc64 bpcc"), |
| 81 | .bpcc => try emit.mirConditionalBranch(inst), |
| 55 | 82 | |
| 56 | 83 | .call => @panic("TODO implement sparc64 call"), |
| 57 | 84 | |
| ... | ... | @@ -89,6 +116,14 @@ pub fn emitMir( |
| 89 | 116 | } |
| 90 | 117 | |
| 91 | 118 | pub fn deinit(emit: *Emit) void { |
| 119 | var iter = emit.branch_forward_origins.valueIterator(); |
| 120 | while (iter.next()) |origin_list| { |
| 121 | origin_list.deinit(emit.bin_file.allocator); |
| 122 | } |
| 123 | |
| 124 | emit.branch_types.deinit(emit.bin_file.allocator); |
| 125 | emit.branch_forward_origins.deinit(emit.bin_file.allocator); |
| 126 | emit.code_offset_mapping.deinit(emit.bin_file.allocator); |
| 92 | 127 | emit.* = undefined; |
| 93 | 128 | } |
| 94 | 129 | |
| ... | ... | @@ -195,6 +230,22 @@ fn mirArithmetic3Op(emit: *Emit, inst: Mir.Inst.Index) !void { |
| 195 | 230 | } |
| 196 | 231 | } |
| 197 | 232 | |
| 233 | fn mirConditionalBranch(emit: *Emit, inst: Mir.Inst.Index) !void { |
| 234 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 235 | const branch_predict_int = emit.mir.instructions.items(.data)[inst].branch_predict_int; |
| 236 | |
| 237 | const offset = @intCast(i64, emit.code_offset_mapping.get(branch_predict_int.inst).?) - @intCast(i64, emit.code.items.len); |
| 238 | const branch_type = emit.branch_types.get(inst).?; |
| 239 | log.debug("mirConditionalBranchImmediate: {} offset={}", .{ inst, offset }); |
| 240 | |
| 241 | switch (branch_type) { |
| 242 | .bpcc => switch (tag) { |
| 243 | .bpcc => try emit.writeInstruction(Instruction.bpcc(branch_predict_int.cond, branch_predict_int.annul, branch_predict_int.pt, branch_predict_int.ccr, @intCast(i21, offset))), |
| 244 | else => unreachable, |
| 245 | }, |
| 246 | } |
| 247 | } |
| 248 | |
| 198 | 249 | fn mirNop(emit: *Emit) !void { |
| 199 | 250 | try emit.writeInstruction(Instruction.nop()); |
| 200 | 251 | } |
| ... | ... | @@ -235,6 +286,15 @@ fn mirTrap(emit: *Emit, inst: Mir.Inst.Index) !void { |
| 235 | 286 | |
| 236 | 287 | // Common helper functions |
| 237 | 288 | |
| 289 | fn branchTarget(emit: *Emit, inst: Mir.Inst.Index) Mir.Inst.Index { |
| 290 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 291 | |
| 292 | switch (tag) { |
| 293 | .bpcc => return emit.mir.instructions.items(.data)[inst].branch_predict_int.inst, |
| 294 | else => unreachable, |
| 295 | } |
| 296 | } |
| 297 | |
| 238 | 298 | fn dbgAdvancePCAndLine(emit: *Emit, line: u32, column: u32) !void { |
| 239 | 299 | const delta_line = @intCast(i32, line) - @intCast(i32, emit.prev_di_line); |
| 240 | 300 | const delta_pc: usize = emit.code.items.len - emit.prev_di_pc; |
| ... | ... | @@ -267,6 +327,155 @@ fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError { |
| 267 | 327 | return error.EmitFail; |
| 268 | 328 | } |
| 269 | 329 | |
| 330 | fn instructionSize(emit: *Emit, inst: Mir.Inst.Index) usize { |
| 331 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 332 | |
| 333 | switch (tag) { |
| 334 | .dbg_line, |
| 335 | .dbg_epilogue_begin, |
| 336 | .dbg_prologue_end, |
| 337 | => return 0, |
| 338 | // Currently Mir instructions always map to single machine instruction. |
| 339 | else => return 4, |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | fn isBranch(tag: Mir.Inst.Tag) bool { |
| 344 | return switch (tag) { |
| 345 | .bpcc => true, |
| 346 | else => false, |
| 347 | }; |
| 348 | } |
| 349 | |
| 350 | fn lowerBranches(emit: *Emit) !void { |
| 351 | const mir_tags = emit.mir.instructions.items(.tag); |
| 352 | const allocator = emit.bin_file.allocator; |
| 353 | |
| 354 | // First pass: Note down all branches and their target |
| 355 | // instructions, i.e. populate branch_types, |
| 356 | // branch_forward_origins, and code_offset_mapping |
| 357 | // |
| 358 | // TODO optimization opportunity: do this in codegen while |
| 359 | // generating MIR |
| 360 | for (mir_tags) |tag, index| { |
| 361 | const inst = @intCast(u32, index); |
| 362 | if (isBranch(tag)) { |
| 363 | const target_inst = emit.branchTarget(inst); |
| 364 | |
| 365 | // Remember this branch instruction |
| 366 | try emit.branch_types.put(allocator, inst, BranchType.default(tag)); |
| 367 | |
| 368 | // Forward branches require some extra stuff: We only |
| 369 | // know their offset once we arrive at the target |
| 370 | // instruction. Therefore, we need to be able to |
| 371 | // access the branch instruction when we visit the |
| 372 | // target instruction in order to manipulate its type |
| 373 | // etc. |
| 374 | if (target_inst > inst) { |
| 375 | // Remember the branch instruction index |
| 376 | try emit.code_offset_mapping.put(allocator, inst, 0); |
| 377 | |
| 378 | if (emit.branch_forward_origins.getPtr(target_inst)) |origin_list| { |
| 379 | try origin_list.append(allocator, inst); |
| 380 | } else { |
| 381 | var origin_list: std.ArrayListUnmanaged(Mir.Inst.Index) = .{}; |
| 382 | try origin_list.append(allocator, inst); |
| 383 | try emit.branch_forward_origins.put(allocator, target_inst, origin_list); |
| 384 | } |
| 385 | } |
| 386 | |
| 387 | // Remember the target instruction index so that we |
| 388 | // update the real code offset in all future passes |
| 389 | // |
| 390 | // putNoClobber may not be used as the put operation |
| 391 | // may clobber the entry when multiple branches branch |
| 392 | // to the same target instruction |
| 393 | try emit.code_offset_mapping.put(allocator, target_inst, 0); |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | // Further passes: Until all branches are lowered, interate |
| 398 | // through all instructions and calculate new offsets and |
| 399 | // potentially new branch types |
| 400 | var all_branches_lowered = false; |
| 401 | while (!all_branches_lowered) { |
| 402 | all_branches_lowered = true; |
| 403 | var current_code_offset: usize = 0; |
| 404 | |
| 405 | for (mir_tags) |tag, index| { |
| 406 | const inst = @intCast(u32, index); |
| 407 | |
| 408 | // If this instruction contained in the code offset |
| 409 | // mapping (when it is a target of a branch or if it is a |
| 410 | // forward branch), update the code offset |
| 411 | if (emit.code_offset_mapping.getPtr(inst)) |offset| { |
| 412 | offset.* = current_code_offset; |
| 413 | } |
| 414 | |
| 415 | // If this instruction is a backward branch, calculate the |
| 416 | // offset, which may potentially update the branch type |
| 417 | if (isBranch(tag)) { |
| 418 | const target_inst = emit.branchTarget(inst); |
| 419 | if (target_inst < inst) { |
| 420 | const target_offset = emit.code_offset_mapping.get(target_inst).?; |
| 421 | const offset = @intCast(i64, target_offset) - @intCast(i64, current_code_offset); |
| 422 | const branch_type = emit.branch_types.getPtr(inst).?; |
| 423 | const optimal_branch_type = try emit.optimalBranchType(tag, offset); |
| 424 | if (branch_type.* != optimal_branch_type) { |
| 425 | branch_type.* = optimal_branch_type; |
| 426 | all_branches_lowered = false; |
| 427 | } |
| 428 | |
| 429 | log.debug("lowerBranches: branch {} has offset {}", .{ inst, offset }); |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | // If this instruction is the target of one or more |
| 434 | // forward branches, calculate the offset, which may |
| 435 | // potentially update the branch type |
| 436 | if (emit.branch_forward_origins.get(inst)) |origin_list| { |
| 437 | for (origin_list.items) |forward_branch_inst| { |
| 438 | const branch_tag = emit.mir.instructions.items(.tag)[forward_branch_inst]; |
| 439 | const forward_branch_inst_offset = emit.code_offset_mapping.get(forward_branch_inst).?; |
| 440 | const offset = @intCast(i64, current_code_offset) - @intCast(i64, forward_branch_inst_offset); |
| 441 | const branch_type = emit.branch_types.getPtr(forward_branch_inst).?; |
| 442 | const optimal_branch_type = try emit.optimalBranchType(branch_tag, offset); |
| 443 | if (branch_type.* != optimal_branch_type) { |
| 444 | branch_type.* = optimal_branch_type; |
| 445 | all_branches_lowered = false; |
| 446 | } |
| 447 | |
| 448 | log.debug("lowerBranches: branch {} has offset {}", .{ forward_branch_inst, offset }); |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | // Increment code offset |
| 453 | current_code_offset += emit.instructionSize(inst); |
| 454 | } |
| 455 | } |
| 456 | } |
| 457 | |
| 458 | fn optimalBranchType(emit: *Emit, tag: Mir.Inst.Tag, offset: i64) !BranchType { |
| 459 | assert(offset & 0b11 == 0); |
| 460 | |
| 461 | switch (tag) { |
| 462 | .bpcc => { |
| 463 | if (std.math.cast(i21, offset)) |_| { |
| 464 | return BranchType.bpcc; |
| 465 | } else |_| { |
| 466 | // TODO use the following strategy to implement long branches: |
| 467 | // - Negate the conditional and target of the original BPcc; |
| 468 | // - In the space immediately after the branch, load |
| 469 | // the address of the original target, preferrably in |
| 470 | // a PC-relative way, into %o7; and |
| 471 | // - jmpl %o7 + %g0, %g0 |
| 472 | return emit.fail("TODO support BPcc branches larger than +-1 MiB", .{}); |
| 473 | } |
| 474 | }, |
| 475 | else => unreachable, |
| 476 | } |
| 477 | } |
| 478 | |
| 270 | 479 | fn writeInstruction(emit: *Emit, instruction: Instruction) !void { |
| 271 | 480 | // SPARCv9 instructions are always arranged in BE regardless of the |
| 272 | 481 | // endianness mode the CPU is running in (Section 3.1 of the ISA specification). |