diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2022-05-11 23:18:01 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2022-05-19 19:36:35 +0200 |
| commit | 70d809e0bbc51efa78b54838cc64158b4d4a2dd2 (patch) | |
| tree | c82a66fcb3f530b0054d023efb36a5f6c2269c8a /src | |
| parent | 50a5ddecc54b88cf295fb97b47fecd2a29a1bf8e (diff) | |
| download | zig-70d809e0bbc51efa78b54838cc64158b4d4a2dd2.tar.gz zig-70d809e0bbc51efa78b54838cc64158b4d4a2dd2.zip | |
x64: add AVX registers and Vex prefix sub-encoder
Diffstat (limited to 'src')
| -rw-r--r-- | src/arch/x86_64/bits.zig | 211 |
1 files changed, 209 insertions, 2 deletions
diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index 02f032ab72..df056fab2c 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -8,7 +8,7 @@ const DW = std.dwarf; // zig fmt: off -/// Definitions of all of the x64 registers. The order is semantically meaningful. +/// Definitions of all of the general purpose x64 registers. The order is semantically meaningful. /// The registers are defined such that IDs go in descending order of 64-bit, /// 32-bit, 16-bit, and then 8-bit, and each set contains exactly sixteen /// registers. This results in some useful properties: @@ -126,6 +126,52 @@ 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) u4 { + return switch (@enumToInt(self)) { + 0...15 => 256, + 16...31 => 128, + else => unreachable, + }; + } + + /// 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 @@ -251,6 +297,98 @@ pub const Encoder = struct { self.code.appendAssumeCapacity(0x66); } + pub fn Vex(comptime count: comptime_int) type { + if (count < 2 or count > 3) { + @compileError("VEX prefix can either be 2- or 3-byte long"); + } + + return struct { + bytes: [count]u8 = switch (count) { + 2 => .{ 0xc5, 0xf8 }, + 3 => .{ 0xc4, 0xe1, 0xf8 }, + else => unreachable, + }, + + pub fn rex(self: *@This(), prefix: Rex) void { + const byte = &self.bytes[1]; + if (prefix.w) switch (count) { + 3 => self.bytes[2] &= 0b0111_1111, + else => unreachable, + }; + if (prefix.r) byte.* &= 0b0111_1111; + if (prefix.x) switch (count) { + 3 => byte.* &= 0b1011_1111, + else => unreachable, + }; + if (prefix.b) switch (count) { + 3 => byte.* &= 0b1101_1111, + else => unreachable, + }; + } + + pub fn leading_opcode_0f(self: *@This()) void { + switch (count) { + 3 => self.bytes[1] |= 0b0_0001, + else => {}, + } + } + + pub fn leading_opcode_0f_38(self: *@This()) void { + switch (count) { + 3 => self.bytes[1] |= 0b0_0010, + else => unreachable, + } + } + + pub fn leading_opcode_0f_3a(self: *@This()) void { + switch (count) { + 3 => self.bytes[1] |= 0b0_0011, + else => unreachable, + } + } + + pub fn reg(self: *@This(), register: u4) void { + const byte = &self.bytes[count - 1]; + const mask = 0b1_0000_111; + byte.* &= mask; + byte.* |= @intCast(u7, ~register) << 3; + } + + pub fn len_128(self: *@This()) void { + const byte = &self.bytes[count - 1]; + byte.* &= 0b0_11; + } + + pub fn len_256(self: *@This()) void { + const byte = &self.bytes[count - 1]; + byte.* |= 0b1_00; + } + + pub fn simd_prefix_66(self: *@This()) void { + const byte = &self.bytes[count - 1]; + byte.* |= 0b01; + } + + pub fn simd_prefix_f2(self: *@This()) void { + const byte = &self.bytes[count - 1]; + byte.* |= 0b11; + } + + pub fn simd_prefix_f3(self: *@This()) void { + const byte = &self.bytes[count - 1]; + byte.* |= 0b10; + } + }; + } + + pub fn vex_2byte(self: Self, prefix: Vex(2)) void { + self.code.appendSliceAssumeCapacity(&prefix.bytes); + } + + pub fn vex_3byte(self: Self, prefix: Vex(3)) void { + self.code.appendSliceAssumeCapacity(&prefix.bytes); + } + /// From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB pub const Rex = struct { /// Wide, enables 64-bit operation @@ -543,7 +681,7 @@ pub const Encoder = struct { } }; -test "x86_64 Encoder helpers" { +test "Encoder helpers - general purpose registers" { var code = ArrayList(u8).init(testing.allocator); defer code.deinit(); @@ -615,6 +753,75 @@ test "x86_64 Encoder helpers" { } } +test "Encoder helpers - Vex prefix" { + { + var vex_prefix = Encoder.Vex(2){}; + vex_prefix.rex(.{ + .r = true, + }); + try testing.expectEqualSlices(u8, &[_]u8{ 0xc5, 0x78 }, &vex_prefix.bytes); + } + + { + var vex_prefix = Encoder.Vex(2){}; + vex_prefix.reg(AvxRegister.xmm15.id()); + try testing.expectEqualSlices(u8, &[_]u8{ 0xc5, 0x80 }, &vex_prefix.bytes); + } + + { + var vex_prefix = Encoder.Vex(3){}; + vex_prefix.rex(.{ + .w = true, + .x = true, + }); + try testing.expectEqualSlices(u8, &[_]u8{ 0xc4, 0b101_0_0001, 0b0_1111_0_00 }, &vex_prefix.bytes); + } + + { + var vex_prefix = Encoder.Vex(3){}; + vex_prefix.rex(.{ + .w = true, + .r = true, + }); + vex_prefix.len_256(); + vex_prefix.leading_opcode_0f(); + vex_prefix.simd_prefix_66(); + try testing.expectEqualSlices(u8, &[_]u8{ 0xc4, 0b011_0_0001, 0b0_1111_1_01 }, &vex_prefix.bytes); + } + + var code = ArrayList(u8).init(testing.allocator); + defer code.deinit(); + + { + // vmovapd xmm1, xmm2 + const encoder = try Encoder.init(&code, 4); + var vex = Encoder.Vex(2){}; + vex.simd_prefix_66(); + encoder.vex_2byte(vex); // use 64 bit operation + encoder.opcode_1byte(0x28); + encoder.modRm_direct(0, AvxRegister.xmm1.lowId()); + try testing.expectEqualSlices(u8, &[_]u8{ 0xC5, 0xF9, 0x28, 0xC1 }, code.items); + } + + { + try code.resize(0); + + // vmovhpd xmm13, xmm1, qword ptr [rip] + const encoder = try Encoder.init(&code, 9); + var vex = Encoder.Vex(2){}; + vex.len_128(); + vex.simd_prefix_66(); + vex.leading_opcode_0f(); + vex.rex(.{ .r = true }); + vex.reg(AvxRegister.xmm1.id()); + encoder.vex_2byte(vex); + encoder.opcode_1byte(0x16); + encoder.modRm_RIPDisp32(AvxRegister.xmm13.lowId()); + encoder.disp32(0); + try testing.expectEqualSlices(u8, &[_]u8{ 0xC5, 0x71, 0x16, 0x2D, 0x00, 0x00, 0x00, 0x00 }, code.items); + } +} + // TODO add these registers to the enum and populate dwarfLocOp // // Return Address register. This is stored in `0(%rsp, "")` and is not a physical register. // RA = (16, "RA"), |
