| ... | @@ -36,7 +36,7 @@ pub fn RegisterManager( | ... | @@ -36,7 +36,7 @@ pub fn RegisterManager( |
| 36 | } | 36 | } |
| 37 | | 37 | |
| 38 | fn isTracked(reg: Register) bool { | 38 | fn isTracked(reg: Register) bool { |
| 39 | return std.mem.indexOfScalar(Register, callee_preserved_regs, reg) != null; | 39 | return reg.allocIndex() != null; |
| 40 | } | 40 | } |
| 41 | | 41 | |
| 42 | fn markRegUsed(self: *Self, reg: Register) void { | 42 | fn markRegUsed(self: *Self, reg: Register) void { |
| ... | @@ -55,6 +55,7 @@ pub fn RegisterManager( | ... | @@ -55,6 +55,7 @@ pub fn RegisterManager( |
| 55 | self.free_registers |= @as(FreeRegInt, 1) << shift; | 55 | self.free_registers |= @as(FreeRegInt, 1) << shift; |
| 56 | } | 56 | } |
| 57 | | 57 | |
| | 58 | /// Returns true when this register is not tracked |
| 58 | pub fn isRegFree(self: Self, reg: Register) bool { | 59 | pub fn isRegFree(self: Self, reg: Register) bool { |
| 59 | if (FreeRegInt == u0) return true; | 60 | if (FreeRegInt == u0) return true; |
| 60 | const index = reg.allocIndex() orelse return true; | 61 | const index = reg.allocIndex() orelse return true; |
| ... | @@ -63,7 +64,8 @@ pub fn RegisterManager( | ... | @@ -63,7 +64,8 @@ pub fn RegisterManager( |
| 63 | } | 64 | } |
| 64 | | 65 | |
| 65 | /// Returns whether this register was allocated in the course | 66 | /// Returns whether this register was allocated in the course |
| 66 | /// of this function | 67 | /// of this function. |
| | 68 | /// Returns false when this register is not tracked |
| 67 | pub fn isRegAllocated(self: Self, reg: Register) bool { | 69 | pub fn isRegAllocated(self: Self, reg: Register) bool { |
| 68 | if (FreeRegInt == u0) return false; | 70 | if (FreeRegInt == u0) return false; |
| 69 | const index = reg.allocIndex() orelse return false; | 71 | const index = reg.allocIndex() orelse return false; |
| ... | @@ -71,57 +73,89 @@ pub fn RegisterManager( | ... | @@ -71,57 +73,89 @@ pub fn RegisterManager( |
| 71 | return self.allocated_registers & @as(FreeRegInt, 1) << shift != 0; | 73 | return self.allocated_registers & @as(FreeRegInt, 1) << shift != 0; |
| 72 | } | 74 | } |
| 73 | | 75 | |
| 74 | /// Before calling, must ensureCapacity + 1 on self.registers. | 76 | /// Before calling, must ensureCapacity + count on self.registers. |
| 75 | /// Returns `null` if all registers are allocated. | 77 | /// Returns `null` if all registers are allocated. |
| 76 | pub fn tryAllocReg(self: *Self, inst: *ir.Inst) ?Register { | 78 | pub fn tryAllocRegs(self: *Self, comptime count: comptime_int, insts: [count]*ir.Inst) ?[count]Register { |
| 77 | const free_index = @ctz(FreeRegInt, self.free_registers); | 79 | if (self.tryAllocRegsWithoutTracking(count)) |regs| { |
| 78 | if (free_index >= callee_preserved_regs.len) { | 80 | for (regs) |reg, i| { |
| | 81 | self.markRegUsed(reg); |
| | 82 | self.registers.putAssumeCapacityNoClobber(reg, insts[i]); |
| | 83 | } |
| | 84 | |
| | 85 | return regs; |
| | 86 | } else { |
| 79 | return null; | 87 | return null; |
| 80 | } | 88 | } |
| | 89 | } |
| 81 | | 90 | |
| 82 | // This is necessary because the return type of @ctz is 1 | 91 | /// Before calling, must ensureCapacity + 1 on self.registers. |
| 83 | // bit longer than ShiftInt if callee_preserved_regs.len | 92 | /// Returns `null` if all registers are allocated. |
| 84 | // is a power of two. This int cast is always safe because | 93 | pub fn tryAllocReg(self: *Self, inst: *ir.Inst) ?Register { |
| 85 | // free_index < callee_preserved_regs.len | 94 | return if (tryAllocRegs(self, 1, .{inst})) |regs| regs[0] else null; |
| 86 | const shift = @intCast(ShiftInt, free_index); | 95 | } |
| 87 | const mask = @as(FreeRegInt, 1) << shift; | | |
| 88 | self.free_registers &= ~mask; | | |
| 89 | self.allocated_registers |= mask; | | |
| 90 | | 96 | |
| 91 | const reg = callee_preserved_regs[free_index]; | 97 | /// Before calling, must ensureCapacity + count on self.registers. |
| 92 | self.registers.putAssumeCapacityNoClobber(reg, inst); | 98 | pub fn allocRegs(self: *Self, comptime count: comptime_int, insts: [count]*ir.Inst) ![count]Register { |
| 93 | log.debug("alloc {} => {*}", .{ reg, inst }); | 99 | comptime assert(count > 0 and count <= callee_preserved_regs.len); |
| 94 | return reg; | 100 | |
| | 101 | return self.tryAllocRegs(count, insts) orelse blk: { |
| | 102 | // We'll take over the first count registers. Spill |
| | 103 | // the instructions that were previously there to a |
| | 104 | // stack allocations. |
| | 105 | var regs: [count]Register = undefined; |
| | 106 | std.mem.copy(Register, &regs, callee_preserved_regs[0..count]); |
| | 107 | |
| | 108 | for (regs) |reg, i| { |
| | 109 | if (self.isRegFree(reg)) { |
| | 110 | self.markRegUsed(reg); |
| | 111 | self.registers.putAssumeCapacityNoClobber(reg, insts[i]); |
| | 112 | } else { |
| | 113 | const regs_entry = self.registers.getEntry(reg).?; |
| | 114 | const spilled_inst = regs_entry.value; |
| | 115 | regs_entry.value = insts[i]; |
| | 116 | try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); |
| | 117 | } |
| | 118 | } |
| | 119 | |
| | 120 | break :blk regs; |
| | 121 | }; |
| 95 | } | 122 | } |
| 96 | | 123 | |
| 97 | /// Before calling, must ensureCapacity + 1 on self.registers. | 124 | /// Before calling, must ensureCapacity + 1 on self.registers. |
| 98 | pub fn allocReg(self: *Self, inst: *ir.Inst) !Register { | 125 | pub fn allocReg(self: *Self, inst: *ir.Inst) !Register { |
| 99 | return self.tryAllocReg(inst) orelse b: { | 126 | return (try allocRegs(self, 1, .{inst}))[0]; |
| 100 | // We'll take over the first register. Move the instruction that was previously | 127 | } |
| 101 | // there to a stack allocation. | | |
| 102 | const reg = callee_preserved_regs[0]; | | |
| 103 | const regs_entry = self.registers.getEntry(reg).?; | | |
| 104 | const spilled_inst = regs_entry.value; | | |
| 105 | regs_entry.value = inst; | | |
| 106 | try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); | | |
| 107 | | 128 | |
| 108 | break :b reg; | 129 | /// Does not track the registers. |
| 109 | }; | 130 | /// Returns `null` if not enough registers are free. |
| | 131 | pub fn tryAllocRegsWithoutTracking(self: *Self, comptime count: comptime_int) ?[count]Register { |
| | 132 | comptime if (callee_preserved_regs.len == 0) return null; |
| | 133 | comptime assert(count > 0 and count <= callee_preserved_regs.len); |
| | 134 | |
| | 135 | const free_registers = @popCount(FreeRegInt, self.free_registers); |
| | 136 | if (free_registers < count) return null; |
| | 137 | |
| | 138 | var regs: [count]Register = undefined; |
| | 139 | var i: usize = 0; |
| | 140 | for (callee_preserved_regs) |reg| { |
| | 141 | if (i >= count) break; |
| | 142 | if (self.isRegFree(reg)) { |
| | 143 | regs[i] = reg; |
| | 144 | i += 1; |
| | 145 | } |
| | 146 | } |
| | 147 | return regs; |
| 110 | } | 148 | } |
| 111 | | 149 | |
| 112 | /// Does not track the register. | 150 | /// Does not track the register. |
| 113 | /// Returns `null` if all registers are allocated. | 151 | /// Returns `null` if all registers are allocated. |
| 114 | pub fn findUnusedReg(self: *Self) ?Register { | 152 | pub fn tryAllocRegWithoutTracking(self: *Self) ?Register { |
| 115 | const free_index = @ctz(FreeRegInt, self.free_registers); | 153 | return if (tryAllocRegsWithoutTracking(self, 1)) |regs| regs[0] else null; |
| 116 | if (free_index >= callee_preserved_regs.len) { | | |
| 117 | return null; | | |
| 118 | } | | |
| 119 | return callee_preserved_regs[free_index]; | | |
| 120 | } | 154 | } |
| 121 | | 155 | |
| 122 | /// Does not track the register. | 156 | /// Does not track the register. |
| 123 | pub fn allocRegWithoutTracking(self: *Self) !Register { | 157 | pub fn allocRegWithoutTracking(self: *Self) !Register { |
| 124 | return self.findUnusedReg() orelse b: { | 158 | return self.tryAllocRegWithoutTracking() orelse b: { |
| 125 | // We'll take over the first register. Move the instruction that was previously | 159 | // We'll take over the first register. Move the instruction that was previously |
| 126 | // there to a stack allocation. | 160 | // there to a stack allocation. |
| 127 | const reg = callee_preserved_regs[0]; | 161 | const reg = callee_preserved_regs[0]; |
| ... | @@ -190,7 +224,10 @@ pub fn RegisterManager( | ... | @@ -190,7 +224,10 @@ pub fn RegisterManager( |
| 190 | } | 224 | } |
| 191 | | 225 | |
| 192 | const MockRegister = enum(u2) { | 226 | const MockRegister = enum(u2) { |
| 193 | r0, r1, r2, r3, | 227 | r0, |
| | 228 | r1, |
| | 229 | r2, |
| | 230 | r3, |
| 194 | | 231 | |
| 195 | pub fn allocIndex(self: MockRegister) ?u2 { | 232 | pub fn allocIndex(self: MockRegister) ?u2 { |
| 196 | inline for (mock_callee_preserved_regs) |cpreg, i| { | 233 | inline for (mock_callee_preserved_regs) |cpreg, i| { |
| ... | @@ -213,7 +250,7 @@ const MockFunction = struct { | ... | @@ -213,7 +250,7 @@ const MockFunction = struct { |
| 213 | self.register_manager.deinit(self.allocator); | 250 | self.register_manager.deinit(self.allocator); |
| 214 | self.spilled.deinit(self.allocator); | 251 | self.spilled.deinit(self.allocator); |
| 215 | } | 252 | } |
| 216 | | 253 | |
| 217 | pub fn spillInstruction(self: *Self, src: LazySrcLoc, reg: MockRegister, inst: *ir.Inst) !void { | 254 | pub fn spillInstruction(self: *Self, src: LazySrcLoc, reg: MockRegister, inst: *ir.Inst) !void { |
| 218 | try self.spilled.append(self.allocator, reg); | 255 | try self.spilled.append(self.allocator, reg); |
| 219 | } | 256 | } |