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