aboutsummaryrefslogtreecommitdiff
path: root/src/register_manager.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2021-04-02 12:09:38 -0700
committerAndrew Kelley <andrew@ziglang.org>2021-04-02 12:09:38 -0700
commita0e89c9b46fc91d9b1dfebd02eaae233802e3cbc (patch)
tree6a1ee37f130b4cb69204158ff915ecc2c6bb002b /src/register_manager.zig
parent94383d14df77fa638dac14f4b2bda5a2e3f21c5c (diff)
parent228a1ce3e8d112a7710fa47c6b9486cf320b5d6f (diff)
downloadzig-a0e89c9b46fc91d9b1dfebd02eaae233802e3cbc.tar.gz
zig-a0e89c9b46fc91d9b1dfebd02eaae233802e3cbc.zip
Merge remote-tracking branch 'origin/master' into llvm12
Diffstat (limited to 'src/register_manager.zig')
-rw-r--r--src/register_manager.zig228
1 files changed, 228 insertions, 0 deletions
diff --git a/src/register_manager.zig b/src/register_manager.zig
new file mode 100644
index 0000000000..abb1632165
--- /dev/null
+++ b/src/register_manager.zig
@@ -0,0 +1,228 @@
+const std = @import("std");
+const math = std.math;
+const assert = std.debug.assert;
+const Allocator = std.mem.Allocator;
+const ir = @import("ir.zig");
+const Type = @import("type.zig").Type;
+const Module = @import("Module.zig");
+const LazySrcLoc = Module.LazySrcLoc;
+
+const log = std.log.scoped(.register_manager);
+
+pub fn RegisterManager(
+ comptime Function: type,
+ comptime Register: type,
+ comptime callee_preserved_regs: []const Register,
+) type {
+ return struct {
+ /// The key must be canonical register.
+ registers: std.AutoHashMapUnmanaged(Register, *ir.Inst) = .{},
+ free_registers: FreeRegInt = math.maxInt(FreeRegInt),
+ /// Tracks all registers allocated in the course of this function
+ allocated_registers: FreeRegInt = 0,
+
+ const Self = @This();
+
+ /// An integer whose bits represent all the registers and whether they are free.
+ const FreeRegInt = std.meta.Int(.unsigned, callee_preserved_regs.len);
+ const ShiftInt = math.Log2Int(FreeRegInt);
+
+ fn getFunction(self: *Self) *Function {
+ return @fieldParentPtr(Function, "register_manager", self);
+ }
+
+ pub fn deinit(self: *Self, allocator: *Allocator) void {
+ self.registers.deinit(allocator);
+ }
+
+ fn markRegUsed(self: *Self, reg: Register) void {
+ if (FreeRegInt == u0) return;
+ const index = reg.allocIndex() orelse return;
+ const shift = @intCast(ShiftInt, index);
+ const mask = @as(FreeRegInt, 1) << shift;
+ self.free_registers &= ~mask;
+ self.allocated_registers |= mask;
+ }
+
+ fn markRegFree(self: *Self, reg: Register) void {
+ if (FreeRegInt == u0) return;
+ const index = reg.allocIndex() orelse return;
+ const shift = @intCast(ShiftInt, index);
+ self.free_registers |= @as(FreeRegInt, 1) << shift;
+ }
+
+ /// Returns whether this register was allocated in the course
+ /// of this function
+ pub fn isRegAllocated(self: Self, reg: Register) bool {
+ if (FreeRegInt == u0) return false;
+ const index = reg.allocIndex() orelse return false;
+ const shift = @intCast(ShiftInt, index);
+ return self.allocated_registers & @as(FreeRegInt, 1) << shift != 0;
+ }
+
+ /// Before calling, must ensureCapacity + 1 on self.registers.
+ /// Returns `null` if all registers are allocated.
+ pub fn tryAllocReg(self: *Self, inst: *ir.Inst) ?Register {
+ const free_index = @ctz(FreeRegInt, self.free_registers);
+ if (free_index >= callee_preserved_regs.len) {
+ return null;
+ }
+
+ // This is necessary because the return type of @ctz is 1
+ // bit longer than ShiftInt if callee_preserved_regs.len
+ // is a power of two. This int cast is always safe because
+ // free_index < callee_preserved_regs.len
+ const shift = @intCast(ShiftInt, free_index);
+ const mask = @as(FreeRegInt, 1) << shift;
+ self.free_registers &= ~mask;
+ self.allocated_registers |= mask;
+
+ const reg = callee_preserved_regs[free_index];
+ self.registers.putAssumeCapacityNoClobber(reg, inst);
+ log.debug("alloc {} => {*}", .{ reg, inst });
+ return reg;
+ }
+
+ /// Before calling, must ensureCapacity + 1 on self.registers.
+ pub fn allocReg(self: *Self, inst: *ir.Inst) !Register {
+ return self.tryAllocReg(inst) orelse b: {
+ // We'll take over the first register. Move the instruction that was previously
+ // there to a stack allocation.
+ const reg = callee_preserved_regs[0];
+ const regs_entry = self.registers.getEntry(reg).?;
+ const spilled_inst = regs_entry.value;
+ regs_entry.value = inst;
+ try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
+
+ break :b reg;
+ };
+ }
+
+ /// Does not track the register.
+ /// Returns `null` if all registers are allocated.
+ pub fn findUnusedReg(self: *Self) ?Register {
+ const free_index = @ctz(FreeRegInt, self.free_registers);
+ if (free_index >= callee_preserved_regs.len) {
+ return null;
+ }
+ return callee_preserved_regs[free_index];
+ }
+
+ /// Does not track the register.
+ pub fn allocRegWithoutTracking(self: *Self) !Register {
+ return self.findUnusedReg() orelse b: {
+ // We'll take over the first register. Move the instruction that was previously
+ // there to a stack allocation.
+ const reg = callee_preserved_regs[0];
+ const regs_entry = self.registers.remove(reg).?;
+ const spilled_inst = regs_entry.value;
+ try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
+
+ break :b reg;
+ };
+ }
+
+ pub fn getRegAssumeFree(self: *Self, reg: Register, inst: *ir.Inst) !void {
+ try self.registers.putNoClobber(self.getFunction().gpa, reg, inst);
+ self.markRegUsed(reg);
+ }
+
+ pub fn freeReg(self: *Self, reg: Register) void {
+ _ = self.registers.remove(reg);
+ self.markRegFree(reg);
+ }
+ };
+}
+
+const MockRegister = enum(u2) {
+ r0, r1, r2, r3,
+
+ pub fn allocIndex(self: MockRegister) ?u2 {
+ inline for (mock_callee_preserved_regs) |cpreg, i| {
+ if (self == cpreg) return i;
+ }
+ return null;
+ }
+};
+
+const mock_callee_preserved_regs = [_]MockRegister{ .r2, .r3 };
+
+const MockFunction = struct {
+ allocator: *Allocator,
+ register_manager: RegisterManager(Self, MockRegister, &mock_callee_preserved_regs) = .{},
+ spilled: std.ArrayListUnmanaged(MockRegister) = .{},
+
+ const Self = @This();
+
+ pub fn deinit(self: *Self) void {
+ self.register_manager.deinit(self.allocator);
+ self.spilled.deinit(self.allocator);
+ }
+
+ pub fn spillInstruction(self: *Self, src: LazySrcLoc, reg: MockRegister, inst: *ir.Inst) !void {
+ try self.spilled.append(self.allocator, reg);
+ }
+};
+
+test "tryAllocReg: no spilling" {
+ const allocator = std.testing.allocator;
+
+ var function = MockFunction{
+ .allocator = allocator,
+ };
+ defer function.deinit();
+
+ var mock_instruction = ir.Inst{
+ .tag = .breakpoint,
+ .ty = Type.initTag(.void),
+ .src = .unneeded,
+ };
+
+ std.testing.expect(!function.register_manager.isRegAllocated(.r2));
+ std.testing.expect(!function.register_manager.isRegAllocated(.r3));
+
+ try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2);
+ std.testing.expectEqual(@as(?MockRegister, .r2), function.register_manager.tryAllocReg(&mock_instruction));
+ std.testing.expectEqual(@as(?MockRegister, .r3), function.register_manager.tryAllocReg(&mock_instruction));
+ std.testing.expectEqual(@as(?MockRegister, null), function.register_manager.tryAllocReg(&mock_instruction));
+
+ std.testing.expect(function.register_manager.isRegAllocated(.r2));
+ std.testing.expect(function.register_manager.isRegAllocated(.r3));
+
+ function.register_manager.freeReg(.r2);
+ function.register_manager.freeReg(.r3);
+
+ std.testing.expect(function.register_manager.isRegAllocated(.r2));
+ std.testing.expect(function.register_manager.isRegAllocated(.r3));
+}
+
+test "allocReg: spilling" {
+ const allocator = std.testing.allocator;
+
+ var function = MockFunction{
+ .allocator = allocator,
+ };
+ defer function.deinit();
+
+ var mock_instruction = ir.Inst{
+ .tag = .breakpoint,
+ .ty = Type.initTag(.void),
+ .src = .unneeded,
+ };
+
+ std.testing.expect(!function.register_manager.isRegAllocated(.r2));
+ std.testing.expect(!function.register_manager.isRegAllocated(.r3));
+
+ try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2);
+ std.testing.expectEqual(@as(?MockRegister, .r2), try function.register_manager.allocReg(&mock_instruction));
+ std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction));
+
+ // Spill a register
+ std.testing.expectEqual(@as(?MockRegister, .r2), try function.register_manager.allocReg(&mock_instruction));
+ std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items);
+
+ // No spilling necessary
+ function.register_manager.freeReg(.r3);
+ std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction));
+ std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items);
+}