| ... | ... | @@ -16,7 +16,7 @@ pub fn RegisterManager( |
| 16 | 16 | ) type { |
| 17 | 17 | return struct { |
| 18 | 18 | /// The key must be canonical register. |
| 19 | | registers: std.AutoHashMapUnmanaged(Register, *ir.Inst) = .{}, |
| 19 | registers: [callee_preserved_regs.len]?*ir.Inst = [_]?*ir.Inst{null} ** callee_preserved_regs.len, |
| 20 | 20 | free_registers: FreeRegInt = math.maxInt(FreeRegInt), |
| 21 | 21 | /// Tracks all registers allocated in the course of this function |
| 22 | 22 | allocated_registers: FreeRegInt = 0, |
| ... | ... | @@ -31,14 +31,6 @@ pub fn RegisterManager( |
| 31 | 31 | return @fieldParentPtr(Function, "register_manager", self); |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | | pub fn deinit(self: *Self, allocator: *Allocator) void { |
| 35 | | self.registers.deinit(allocator); |
| 36 | | } |
| 37 | | |
| 38 | | fn isTracked(reg: Register) bool { |
| 39 | | return reg.allocIndex() != null; |
| 40 | | } |
| 41 | | |
| 42 | 34 | fn markRegUsed(self: *Self, reg: Register) void { |
| 43 | 35 | if (FreeRegInt == u0) return; |
| 44 | 36 | const index = reg.allocIndex() orelse return; |
| ... | ... | @@ -73,13 +65,13 @@ pub fn RegisterManager( |
| 73 | 65 | return self.allocated_registers & @as(FreeRegInt, 1) << shift != 0; |
| 74 | 66 | } |
| 75 | 67 | |
| 76 | | /// Before calling, must ensureCapacity + count on self.registers. |
| 77 | 68 | /// Returns `null` if all registers are allocated. |
| 78 | 69 | pub fn tryAllocRegs(self: *Self, comptime count: comptime_int, insts: [count]*ir.Inst) ?[count]Register { |
| 79 | 70 | if (self.tryAllocRegsWithoutTracking(count)) |regs| { |
| 80 | 71 | for (regs) |reg, i| { |
| 72 | const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null |
| 73 | self.registers[index] = insts[i]; |
| 81 | 74 | self.markRegUsed(reg); |
| 82 | | self.registers.putAssumeCapacityNoClobber(reg, insts[i]); |
| 83 | 75 | } |
| 84 | 76 | |
| 85 | 77 | return regs; |
| ... | ... | @@ -88,13 +80,11 @@ pub fn RegisterManager( |
| 88 | 80 | } |
| 89 | 81 | } |
| 90 | 82 | |
| 91 | | /// Before calling, must ensureCapacity + 1 on self.registers. |
| 92 | 83 | /// Returns `null` if all registers are allocated. |
| 93 | 84 | pub fn tryAllocReg(self: *Self, inst: *ir.Inst) ?Register { |
| 94 | 85 | return if (tryAllocRegs(self, 1, .{inst})) |regs| regs[0] else null; |
| 95 | 86 | } |
| 96 | 87 | |
| 97 | | /// Before calling, must ensureCapacity + count on self.registers. |
| 98 | 88 | pub fn allocRegs(self: *Self, comptime count: comptime_int, insts: [count]*ir.Inst) ![count]Register { |
| 99 | 89 | comptime assert(count > 0 and count <= callee_preserved_regs.len); |
| 100 | 90 | |
| ... | ... | @@ -106,24 +96,22 @@ pub fn RegisterManager( |
| 106 | 96 | std.mem.copy(Register, &regs, callee_preserved_regs[0..count]); |
| 107 | 97 | |
| 108 | 98 | for (regs) |reg, i| { |
| 99 | const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null |
| 109 | 100 | if (self.isRegFree(reg)) { |
| 110 | 101 | self.markRegUsed(reg); |
| 111 | | self.registers.putAssumeCapacityNoClobber(reg, insts[i]); |
| 112 | 102 | } else { |
| 113 | | const regs_entry = self.registers.getEntry(reg).?; |
| 114 | | const spilled_inst = regs_entry.value; |
| 115 | | regs_entry.value = insts[i]; |
| 103 | const spilled_inst = self.registers[index].?; |
| 116 | 104 | try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); |
| 117 | 105 | } |
| 106 | self.registers[index] = insts[i]; |
| 118 | 107 | } |
| 119 | 108 | |
| 120 | 109 | break :blk regs; |
| 121 | 110 | }; |
| 122 | 111 | } |
| 123 | 112 | |
| 124 | | /// Before calling, must ensureCapacity + 1 on self.registers. |
| 125 | 113 | pub fn allocReg(self: *Self, inst: *ir.Inst) !Register { |
| 126 | | return (try allocRegs(self, 1, .{inst}))[0]; |
| 114 | return (try self.allocRegs(1, .{inst}))[0]; |
| 127 | 115 | } |
| 128 | 116 | |
| 129 | 117 | /// Does not track the registers. |
| ... | ... | @@ -150,37 +138,48 @@ pub fn RegisterManager( |
| 150 | 138 | /// Does not track the register. |
| 151 | 139 | /// Returns `null` if all registers are allocated. |
| 152 | 140 | pub fn tryAllocRegWithoutTracking(self: *Self) ?Register { |
| 153 | | return if (tryAllocRegsWithoutTracking(self, 1)) |regs| regs[0] else null; |
| 141 | return if (self.tryAllocRegsWithoutTracking(1)) |regs| regs[0] else null; |
| 154 | 142 | } |
| 155 | 143 | |
| 156 | | /// Does not track the register. |
| 157 | | pub fn allocRegWithoutTracking(self: *Self) !Register { |
| 158 | | return self.tryAllocRegWithoutTracking() orelse b: { |
| 159 | | // We'll take over the first register. Move the instruction that was previously |
| 160 | | // there to a stack allocation. |
| 161 | | const reg = callee_preserved_regs[0]; |
| 162 | | const regs_entry = self.registers.remove(reg).?; |
| 163 | | const spilled_inst = regs_entry.value; |
| 164 | | try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); |
| 165 | | self.markRegFree(reg); |
| 144 | /// Does not track the registers |
| 145 | pub fn allocRegsWithoutTracking(self: *Self, comptime count: comptime_int) ![count]Register { |
| 146 | return self.tryAllocRegsWithoutTracking(count) orelse blk: { |
| 147 | // We'll take over the first count registers. Spill |
| 148 | // the instructions that were previously there to a |
| 149 | // stack allocations. |
| 150 | var regs: [count]Register = undefined; |
| 151 | std.mem.copy(Register, &regs, callee_preserved_regs[0..count]); |
| 166 | 152 | |
| 167 | | break :b reg; |
| 153 | for (regs) |reg, i| { |
| 154 | const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null |
| 155 | if (!self.isRegFree(reg)) { |
| 156 | const spilled_inst = self.registers[index].?; |
| 157 | try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); |
| 158 | self.registers[index] = null; |
| 159 | self.markRegFree(reg); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | break :blk regs; |
| 168 | 164 | }; |
| 169 | 165 | } |
| 170 | 166 | |
| 167 | /// Does not track the register. |
| 168 | pub fn allocRegWithoutTracking(self: *Self) !Register { |
| 169 | return (try self.allocRegsWithoutTracking(1))[0]; |
| 170 | } |
| 171 | |
| 171 | 172 | /// Allocates the specified register with the specified |
| 172 | 173 | /// instruction. Spills the register if it is currently |
| 173 | 174 | /// allocated. |
| 174 | | /// Before calling, must ensureCapacity + 1 on self.registers. |
| 175 | 175 | pub fn getReg(self: *Self, reg: Register, inst: *ir.Inst) !void { |
| 176 | | if (!isTracked(reg)) return; |
| 176 | const index = reg.allocIndex() orelse return; |
| 177 | 177 | |
| 178 | 178 | if (!self.isRegFree(reg)) { |
| 179 | 179 | // Move the instruction that was previously there to a |
| 180 | 180 | // stack allocation. |
| 181 | | const regs_entry = self.registers.getEntry(reg).?; |
| 182 | | const spilled_inst = regs_entry.value; |
| 183 | | regs_entry.value = inst; |
| 181 | const spilled_inst = self.registers[index].?; |
| 182 | self.registers[index] = inst; |
| 184 | 183 | try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); |
| 185 | 184 | } else { |
| 186 | 185 | self.getRegAssumeFree(reg, inst); |
| ... | ... | @@ -190,34 +189,33 @@ pub fn RegisterManager( |
| 190 | 189 | /// Spills the register if it is currently allocated. |
| 191 | 190 | /// Does not track the register. |
| 192 | 191 | pub fn getRegWithoutTracking(self: *Self, reg: Register) !void { |
| 193 | | if (!isTracked(reg)) return; |
| 192 | const index = reg.allocIndex() orelse return; |
| 194 | 193 | |
| 195 | 194 | if (!self.isRegFree(reg)) { |
| 196 | 195 | // Move the instruction that was previously there to a |
| 197 | 196 | // stack allocation. |
| 198 | | const regs_entry = self.registers.remove(reg).?; |
| 199 | | const spilled_inst = regs_entry.value; |
| 197 | const spilled_inst = self.registers[index].?; |
| 200 | 198 | try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); |
| 201 | 199 | self.markRegFree(reg); |
| 202 | 200 | } |
| 203 | 201 | } |
| 204 | 202 | |
| 205 | 203 | /// Allocates the specified register with the specified |
| 206 | | /// instruction. Assumes that the register is free and no |
| 204 | /// instruction. Asserts that the register is free and no |
| 207 | 205 | /// spilling is necessary. |
| 208 | | /// Before calling, must ensureCapacity + 1 on self.registers. |
| 209 | 206 | pub fn getRegAssumeFree(self: *Self, reg: Register, inst: *ir.Inst) void { |
| 210 | | if (!isTracked(reg)) return; |
| 207 | const index = reg.allocIndex() orelse return; |
| 211 | 208 | |
| 212 | | self.registers.putAssumeCapacityNoClobber(reg, inst); |
| 209 | assert(self.registers[index] == null); |
| 210 | self.registers[index] = inst; |
| 213 | 211 | self.markRegUsed(reg); |
| 214 | 212 | } |
| 215 | 213 | |
| 216 | 214 | /// Marks the specified register as free |
| 217 | 215 | pub fn freeReg(self: *Self, reg: Register) void { |
| 218 | | if (!isTracked(reg)) return; |
| 216 | const index = reg.allocIndex() orelse return; |
| 219 | 217 | |
| 220 | | _ = self.registers.remove(reg); |
| 218 | self.registers[index] = null; |
| 221 | 219 | self.markRegFree(reg); |
| 222 | 220 | } |
| 223 | 221 | }; |
| ... | ... | @@ -247,7 +245,6 @@ const MockFunction = struct { |
| 247 | 245 | const Self = @This(); |
| 248 | 246 | |
| 249 | 247 | pub fn deinit(self: *Self) void { |
| 250 | | self.register_manager.deinit(self.allocator); |
| 251 | 248 | self.spilled.deinit(self.allocator); |
| 252 | 249 | } |
| 253 | 250 | |
| ... | ... | @@ -273,7 +270,6 @@ test "tryAllocReg: no spilling" { |
| 273 | 270 | std.testing.expect(!function.register_manager.isRegAllocated(.r2)); |
| 274 | 271 | std.testing.expect(!function.register_manager.isRegAllocated(.r3)); |
| 275 | 272 | |
| 276 | | try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2); |
| 277 | 273 | std.testing.expectEqual(@as(?MockRegister, .r2), function.register_manager.tryAllocReg(&mock_instruction)); |
| 278 | 274 | std.testing.expectEqual(@as(?MockRegister, .r3), function.register_manager.tryAllocReg(&mock_instruction)); |
| 279 | 275 | std.testing.expectEqual(@as(?MockRegister, null), function.register_manager.tryAllocReg(&mock_instruction)); |
| ... | ... | @@ -305,7 +301,6 @@ test "allocReg: spilling" { |
| 305 | 301 | std.testing.expect(!function.register_manager.isRegAllocated(.r2)); |
| 306 | 302 | std.testing.expect(!function.register_manager.isRegAllocated(.r3)); |
| 307 | 303 | |
| 308 | | try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2); |
| 309 | 304 | std.testing.expectEqual(@as(?MockRegister, .r2), try function.register_manager.allocReg(&mock_instruction)); |
| 310 | 305 | std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction)); |
| 311 | 306 | |
| ... | ... | @@ -336,14 +331,12 @@ test "getReg" { |
| 336 | 331 | std.testing.expect(!function.register_manager.isRegAllocated(.r2)); |
| 337 | 332 | std.testing.expect(!function.register_manager.isRegAllocated(.r3)); |
| 338 | 333 | |
| 339 | | try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2); |
| 340 | 334 | try function.register_manager.getReg(.r3, &mock_instruction); |
| 341 | 335 | |
| 342 | 336 | std.testing.expect(!function.register_manager.isRegAllocated(.r2)); |
| 343 | 337 | std.testing.expect(function.register_manager.isRegAllocated(.r3)); |
| 344 | 338 | |
| 345 | 339 | // Spill r3 |
| 346 | | try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2); |
| 347 | 340 | try function.register_manager.getReg(.r3, &mock_instruction); |
| 348 | 341 | |
| 349 | 342 | std.testing.expect(!function.register_manager.isRegAllocated(.r2)); |