mirror of
https://github.com/ziglang/zig.git
synced 2026-01-31 11:43:37 +00:00
x64: merge general purpose with simd register into one bitset
This way, we do not have to tweak the `RegisterManager` to handle multiple register types - we have one linear space instead. Additionally we can use the bitset itself to separate the registers into overlapping (the ones that are aliases of differing bitwidths) and nonoverlapping classes (for example, AVX registers do not overlap general purpose registers, thus they can be allocated simultaneously). Another huge benefit of this simple approach is the fact that we can still refer to *all* registers regardless of their class via enum literals which makes the code so much more readable. Finally, `RegisterLock` is universal across different register classes.
This commit is contained in:
parent
2aee230251
commit
9e5c8cb008
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -14,8 +14,7 @@ const assert = std.debug.assert;
|
||||
const bits = @import("bits.zig");
|
||||
const Air = @import("../../Air.zig");
|
||||
const CodeGen = @import("CodeGen.zig");
|
||||
const GpRegister = bits.Register;
|
||||
const AvxRegister = bits.AvxRegister;
|
||||
const Register = bits.Register;
|
||||
|
||||
instructions: std.MultiArrayList(Inst).Slice,
|
||||
/// The meaning of this data is determined by `Inst.Tag` value.
|
||||
@ -23,11 +22,7 @@ extra: []const u32,
|
||||
|
||||
pub const Inst = struct {
|
||||
tag: Tag,
|
||||
/// This is 3 fields, and the meaning of each depends on `tag`.
|
||||
/// reg1: Register
|
||||
/// reg2: Register
|
||||
/// flags: u2
|
||||
ops: u16,
|
||||
ops: Ops,
|
||||
/// The meaning of this depends on `tag` and `ops`.
|
||||
data: Data,
|
||||
|
||||
@ -397,6 +392,36 @@ pub const Inst = struct {
|
||||
/// The position of an MIR instruction within the `Mir` instructions array.
|
||||
pub const Index = u32;
|
||||
|
||||
pub const Ops = packed struct {
|
||||
reg1: u7,
|
||||
reg2: u7,
|
||||
flags: u2,
|
||||
|
||||
pub fn encode(vals: struct {
|
||||
reg1: Register = .none,
|
||||
reg2: Register = .none,
|
||||
flags: u2 = 0b00,
|
||||
}) Ops {
|
||||
return .{
|
||||
.reg1 = @enumToInt(vals.reg1),
|
||||
.reg2 = @enumToInt(vals.reg2),
|
||||
.flags = vals.flags,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn decode(ops: Ops) struct {
|
||||
reg1: Register,
|
||||
reg2: Register,
|
||||
flags: u2,
|
||||
} {
|
||||
return .{
|
||||
.reg1 = @intToEnum(Register, ops.reg1),
|
||||
.reg2 = @intToEnum(Register, ops.reg2),
|
||||
.flags = ops.flags,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// All instructions have a 4-byte payload, which is contained within
|
||||
/// this union. `Tag` determines which union field is active, as well as
|
||||
/// how to interpret the data within.
|
||||
@ -466,33 +491,6 @@ pub const DbgLineColumn = struct {
|
||||
column: u32,
|
||||
};
|
||||
|
||||
pub fn Ops(comptime Reg1: type, comptime Reg2: type) type {
|
||||
return struct {
|
||||
reg1: Reg1 = .none,
|
||||
reg2: Reg2 = .none,
|
||||
flags: u2 = 0b00,
|
||||
|
||||
pub fn encode(self: @This()) u16 {
|
||||
var ops: u16 = 0;
|
||||
ops |= @intCast(u16, @enumToInt(self.reg1)) << 9;
|
||||
ops |= @intCast(u16, @enumToInt(self.reg2)) << 2;
|
||||
ops |= self.flags;
|
||||
return ops;
|
||||
}
|
||||
|
||||
pub fn decode(ops: u16) @This() {
|
||||
const reg1 = @intToEnum(Reg1, @truncate(u7, ops >> 9));
|
||||
const reg2 = @intToEnum(Reg2, @truncate(u7, ops >> 2));
|
||||
const flags = @truncate(u2, ops);
|
||||
return .{
|
||||
.reg1 = reg1,
|
||||
.reg2 = reg2,
|
||||
.flags = flags,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void {
|
||||
mir.instructions.deinit(gpa);
|
||||
gpa.free(mir.extra);
|
||||
|
||||
@ -3,7 +3,6 @@ const Type = @import("../../type.zig").Type;
|
||||
const Target = std.Target;
|
||||
const assert = std.debug.assert;
|
||||
const Register = @import("bits.zig").Register;
|
||||
const AvxRegister = @import("bits.zig").AvxRegister;
|
||||
|
||||
pub const Class = enum { integer, sse, sseup, x87, x87up, complex_x87, memory, none };
|
||||
|
||||
@ -379,11 +378,17 @@ pub const callee_preserved_regs = [_]Register{ .rbx, .r12, .r13, .r14, .r15 };
|
||||
/// the caller relinquishes control to a subroutine via call instruction (or similar).
|
||||
/// In other words, these registers are free to use by the callee.
|
||||
pub const caller_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 };
|
||||
pub const allocatable_registers = callee_preserved_regs ++ caller_preserved_regs;
|
||||
pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
|
||||
pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx };
|
||||
|
||||
pub const avx_regs = [_]AvxRegister{
|
||||
pub const avx_regs = [_]Register{
|
||||
.ymm0, .ymm1, .ymm2, .ymm3, .ymm4, .ymm5, .ymm6, .ymm7,
|
||||
.ymm8, .ymm9, .ymm10, .ymm11, .ymm12, .ymm13, .ymm14, .ymm15,
|
||||
};
|
||||
pub const allocatable_registers = callee_preserved_regs ++ caller_preserved_regs ++ avx_regs;
|
||||
|
||||
// Masks for register manager
|
||||
const FreeRegInt = std.meta.Int(.unsigned, allocatable_registers.len);
|
||||
// TODO
|
||||
pub const gp_mask: FreeRegInt = 0x3fff;
|
||||
pub const avx_mask: FreeRegInt = 0x3fff_c000;
|
||||
|
||||
pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
|
||||
pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx };
|
||||
|
||||
@ -43,17 +43,36 @@ pub const Register = enum(u7) {
|
||||
al, cl, dl, bl, ah, ch, dh, bh,
|
||||
r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b,
|
||||
|
||||
// Pseudo, used only for MIR to signify that the
|
||||
// operand is not a register but an immediate, etc.
|
||||
// 64-79, 256-bit registers.
|
||||
// id is int value - 64.
|
||||
ymm0, ymm1, ymm2, ymm3, ymm4, ymm5, ymm6, ymm7,
|
||||
ymm8, ymm9, ymm10, ymm11, ymm12, ymm13, ymm14, ymm15,
|
||||
|
||||
// 80-95, 128-bit registers.
|
||||
// id is int value - 80.
|
||||
xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7,
|
||||
xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15,
|
||||
|
||||
// Pseudo-value for MIR instructions.
|
||||
none,
|
||||
|
||||
pub fn id(self: Register) u5 {
|
||||
return switch (@enumToInt(self)) {
|
||||
0...63 => @as(u5, @truncate(u4, @enumToInt(self))),
|
||||
64...79 => @truncate(u5, @enumToInt(self)),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the bit-width of the register.
|
||||
pub fn size(self: Register) u7 {
|
||||
pub fn size(self: Register) u9 {
|
||||
return switch (@enumToInt(self)) {
|
||||
0...15 => 64,
|
||||
16...31 => 32,
|
||||
32...47 => 16,
|
||||
48...64 => 8,
|
||||
48...63 => 8,
|
||||
64...79 => 256,
|
||||
80...95 => 128,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@ -72,15 +91,23 @@ pub const Register = enum(u7) {
|
||||
/// an instruction (@see isExtended), and requires special handling. The
|
||||
/// lower three bits are often embedded directly in instructions (such as
|
||||
/// the B8 variant of moves), or used in R/M bytes.
|
||||
pub fn id(self: Register) u4 {
|
||||
pub fn enc(self: Register) u4 {
|
||||
return @truncate(u4, @enumToInt(self));
|
||||
}
|
||||
|
||||
/// Like id, but only returns the lower 3 bits.
|
||||
pub fn lowId(self: Register) u3 {
|
||||
/// Like enc, but only returns the lower 3 bits.
|
||||
pub fn lowEnc(self: Register) u3 {
|
||||
return @truncate(u3, @enumToInt(self));
|
||||
}
|
||||
|
||||
pub fn to256(self: Register) Register {
|
||||
return @intToEnum(Register, @as(u8, self.id()) + 64);
|
||||
}
|
||||
|
||||
pub fn to128(self: Register) Register {
|
||||
return @intToEnum(Register, @as(u8, self.id()) + 80);
|
||||
}
|
||||
|
||||
/// Convert from any register to its 64 bit alias.
|
||||
pub fn to64(self: Register) Register {
|
||||
return @intToEnum(Register, self.id());
|
||||
@ -126,57 +153,6 @@ pub const Register = enum(u7) {
|
||||
}
|
||||
};
|
||||
|
||||
/// AVX registers.
|
||||
/// TODO missing dwarfLocOp implementation.
|
||||
/// TODO add support for AVX-512
|
||||
pub const AvxRegister = enum(u6) {
|
||||
// 256-bit registers
|
||||
ymm0, ymm1, ymm2, ymm3, ymm4, ymm5, ymm6, ymm7,
|
||||
ymm8, ymm9, ymm10, ymm11, ymm12, ymm13, ymm14, ymm15,
|
||||
|
||||
// 128-bit registers
|
||||
xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7,
|
||||
xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15,
|
||||
|
||||
// Pseudo, used only for MIR to signify that the
|
||||
// operand is not a register but an immediate, etc.
|
||||
none,
|
||||
|
||||
/// Returns the bit-width of the register.
|
||||
pub fn size(self: AvxRegister) u9 {
|
||||
return switch (@enumToInt(self)) {
|
||||
0...15 => 256,
|
||||
16...31 => 128,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns whether the register is *extended*.
|
||||
pub fn isExtended(self: AvxRegister) bool {
|
||||
return @enumToInt(self) & 0x08 != 0;
|
||||
}
|
||||
|
||||
/// This returns the 4-bit register ID.
|
||||
pub fn id(self: AvxRegister) u4 {
|
||||
return @truncate(u4, @enumToInt(self));
|
||||
}
|
||||
|
||||
/// Like id, but only returns the lower 3 bits.
|
||||
pub fn lowId(self: AvxRegister) u3 {
|
||||
return @truncate(u3, @enumToInt(self));
|
||||
}
|
||||
|
||||
/// Convert from any register to its 256 bit alias.
|
||||
pub fn to256(self: AvxRegister) AvxRegister {
|
||||
return @intToEnum(AvxRegister, self.id());
|
||||
}
|
||||
|
||||
/// Convert from any register to its 128 bit alias.
|
||||
pub fn to128(self: AvxRegister) AvxRegister {
|
||||
return @intToEnum(AvxRegister, @as(u8, self.id()) + 16);
|
||||
}
|
||||
};
|
||||
|
||||
// zig fmt: on
|
||||
|
||||
/// Encoding helper functions for x86_64 instructions
|
||||
@ -792,7 +768,7 @@ test "Encoder helpers - Vex prefix" {
|
||||
{
|
||||
stream.reset();
|
||||
var vex_prefix = Encoder.Vex{};
|
||||
vex_prefix.reg(AvxRegister.xmm15.id());
|
||||
vex_prefix.reg(Register.xmm15.id());
|
||||
const nwritten = vex_prefix.write(writer);
|
||||
try testing.expectEqualSlices(u8, &[_]u8{ 0xc5, 0x80 }, buf[0..nwritten]);
|
||||
}
|
||||
@ -832,7 +808,7 @@ test "Encoder helpers - Vex prefix" {
|
||||
vex.simd_prefix_66();
|
||||
encoder.vex(vex); // use 64 bit operation
|
||||
encoder.opcode_1byte(0x28);
|
||||
encoder.modRm_direct(0, AvxRegister.xmm1.lowId());
|
||||
encoder.modRm_direct(0, Register.xmm1.lowId());
|
||||
try testing.expectEqualSlices(u8, &[_]u8{ 0xC5, 0xF9, 0x28, 0xC1 }, code.items);
|
||||
}
|
||||
|
||||
@ -846,10 +822,10 @@ test "Encoder helpers - Vex prefix" {
|
||||
vex.simd_prefix_66();
|
||||
vex.lead_opc_0f();
|
||||
vex.rex(.{ .r = true });
|
||||
vex.reg(AvxRegister.xmm1.id());
|
||||
vex.reg(Register.xmm1.id());
|
||||
encoder.vex(vex);
|
||||
encoder.opcode_1byte(0x16);
|
||||
encoder.modRm_RIPDisp32(AvxRegister.xmm13.lowId());
|
||||
encoder.modRm_RIPDisp32(Register.xmm13.lowId());
|
||||
encoder.disp32(0);
|
||||
try testing.expectEqualSlices(u8, &[_]u8{ 0xC5, 0x71, 0x16, 0x2D, 0x00, 0x00, 0x00, 0x00 }, code.items);
|
||||
}
|
||||
|
||||
@ -78,13 +78,13 @@ pub fn generateFunction(
|
||||
debug_output: DebugInfoOutput,
|
||||
) GenerateSymbolError!FnResult {
|
||||
switch (bin_file.options.target.cpu.arch) {
|
||||
// .arm,
|
||||
// .armeb,
|
||||
// => return @import("arch/arm/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
// .aarch64,
|
||||
// .aarch64_be,
|
||||
// .aarch64_32,
|
||||
// => return @import("arch/aarch64/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
.arm,
|
||||
.armeb,
|
||||
=> return @import("arch/arm/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
.aarch64,
|
||||
.aarch64_be,
|
||||
.aarch64_32,
|
||||
=> return @import("arch/aarch64/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
//.arc => return Function(.arc).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
//.avr => return Function(.avr).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
//.bpfel => return Function(.bpfel).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
@ -101,9 +101,9 @@ pub fn generateFunction(
|
||||
//.r600 => return Function(.r600).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
//.amdgcn => return Function(.amdgcn).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
//.riscv32 => return Function(.riscv32).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
// .riscv64 => return @import("arch/riscv64/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
.riscv64 => return @import("arch/riscv64/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
//.sparc => return Function(.sparc).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
// .sparc64 => return @import("arch/sparc64/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
.sparc64 => return @import("arch/sparc64/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
//.sparcel => return Function(.sparcel).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
//.s390x => return Function(.s390x).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
//.tce => return Function(.tce).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
@ -129,9 +129,9 @@ pub fn generateFunction(
|
||||
//.renderscript32 => return Function(.renderscript32).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
//.renderscript64 => return Function(.renderscript64).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
//.ve => return Function(.ve).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
// .wasm32,
|
||||
// .wasm64,
|
||||
// => return @import("arch/wasm/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
.wasm32,
|
||||
.wasm64,
|
||||
=> return @import("arch/wasm/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output),
|
||||
else => @panic("Backend architectures that don't have good support yet are commented out, to improve compilation performance. If you are interested in one of these other backends feel free to uncomment them. Eventually these will be completed, but stage1 is slow and a memory hog."),
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,15 +23,10 @@ pub const AllocateRegistersError = error{
|
||||
CodegenFail,
|
||||
};
|
||||
|
||||
pub fn SpillFn(comptime Function: type, comptime Register: type) type {
|
||||
return fn (*Function, Register, Air.Inst.Index) anyerror!void;
|
||||
}
|
||||
|
||||
pub fn RegisterManager(
|
||||
comptime Function: type,
|
||||
comptime Register: type,
|
||||
comptime tracked_registers: []const Register,
|
||||
comptime spill_fn: SpillFn(Function, Register),
|
||||
) type {
|
||||
// architectures which do not have a concept of registers should
|
||||
// refrain from using RegisterManager
|
||||
@ -52,7 +47,6 @@ pub fn RegisterManager(
|
||||
allocated_registers: FreeRegInt = 0,
|
||||
/// Tracks registers which are locked from being allocated
|
||||
locked_registers: FreeRegInt = 0,
|
||||
function: *Function,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
@ -61,8 +55,8 @@ pub fn RegisterManager(
|
||||
const FreeRegInt = std.meta.Int(.unsigned, tracked_registers.len);
|
||||
const ShiftInt = math.Log2Int(FreeRegInt);
|
||||
|
||||
fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) AllocateRegistersError!void {
|
||||
return try spill_fn(self.function, reg, inst);
|
||||
fn getFunction(self: *Self) *Function {
|
||||
return @fieldParentPtr(Function, "register_manager", self);
|
||||
}
|
||||
|
||||
fn getRegisterMask(reg: Register) ?FreeRegInt {
|
||||
@ -257,14 +251,14 @@ pub fn RegisterManager(
|
||||
self.markRegUsed(reg);
|
||||
} else {
|
||||
const spilled_inst = self.registers[index];
|
||||
try self.spillInstruction(reg, spilled_inst);
|
||||
try self.getFunction().spillInstruction(reg, spilled_inst);
|
||||
}
|
||||
self.registers[index] = inst;
|
||||
} else {
|
||||
// Don't track the register
|
||||
if (!self.isRegFree(reg)) {
|
||||
const spilled_inst = self.registers[index];
|
||||
try self.spillInstruction(reg, spilled_inst);
|
||||
try self.getFunction().spillInstruction(reg, spilled_inst);
|
||||
self.freeReg(reg);
|
||||
}
|
||||
}
|
||||
@ -299,7 +293,7 @@ pub fn RegisterManager(
|
||||
// stack allocation.
|
||||
const spilled_inst = self.registers[index];
|
||||
self.registers[index] = tracked_inst;
|
||||
try self.spillInstruction(reg, spilled_inst);
|
||||
try self.getFunction().spillInstruction(reg, spilled_inst);
|
||||
} else {
|
||||
self.getRegAssumeFree(reg, tracked_inst);
|
||||
}
|
||||
@ -308,7 +302,7 @@ pub fn RegisterManager(
|
||||
// Move the instruction that was previously there to a
|
||||
// stack allocation.
|
||||
const spilled_inst = self.registers[index];
|
||||
try self.spillInstruction(reg, spilled_inst);
|
||||
try self.getFunction().spillInstruction(reg, spilled_inst);
|
||||
self.freeReg(reg);
|
||||
}
|
||||
}
|
||||
@ -338,253 +332,253 @@ pub fn RegisterManager(
|
||||
};
|
||||
}
|
||||
|
||||
//const MockRegister1 = enum(u2) {
|
||||
// r0,
|
||||
// r1,
|
||||
// r2,
|
||||
// r3,
|
||||
const MockRegister1 = enum(u2) {
|
||||
r0,
|
||||
r1,
|
||||
r2,
|
||||
r3,
|
||||
|
||||
// pub fn id(reg: MockRegister1) u2 {
|
||||
// return @enumToInt(reg);
|
||||
// }
|
||||
pub fn id(reg: MockRegister1) u2 {
|
||||
return @enumToInt(reg);
|
||||
}
|
||||
|
||||
// const allocatable_registers = [_]MockRegister1{ .r2, .r3 };
|
||||
//};
|
||||
const allocatable_registers = [_]MockRegister1{ .r2, .r3 };
|
||||
};
|
||||
|
||||
//const MockRegister2 = enum(u2) {
|
||||
// r0,
|
||||
// r1,
|
||||
// r2,
|
||||
// r3,
|
||||
const MockRegister2 = enum(u2) {
|
||||
r0,
|
||||
r1,
|
||||
r2,
|
||||
r3,
|
||||
|
||||
// pub fn id(reg: MockRegister2) u2 {
|
||||
// return @enumToInt(reg);
|
||||
// }
|
||||
pub fn id(reg: MockRegister2) u2 {
|
||||
return @enumToInt(reg);
|
||||
}
|
||||
|
||||
// const allocatable_registers = [_]MockRegister2{ .r0, .r1, .r2, .r3 };
|
||||
//};
|
||||
const allocatable_registers = [_]MockRegister2{ .r0, .r1, .r2, .r3 };
|
||||
};
|
||||
|
||||
//fn MockFunction(comptime Register: type) type {
|
||||
// return struct {
|
||||
// allocator: Allocator,
|
||||
// register_manager: RegisterManager(Self, Register, &Register.allocatable_registers) = .{},
|
||||
// spilled: std.ArrayListUnmanaged(Register) = .{},
|
||||
fn MockFunction(comptime Register: type) type {
|
||||
return struct {
|
||||
allocator: Allocator,
|
||||
register_manager: RegisterManager(Self, Register, &Register.allocatable_registers) = .{},
|
||||
spilled: std.ArrayListUnmanaged(Register) = .{},
|
||||
|
||||
// const Self = @This();
|
||||
const Self = @This();
|
||||
|
||||
// pub fn deinit(self: *Self) void {
|
||||
// self.spilled.deinit(self.allocator);
|
||||
// }
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.spilled.deinit(self.allocator);
|
||||
}
|
||||
|
||||
// pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void {
|
||||
// _ = inst;
|
||||
// try self.spilled.append(self.allocator, reg);
|
||||
// }
|
||||
pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void {
|
||||
_ = inst;
|
||||
try self.spilled.append(self.allocator, reg);
|
||||
}
|
||||
|
||||
// pub fn genAdd(self: *Self, res: Register, lhs: Register, rhs: Register) !void {
|
||||
// _ = self;
|
||||
// _ = res;
|
||||
// _ = lhs;
|
||||
// _ = rhs;
|
||||
// }
|
||||
// };
|
||||
//}
|
||||
pub fn genAdd(self: *Self, res: Register, lhs: Register, rhs: Register) !void {
|
||||
_ = self;
|
||||
_ = res;
|
||||
_ = lhs;
|
||||
_ = rhs;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//const MockFunction1 = MockFunction(MockRegister1);
|
||||
//const MockFunction2 = MockFunction(MockRegister2);
|
||||
const MockFunction1 = MockFunction(MockRegister1);
|
||||
const MockFunction2 = MockFunction(MockRegister2);
|
||||
|
||||
//test "default state" {
|
||||
// const allocator = std.testing.allocator;
|
||||
test "default state" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
// var function = MockFunction1{
|
||||
// .allocator = allocator,
|
||||
// };
|
||||
// defer function.deinit();
|
||||
var function = MockFunction1{
|
||||
.allocator = allocator,
|
||||
};
|
||||
defer function.deinit();
|
||||
|
||||
// try expect(!function.register_manager.isRegAllocated(.r2));
|
||||
// try expect(!function.register_manager.isRegAllocated(.r3));
|
||||
// try expect(function.register_manager.isRegFree(.r2));
|
||||
// try expect(function.register_manager.isRegFree(.r3));
|
||||
//}
|
||||
try expect(!function.register_manager.isRegAllocated(.r2));
|
||||
try expect(!function.register_manager.isRegAllocated(.r3));
|
||||
try expect(function.register_manager.isRegFree(.r2));
|
||||
try expect(function.register_manager.isRegFree(.r3));
|
||||
}
|
||||
|
||||
//test "tryAllocReg: no spilling" {
|
||||
// const allocator = std.testing.allocator;
|
||||
test "tryAllocReg: no spilling" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
// var function = MockFunction1{
|
||||
// .allocator = allocator,
|
||||
// };
|
||||
// defer function.deinit();
|
||||
var function = MockFunction1{
|
||||
.allocator = allocator,
|
||||
};
|
||||
defer function.deinit();
|
||||
|
||||
// const mock_instruction: Air.Inst.Index = 1;
|
||||
const mock_instruction: Air.Inst.Index = 1;
|
||||
|
||||
// try expectEqual(@as(?MockRegister1, .r2), function.register_manager.tryAllocReg(mock_instruction));
|
||||
// try expectEqual(@as(?MockRegister1, .r3), function.register_manager.tryAllocReg(mock_instruction));
|
||||
// try expectEqual(@as(?MockRegister1, null), function.register_manager.tryAllocReg(mock_instruction));
|
||||
try expectEqual(@as(?MockRegister1, .r2), function.register_manager.tryAllocReg(mock_instruction));
|
||||
try expectEqual(@as(?MockRegister1, .r3), function.register_manager.tryAllocReg(mock_instruction));
|
||||
try expectEqual(@as(?MockRegister1, null), function.register_manager.tryAllocReg(mock_instruction));
|
||||
|
||||
// try expect(function.register_manager.isRegAllocated(.r2));
|
||||
// try expect(function.register_manager.isRegAllocated(.r3));
|
||||
// try expect(!function.register_manager.isRegFree(.r2));
|
||||
// try expect(!function.register_manager.isRegFree(.r3));
|
||||
try expect(function.register_manager.isRegAllocated(.r2));
|
||||
try expect(function.register_manager.isRegAllocated(.r3));
|
||||
try expect(!function.register_manager.isRegFree(.r2));
|
||||
try expect(!function.register_manager.isRegFree(.r3));
|
||||
|
||||
// function.register_manager.freeReg(.r2);
|
||||
// function.register_manager.freeReg(.r3);
|
||||
function.register_manager.freeReg(.r2);
|
||||
function.register_manager.freeReg(.r3);
|
||||
|
||||
// try expect(function.register_manager.isRegAllocated(.r2));
|
||||
// try expect(function.register_manager.isRegAllocated(.r3));
|
||||
// try expect(function.register_manager.isRegFree(.r2));
|
||||
// try expect(function.register_manager.isRegFree(.r3));
|
||||
//}
|
||||
try expect(function.register_manager.isRegAllocated(.r2));
|
||||
try expect(function.register_manager.isRegAllocated(.r3));
|
||||
try expect(function.register_manager.isRegFree(.r2));
|
||||
try expect(function.register_manager.isRegFree(.r3));
|
||||
}
|
||||
|
||||
//test "allocReg: spilling" {
|
||||
// const allocator = std.testing.allocator;
|
||||
test "allocReg: spilling" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
// var function = MockFunction1{
|
||||
// .allocator = allocator,
|
||||
// };
|
||||
// defer function.deinit();
|
||||
var function = MockFunction1{
|
||||
.allocator = allocator,
|
||||
};
|
||||
defer function.deinit();
|
||||
|
||||
// const mock_instruction: Air.Inst.Index = 1;
|
||||
const mock_instruction: Air.Inst.Index = 1;
|
||||
|
||||
// try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction));
|
||||
// try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction));
|
||||
try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction));
|
||||
try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction));
|
||||
|
||||
// // Spill a register
|
||||
// try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction));
|
||||
// try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items);
|
||||
// Spill a register
|
||||
try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction));
|
||||
try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items);
|
||||
|
||||
// // No spilling necessary
|
||||
// function.register_manager.freeReg(.r3);
|
||||
// try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction));
|
||||
// try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items);
|
||||
// No spilling necessary
|
||||
function.register_manager.freeReg(.r3);
|
||||
try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction));
|
||||
try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items);
|
||||
|
||||
// // Locked registers
|
||||
// function.register_manager.freeReg(.r3);
|
||||
// {
|
||||
// const lock = function.register_manager.lockReg(.r2);
|
||||
// defer if (lock) |reg| function.register_manager.unlockReg(reg);
|
||||
// Locked registers
|
||||
function.register_manager.freeReg(.r3);
|
||||
{
|
||||
const lock = function.register_manager.lockReg(.r2);
|
||||
defer if (lock) |reg| function.register_manager.unlockReg(reg);
|
||||
|
||||
// try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction));
|
||||
// }
|
||||
// try expect(!function.register_manager.lockedRegsExist());
|
||||
//}
|
||||
try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction));
|
||||
}
|
||||
try expect(!function.register_manager.lockedRegsExist());
|
||||
}
|
||||
|
||||
//test "tryAllocRegs" {
|
||||
// const allocator = std.testing.allocator;
|
||||
test "tryAllocRegs" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
// var function = MockFunction2{
|
||||
// .allocator = allocator,
|
||||
// };
|
||||
// defer function.deinit();
|
||||
var function = MockFunction2{
|
||||
.allocator = allocator,
|
||||
};
|
||||
defer function.deinit();
|
||||
|
||||
// try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, function.register_manager.tryAllocRegs(3, .{ null, null, null }).?);
|
||||
try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, function.register_manager.tryAllocRegs(3, .{ null, null, null }).?);
|
||||
|
||||
// try expect(function.register_manager.isRegAllocated(.r0));
|
||||
// try expect(function.register_manager.isRegAllocated(.r1));
|
||||
// try expect(function.register_manager.isRegAllocated(.r2));
|
||||
// try expect(!function.register_manager.isRegAllocated(.r3));
|
||||
try expect(function.register_manager.isRegAllocated(.r0));
|
||||
try expect(function.register_manager.isRegAllocated(.r1));
|
||||
try expect(function.register_manager.isRegAllocated(.r2));
|
||||
try expect(!function.register_manager.isRegAllocated(.r3));
|
||||
|
||||
// // Locked registers
|
||||
// function.register_manager.freeReg(.r0);
|
||||
// function.register_manager.freeReg(.r2);
|
||||
// function.register_manager.freeReg(.r3);
|
||||
// {
|
||||
// const lock = function.register_manager.lockReg(.r1);
|
||||
// defer if (lock) |reg| function.register_manager.unlockReg(reg);
|
||||
// Locked registers
|
||||
function.register_manager.freeReg(.r0);
|
||||
function.register_manager.freeReg(.r2);
|
||||
function.register_manager.freeReg(.r3);
|
||||
{
|
||||
const lock = function.register_manager.lockReg(.r1);
|
||||
defer if (lock) |reg| function.register_manager.unlockReg(reg);
|
||||
|
||||
// try expectEqual([_]MockRegister2{ .r0, .r2, .r3 }, function.register_manager.tryAllocRegs(3, .{ null, null, null }).?);
|
||||
// }
|
||||
// try expect(!function.register_manager.lockedRegsExist());
|
||||
try expectEqual([_]MockRegister2{ .r0, .r2, .r3 }, function.register_manager.tryAllocRegs(3, .{ null, null, null }).?);
|
||||
}
|
||||
try expect(!function.register_manager.lockedRegsExist());
|
||||
|
||||
// try expect(function.register_manager.isRegAllocated(.r0));
|
||||
// try expect(function.register_manager.isRegAllocated(.r1));
|
||||
// try expect(function.register_manager.isRegAllocated(.r2));
|
||||
// try expect(function.register_manager.isRegAllocated(.r3));
|
||||
//}
|
||||
try expect(function.register_manager.isRegAllocated(.r0));
|
||||
try expect(function.register_manager.isRegAllocated(.r1));
|
||||
try expect(function.register_manager.isRegAllocated(.r2));
|
||||
try expect(function.register_manager.isRegAllocated(.r3));
|
||||
}
|
||||
|
||||
//test "allocRegs: normal usage" {
|
||||
// // TODO: convert this into a decltest once that is supported
|
||||
test "allocRegs: normal usage" {
|
||||
// TODO: convert this into a decltest once that is supported
|
||||
|
||||
// const allocator = std.testing.allocator;
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
// var function = MockFunction2{
|
||||
// .allocator = allocator,
|
||||
// };
|
||||
// defer function.deinit();
|
||||
var function = MockFunction2{
|
||||
.allocator = allocator,
|
||||
};
|
||||
defer function.deinit();
|
||||
|
||||
// {
|
||||
// const result_reg: MockRegister2 = .r1;
|
||||
{
|
||||
const result_reg: MockRegister2 = .r1;
|
||||
|
||||
// // The result register is known and fixed at this point, we
|
||||
// // don't want to accidentally allocate lhs or rhs to the
|
||||
// // result register, this is why we lock it.
|
||||
// //
|
||||
// // Using defer unlock right after lock is a good idea in
|
||||
// // most cases as you probably are using the locked registers
|
||||
// // in the remainder of this scope and don't need to use it
|
||||
// // after the end of this scope. However, in some situations,
|
||||
// // it may make sense to manually unlock registers before the
|
||||
// // end of the scope when you are certain that they don't
|
||||
// // contain any valuable data anymore and can be reused. For an
|
||||
// // example of that, see `selectively reducing register
|
||||
// // pressure`.
|
||||
// const lock = function.register_manager.lockReg(result_reg);
|
||||
// defer if (lock) |reg| function.register_manager.unlockReg(reg);
|
||||
// The result register is known and fixed at this point, we
|
||||
// don't want to accidentally allocate lhs or rhs to the
|
||||
// result register, this is why we lock it.
|
||||
//
|
||||
// Using defer unlock right after lock is a good idea in
|
||||
// most cases as you probably are using the locked registers
|
||||
// in the remainder of this scope and don't need to use it
|
||||
// after the end of this scope. However, in some situations,
|
||||
// it may make sense to manually unlock registers before the
|
||||
// end of the scope when you are certain that they don't
|
||||
// contain any valuable data anymore and can be reused. For an
|
||||
// example of that, see `selectively reducing register
|
||||
// pressure`.
|
||||
const lock = function.register_manager.lockReg(result_reg);
|
||||
defer if (lock) |reg| function.register_manager.unlockReg(reg);
|
||||
|
||||
// const regs = try function.register_manager.allocRegs(2, .{ null, null });
|
||||
// try function.genAdd(result_reg, regs[0], regs[1]);
|
||||
// }
|
||||
//}
|
||||
const regs = try function.register_manager.allocRegs(2, .{ null, null });
|
||||
try function.genAdd(result_reg, regs[0], regs[1]);
|
||||
}
|
||||
}
|
||||
|
||||
//test "allocRegs: selectively reducing register pressure" {
|
||||
// // TODO: convert this into a decltest once that is supported
|
||||
test "allocRegs: selectively reducing register pressure" {
|
||||
// TODO: convert this into a decltest once that is supported
|
||||
|
||||
// const allocator = std.testing.allocator;
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
// var function = MockFunction2{
|
||||
// .allocator = allocator,
|
||||
// };
|
||||
// defer function.deinit();
|
||||
var function = MockFunction2{
|
||||
.allocator = allocator,
|
||||
};
|
||||
defer function.deinit();
|
||||
|
||||
// {
|
||||
// const result_reg: MockRegister2 = .r1;
|
||||
{
|
||||
const result_reg: MockRegister2 = .r1;
|
||||
|
||||
// const lock = function.register_manager.lockReg(result_reg);
|
||||
const lock = function.register_manager.lockReg(result_reg);
|
||||
|
||||
// // Here, we don't defer unlock because we manually unlock
|
||||
// // after genAdd
|
||||
// const regs = try function.register_manager.allocRegs(2, .{ null, null });
|
||||
// Here, we don't defer unlock because we manually unlock
|
||||
// after genAdd
|
||||
const regs = try function.register_manager.allocRegs(2, .{ null, null });
|
||||
|
||||
// try function.genAdd(result_reg, regs[0], regs[1]);
|
||||
// function.register_manager.unlockReg(lock.?);
|
||||
try function.genAdd(result_reg, regs[0], regs[1]);
|
||||
function.register_manager.unlockReg(lock.?);
|
||||
|
||||
// const extra_summand_reg = try function.register_manager.allocReg(null);
|
||||
// try function.genAdd(result_reg, result_reg, extra_summand_reg);
|
||||
// }
|
||||
//}
|
||||
const extra_summand_reg = try function.register_manager.allocReg(null);
|
||||
try function.genAdd(result_reg, result_reg, extra_summand_reg);
|
||||
}
|
||||
}
|
||||
|
||||
//test "getReg" {
|
||||
// const allocator = std.testing.allocator;
|
||||
test "getReg" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
// var function = MockFunction1{
|
||||
// .allocator = allocator,
|
||||
// };
|
||||
// defer function.deinit();
|
||||
var function = MockFunction1{
|
||||
.allocator = allocator,
|
||||
};
|
||||
defer function.deinit();
|
||||
|
||||
// const mock_instruction: Air.Inst.Index = 1;
|
||||
const mock_instruction: Air.Inst.Index = 1;
|
||||
|
||||
// try function.register_manager.getReg(.r3, mock_instruction);
|
||||
try function.register_manager.getReg(.r3, mock_instruction);
|
||||
|
||||
// try expect(!function.register_manager.isRegAllocated(.r2));
|
||||
// try expect(function.register_manager.isRegAllocated(.r3));
|
||||
// try expect(function.register_manager.isRegFree(.r2));
|
||||
// try expect(!function.register_manager.isRegFree(.r3));
|
||||
try expect(!function.register_manager.isRegAllocated(.r2));
|
||||
try expect(function.register_manager.isRegAllocated(.r3));
|
||||
try expect(function.register_manager.isRegFree(.r2));
|
||||
try expect(!function.register_manager.isRegFree(.r3));
|
||||
|
||||
// // Spill r3
|
||||
// try function.register_manager.getReg(.r3, mock_instruction);
|
||||
// Spill r3
|
||||
try function.register_manager.getReg(.r3, mock_instruction);
|
||||
|
||||
// try expect(!function.register_manager.isRegAllocated(.r2));
|
||||
// try expect(function.register_manager.isRegAllocated(.r3));
|
||||
// try expect(function.register_manager.isRegFree(.r2));
|
||||
// try expect(!function.register_manager.isRegFree(.r3));
|
||||
// try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r3}, function.spilled.items);
|
||||
//}
|
||||
try expect(!function.register_manager.isRegAllocated(.r2));
|
||||
try expect(function.register_manager.isRegAllocated(.r3));
|
||||
try expect(function.register_manager.isRegFree(.r2));
|
||||
try expect(!function.register_manager.isRegFree(.r3));
|
||||
try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r3}, function.spilled.items);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user