| ... | @@ -28,6 +28,7 @@ const build_options = @import("build_options"); | ... | @@ -28,6 +28,7 @@ const build_options = @import("build_options"); |
| 28 | | 28 | |
| 29 | const bits = @import("bits.zig"); | 29 | const bits = @import("bits.zig"); |
| 30 | const abi = @import("abi.zig"); | 30 | const abi = @import("abi.zig"); |
| | 31 | const Instruction = bits.Instruction; |
| 31 | const Register = bits.Register; | 32 | const Register = bits.Register; |
| 32 | | 33 | |
| 33 | const Self = @This(); | 34 | const Self = @This(); |
| ... | @@ -90,6 +91,9 @@ register_manager: RegisterManager = .{}, | ... | @@ -90,6 +91,9 @@ register_manager: RegisterManager = .{}, |
| 90 | /// Maps offset to what is stored there. | 91 | /// Maps offset to what is stored there. |
| 91 | stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{}, | 92 | stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{}, |
| 92 | | 93 | |
| | 94 | /// Tracks the current instruction allocated to the compare flags |
| | 95 | compare_flags_inst: ?Air.Inst.Index = null, |
| | 96 | |
| 93 | /// Offset from the stack base, representing the end of the stack frame. | 97 | /// Offset from the stack base, representing the end of the stack frame. |
| 94 | max_end_stack: u32 = 0, | 98 | max_end_stack: u32 = 0, |
| 95 | /// Represents the current end stack offset. If there is no existing slot | 99 | /// Represents the current end stack offset. If there is no existing slot |
| ... | @@ -373,18 +377,31 @@ fn gen(self: *Self) !void { | ... | @@ -373,18 +377,31 @@ fn gen(self: *Self) !void { |
| 373 | | 377 | |
| 374 | // exitlude jumps | 378 | // exitlude jumps |
| 375 | if (self.exitlude_jump_relocs.items.len > 0 and | 379 | if (self.exitlude_jump_relocs.items.len > 0 and |
| 376 | self.exitlude_jump_relocs.items[self.exitlude_jump_relocs.items.len - 1] == self.mir_instructions.len - 2) | 380 | self.exitlude_jump_relocs.items[self.exitlude_jump_relocs.items.len - 1] == self.mir_instructions.len - 3) |
| 377 | { | 381 | { |
| 378 | // If the last Mir instruction (apart from the | 382 | // If the last Mir instruction (apart from the |
| 379 | // dbg_epilogue_begin) is the last exitlude jump | 383 | // dbg_epilogue_begin) is the last exitlude jump |
| 380 | // relocation (which would just jump one instruction | 384 | // relocation (which would just jump two instructions |
| 381 | // further), it can be safely removed | 385 | // further), it can be safely removed |
| 382 | self.mir_instructions.orderedRemove(self.exitlude_jump_relocs.pop()); | 386 | const index = self.exitlude_jump_relocs.pop(); |
| | 387 | |
| | 388 | // First, remove the delay slot, then remove |
| | 389 | // the branch instruction itself. |
| | 390 | self.mir_instructions.orderedRemove(index + 1); |
| | 391 | self.mir_instructions.orderedRemove(index); |
| 383 | } | 392 | } |
| 384 | | 393 | |
| 385 | for (self.exitlude_jump_relocs.items) |jmp_reloc| { | 394 | for (self.exitlude_jump_relocs.items) |jmp_reloc| { |
| 386 | _ = jmp_reloc; | 395 | self.mir_instructions.set(jmp_reloc, .{ |
| 387 | return self.fail("TODO add branches in sparc64", .{}); | 396 | .tag = .bpcc, |
| | 397 | .data = .{ |
| | 398 | .branch_predict_int = .{ |
| | 399 | .ccr = .xcc, |
| | 400 | .cond = .al, |
| | 401 | .inst = @intCast(u32, self.mir_instructions.len), |
| | 402 | }, |
| | 403 | }, |
| | 404 | }); |
| 388 | } | 405 | } |
| 389 | | 406 | |
| 390 | // Backpatch stack offset | 407 | // Backpatch stack offset |
| ... | @@ -531,7 +548,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -531,7 +548,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 531 | .ret_addr => @panic("TODO try self.airRetAddr(inst)"), | 548 | .ret_addr => @panic("TODO try self.airRetAddr(inst)"), |
| 532 | .frame_addr => @panic("TODO try self.airFrameAddress(inst)"), | 549 | .frame_addr => @panic("TODO try self.airFrameAddress(inst)"), |
| 533 | .fence => @panic("TODO try self.airFence()"), | 550 | .fence => @panic("TODO try self.airFence()"), |
| 534 | .cond_br => @panic("TODO try self.airCondBr(inst)"), | 551 | .cond_br => try self.airCondBr(inst), |
| 535 | .dbg_stmt => try self.airDbgStmt(inst), | 552 | .dbg_stmt => try self.airDbgStmt(inst), |
| 536 | .fptrunc => @panic("TODO try self.airFptrunc(inst)"), | 553 | .fptrunc => @panic("TODO try self.airFptrunc(inst)"), |
| 537 | .fpext => @panic("TODO try self.airFpext(inst)"), | 554 | .fpext => @panic("TODO try self.airFpext(inst)"), |
| ... | @@ -968,6 +985,188 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. | ... | @@ -968,6 +985,188 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. |
| 968 | @panic("TODO handle return value with BigTomb"); | 985 | @panic("TODO handle return value with BigTomb"); |
| 969 | } | 986 | } |
| 970 | | 987 | |
| | 988 | fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| | 989 | const pl_op = self.air.instructions.items(.data)[inst].pl_op; |
| | 990 | const cond = try self.resolveInst(pl_op.operand); |
| | 991 | const extra = self.air.extraData(Air.CondBr, pl_op.payload); |
| | 992 | const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len]; |
| | 993 | const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; |
| | 994 | const liveness_condbr = self.liveness.getCondBr(inst); |
| | 995 | |
| | 996 | // Here we either emit a BPcc for branching on CCR content, |
| | 997 | // or emit a BPr to branch on register content. |
| | 998 | const reloc: Mir.Inst.Index = switch (cond) { |
| | 999 | .compare_flags_signed, |
| | 1000 | .compare_flags_unsigned, |
| | 1001 | => try self.addInst(.{ |
| | 1002 | .tag = .bpcc, |
| | 1003 | .data = .{ |
| | 1004 | .branch_predict_int = .{ |
| | 1005 | .ccr = .xcc, |
| | 1006 | .cond = switch (cond) { |
| | 1007 | .compare_flags_signed => |cmp_op| blk: { |
| | 1008 | // Here we map to the opposite condition because the jump is to the false branch. |
| | 1009 | const condition = Instruction.ICondition.fromCompareOperatorSigned(cmp_op); |
| | 1010 | break :blk condition.negate(); |
| | 1011 | }, |
| | 1012 | .compare_flags_unsigned => |cmp_op| blk: { |
| | 1013 | // Here we map to the opposite condition because the jump is to the false branch. |
| | 1014 | const condition = Instruction.ICondition.fromCompareOperatorUnsigned(cmp_op); |
| | 1015 | break :blk condition.negate(); |
| | 1016 | }, |
| | 1017 | else => unreachable, |
| | 1018 | }, |
| | 1019 | .inst = undefined, // Will be filled by performReloc |
| | 1020 | }, |
| | 1021 | }, |
| | 1022 | }), |
| | 1023 | else => return self.fail("TODO branch on register content (BPr)", .{}), |
| | 1024 | }; |
| | 1025 | |
| | 1026 | // Regardless of the branch type that's emitted, we need to reserve |
| | 1027 | // a space for the delay slot. |
| | 1028 | // TODO Find a way to fill this delay slot |
| | 1029 | _ = try self.addInst(.{ |
| | 1030 | .tag = .nop, |
| | 1031 | .data = .{ .nop = {} }, |
| | 1032 | }); |
| | 1033 | |
| | 1034 | // If the condition dies here in this condbr instruction, process |
| | 1035 | // that death now instead of later as this has an effect on |
| | 1036 | // whether it needs to be spilled in the branches |
| | 1037 | if (self.liveness.operandDies(inst, 0)) { |
| | 1038 | const op_int = @enumToInt(pl_op.operand); |
| | 1039 | if (op_int >= Air.Inst.Ref.typed_value_map.len) { |
| | 1040 | const op_index = @intCast(Air.Inst.Index, op_int - Air.Inst.Ref.typed_value_map.len); |
| | 1041 | self.processDeath(op_index); |
| | 1042 | } |
| | 1043 | } |
| | 1044 | |
| | 1045 | // Capture the state of register and stack allocation state so that we can revert to it. |
| | 1046 | const parent_next_stack_offset = self.next_stack_offset; |
| | 1047 | const parent_free_registers = self.register_manager.free_registers; |
| | 1048 | var parent_stack = try self.stack.clone(self.gpa); |
| | 1049 | defer parent_stack.deinit(self.gpa); |
| | 1050 | const parent_registers = self.register_manager.registers; |
| | 1051 | const parent_compare_flags_inst = self.compare_flags_inst; |
| | 1052 | |
| | 1053 | try self.branch_stack.append(.{}); |
| | 1054 | errdefer { |
| | 1055 | _ = self.branch_stack.pop(); |
| | 1056 | } |
| | 1057 | |
| | 1058 | try self.ensureProcessDeathCapacity(liveness_condbr.then_deaths.len); |
| | 1059 | for (liveness_condbr.then_deaths) |operand| { |
| | 1060 | self.processDeath(operand); |
| | 1061 | } |
| | 1062 | try self.genBody(then_body); |
| | 1063 | |
| | 1064 | // Revert to the previous register and stack allocation state. |
| | 1065 | |
| | 1066 | var saved_then_branch = self.branch_stack.pop(); |
| | 1067 | defer saved_then_branch.deinit(self.gpa); |
| | 1068 | |
| | 1069 | self.register_manager.registers = parent_registers; |
| | 1070 | self.compare_flags_inst = parent_compare_flags_inst; |
| | 1071 | |
| | 1072 | self.stack.deinit(self.gpa); |
| | 1073 | self.stack = parent_stack; |
| | 1074 | parent_stack = .{}; |
| | 1075 | |
| | 1076 | self.next_stack_offset = parent_next_stack_offset; |
| | 1077 | self.register_manager.free_registers = parent_free_registers; |
| | 1078 | |
| | 1079 | try self.performReloc(reloc); |
| | 1080 | const else_branch = self.branch_stack.addOneAssumeCapacity(); |
| | 1081 | else_branch.* = .{}; |
| | 1082 | |
| | 1083 | try self.ensureProcessDeathCapacity(liveness_condbr.else_deaths.len); |
| | 1084 | for (liveness_condbr.else_deaths) |operand| { |
| | 1085 | self.processDeath(operand); |
| | 1086 | } |
| | 1087 | try self.genBody(else_body); |
| | 1088 | |
| | 1089 | // At this point, each branch will possibly have conflicting values for where |
| | 1090 | // each instruction is stored. They agree, however, on which instructions are alive/dead. |
| | 1091 | // We use the first ("then") branch as canonical, and here emit |
| | 1092 | // instructions into the second ("else") branch to make it conform. |
| | 1093 | // We continue respect the data structure semantic guarantees of the else_branch so |
| | 1094 | // that we can use all the code emitting abstractions. This is why at the bottom we |
| | 1095 | // assert that parent_branch.free_registers equals the saved_then_branch.free_registers |
| | 1096 | // rather than assigning it. |
| | 1097 | const parent_branch = &self.branch_stack.items[self.branch_stack.items.len - 2]; |
| | 1098 | try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, else_branch.inst_table.count()); |
| | 1099 | |
| | 1100 | const else_slice = else_branch.inst_table.entries.slice(); |
| | 1101 | const else_keys = else_slice.items(.key); |
| | 1102 | const else_values = else_slice.items(.value); |
| | 1103 | for (else_keys) |else_key, else_idx| { |
| | 1104 | const else_value = else_values[else_idx]; |
| | 1105 | const canon_mcv = if (saved_then_branch.inst_table.fetchSwapRemove(else_key)) |then_entry| blk: { |
| | 1106 | // The instruction's MCValue is overridden in both branches. |
| | 1107 | parent_branch.inst_table.putAssumeCapacity(else_key, then_entry.value); |
| | 1108 | if (else_value == .dead) { |
| | 1109 | assert(then_entry.value == .dead); |
| | 1110 | continue; |
| | 1111 | } |
| | 1112 | break :blk then_entry.value; |
| | 1113 | } else blk: { |
| | 1114 | if (else_value == .dead) |
| | 1115 | continue; |
| | 1116 | // The instruction is only overridden in the else branch. |
| | 1117 | var i: usize = self.branch_stack.items.len - 2; |
| | 1118 | while (true) { |
| | 1119 | i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead? |
| | 1120 | if (self.branch_stack.items[i].inst_table.get(else_key)) |mcv| { |
| | 1121 | assert(mcv != .dead); |
| | 1122 | break :blk mcv; |
| | 1123 | } |
| | 1124 | } |
| | 1125 | }; |
| | 1126 | log.debug("consolidating else_entry {d} {}=>{}", .{ else_key, else_value, canon_mcv }); |
| | 1127 | // TODO make sure the destination stack offset / register does not already have something |
| | 1128 | // going on there. |
| | 1129 | try self.setRegOrMem(self.air.typeOfIndex(else_key), canon_mcv, else_value); |
| | 1130 | // TODO track the new register / stack allocation |
| | 1131 | } |
| | 1132 | try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, saved_then_branch.inst_table.count()); |
| | 1133 | const then_slice = saved_then_branch.inst_table.entries.slice(); |
| | 1134 | const then_keys = then_slice.items(.key); |
| | 1135 | const then_values = then_slice.items(.value); |
| | 1136 | for (then_keys) |then_key, then_idx| { |
| | 1137 | const then_value = then_values[then_idx]; |
| | 1138 | // We already deleted the items from this table that matched the else_branch. |
| | 1139 | // So these are all instructions that are only overridden in the then branch. |
| | 1140 | parent_branch.inst_table.putAssumeCapacity(then_key, then_value); |
| | 1141 | if (then_value == .dead) |
| | 1142 | continue; |
| | 1143 | const parent_mcv = blk: { |
| | 1144 | var i: usize = self.branch_stack.items.len - 2; |
| | 1145 | while (true) { |
| | 1146 | i -= 1; |
| | 1147 | if (self.branch_stack.items[i].inst_table.get(then_key)) |mcv| { |
| | 1148 | assert(mcv != .dead); |
| | 1149 | break :blk mcv; |
| | 1150 | } |
| | 1151 | } |
| | 1152 | }; |
| | 1153 | log.debug("consolidating then_entry {d} {}=>{}", .{ then_key, parent_mcv, then_value }); |
| | 1154 | // TODO make sure the destination stack offset / register does not already have something |
| | 1155 | // going on there. |
| | 1156 | try self.setRegOrMem(self.air.typeOfIndex(then_key), parent_mcv, then_value); |
| | 1157 | // TODO track the new register / stack allocation |
| | 1158 | } |
| | 1159 | |
| | 1160 | { |
| | 1161 | var item = self.branch_stack.pop(); |
| | 1162 | item.deinit(self.gpa); |
| | 1163 | } |
| | 1164 | |
| | 1165 | // We already took care of pl_op.operand earlier, so we're going |
| | 1166 | // to pass .none here |
| | 1167 | return self.finishAir(inst, .unreach, .{ .none, .none, .none }); |
| | 1168 | } |
| | 1169 | |
| 971 | fn airDbgBlock(self: *Self, inst: Air.Inst.Index) !void { | 1170 | fn airDbgBlock(self: *Self, inst: Air.Inst.Index) !void { |
| 972 | // TODO emit debug info lexical block | 1171 | // TODO emit debug info lexical block |
| 973 | return self.finishAir(inst, .dead, .{ .none, .none, .none }); | 1172 | return self.finishAir(inst, .dead, .{ .none, .none, .none }); |
| ... | @@ -1798,11 +1997,18 @@ fn ret(self: *Self, mcv: MCValue) !void { | ... | @@ -1798,11 +1997,18 @@ fn ret(self: *Self, mcv: MCValue) !void { |
| 1798 | const ret_ty = self.fn_type.fnReturnType(); | 1997 | const ret_ty = self.fn_type.fnReturnType(); |
| 1799 | try self.setRegOrMem(ret_ty, self.ret_mcv, mcv); | 1998 | try self.setRegOrMem(ret_ty, self.ret_mcv, mcv); |
| 1800 | | 1999 | |
| 1801 | // Just add space for an instruction, patch this later | 2000 | // Just add space for a branch instruction, patch this later |
| 1802 | const index = try self.addInst(.{ | 2001 | const index = try self.addInst(.{ |
| 1803 | .tag = .nop, | 2002 | .tag = .nop, |
| 1804 | .data = .{ .nop = {} }, | 2003 | .data = .{ .nop = {} }, |
| 1805 | }); | 2004 | }); |
| | 2005 | |
| | 2006 | // Reserve space for the delay slot too |
| | 2007 | // TODO find out a way to fill this |
| | 2008 | _ = try self.addInst(.{ |
| | 2009 | .tag = .nop, |
| | 2010 | .data = .{ .nop = {} }, |
| | 2011 | }); |
| 1806 | try self.exitlude_jump_relocs.append(self.gpa, index); | 2012 | try self.exitlude_jump_relocs.append(self.gpa, index); |
| 1807 | } | 2013 | } |
| 1808 | | 2014 | |