authorgravatar for noam@pixelhero.devNoam Preil <noam@pixelhero.dev> 2020-05-16 18:16:49-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-05-17 12:03:01-04:00
log13ea698a40acb88063e7e6bd633fc2848d3d3a05
treee04243426e57da6a7a7c9b13e3c83d145f2c235f
parent54820a3005f25e1e542d8add39f184ed1e1eddba

rework x64 genSetReg


4 files changed, 251 insertions(+), 261 deletions(-)

src-self-hosted/backend.zig created+2
...@@ -0,0 +1,2 @@
1pub const x86_64 = @import("backend/x86_64.zig");
2pub const x86 = @import("backend/x86.zig");
src-self-hosted/backend/x86.zig created+32
...@@ -0,0 +1,32 @@
1// zig fmt: off
2pub const Register = enum(u8) {
3 // 0 through 7, 32-bit registers. id is int value
4 eax, ecx, edx, ebx, esp, ebp, esi, edi,
5
6 // 8-15, 16-bit registers. id is int value - 8.
7 ax, cx, dx, bx, sp, bp, si, di,
8
9 // 16-23, 8-bit registers. id is int value - 16.
10 al, bl, cl, dl, ah, ch, dh, bh,
11
12 pub fn size(self: @This()) u7 {
13 return switch (@enumToInt(self)) {
14 0...7 => 32,
15 8...15 => 16,
16 16...23 => 8,
17 else => unreachable,
18 };
19 }
20
21 pub fn id(self: @This()) u3 {
22 return @intCast(u4, switch (@enumToInt(self)) {
23 0...7 => |i| i,
24 8...15 => |i| i - 8,
25 16...23 => |i| i - 16,
26 else => unreachable,
27 });
28 }
29
30};
31
32// zig fmt: on
src-self-hosted/backend/x86_64.zig created+49
...@@ -0,0 +1,49 @@
1// zig fmt: off
2pub const Register = enum(u8) {
3 // 0 through 15, 64-bit registers. 8-15 are extended.
4 // id is just the int value.
5 rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi,
6 r8, r9, r10, r11, r12, r13, r14, r15,
7
8 // 16 through 31, 32-bit registers. 24-31 are extended.
9 // id is int value - 16.
10 eax, ecx, edx, ebx, esp, ebp, esi, edi,
11 r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d,
12
13 // 32-47, 16-bit registers. 40-47 are extended.
14 // id is int value - 32.
15 ax, cx, dx, bx, sp, bp, si, di,
16 r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w,
17
18 // 48-63, 8-bit registers. 56-63 are extended.
19 // id is int value - 48.
20 al, bl, cl, dl, ah, ch, dh, bh,
21 r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b,
22
23 pub fn size(self: @This()) u7 {
24 return switch (@enumToInt(self)) {
25 0...15 => 64,
26 16...31 => 32,
27 32...47 => 16,
28 48...64 => 8,
29 else => unreachable,
30 };
31 }
32
33 pub fn isExtended(self: @This()) bool {
34 return @enumToInt(self) & 0x08 != 0;
35 }
36
37 pub fn id(self: @This()) u4 {
38 return @intCast(u4, switch (@enumToInt(self)) {
39 0...15 => |i| i,
40 16...31 => |i| i - 16,
41 32...47 => |i| i - 32,
42 48...64 => |i| i - 48,
43 else => unreachable,
44 });
45 }
46
47};
48
49// zig fmt: on
src-self-hosted/codegen.zig+168-261
...@@ -11,6 +11,8 @@ const ErrorMsg = Module.ErrorMsg;...@@ -11,6 +11,8 @@ const ErrorMsg = Module.ErrorMsg;
11const Target = std.Target;11const Target = std.Target;
12const Allocator = mem.Allocator;12const Allocator = mem.Allocator;
1313
14const Backend = @import("backend.zig");
15
14pub const Result = union(enum) {16pub const Result = union(enum) {
15 /// The `code` parameter passed to `generateSymbol` has the value appended.17 /// The `code` parameter passed to `generateSymbol` has the value appended.
16 appended: void,18 appended: void,
...@@ -348,172 +350,182 @@ const Function = struct {...@@ -348,172 +350,182 @@ const Function = struct {
348 }350 }
349 }351 }
350352
351 fn genSetReg(self: *Function, src: usize, comptime arch: Target.Cpu.Arch, reg: Reg(arch), mcv: MCValue) !void {353 fn genSetReg(self: *Function, src: usize, comptime arch: Target.Cpu.Arch, reg: Reg(arch), mcv: MCValue) error{ CodegenFail, OutOfMemory }!void {
352 switch (arch) {354 switch (arch) {
353 .x86_64 => switch (reg) {355 .x86_64 => switch (mcv) {
354 .rax => switch (mcv) {356 .none, .unreach => unreachable,
355 .none, .unreach => unreachable,357 .immediate => |x| {
356 .immediate => |x| {358 if (reg.size() != 64) {
357 // Setting the eax register zeroes the upper part of rax, so if the number is small359 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
358 // enough, that is preferable.360 }
359 // Best case: zero361 // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit
360 // 31 c0 xor eax,eax362 // register is the fastest way to zero a register.
361 if (x == 0) {363 if (x == 0) {
362 return self.code.appendSlice(&[_]u8{ 0x31, 0xc0 });364 // The encoding for `xor r32, r32` is `0x31 /r`.
365 // Section 3.1.1.1 of the Intel x64 Manual states that "/r indicates that the
366 // ModR/M byte of the instruction contains a register operand and an r/m operand."
367 //
368 // R/M bytes are composed of two bits for the mode, then three bits for the register,
369 // then three bits for the operand. Since we're zeroing a register, the two three-bit
370 // values will be identical, and the mode is three (the raw register value).
371 //
372 if (reg.isExtended()) {
373 // If we're accessing e.g. r8d, we need to use a REX prefix before the actual operation. Since
374 // this is a 32-bit operation, the W flag is set to zero. X is also zero, as we're not using a SIB.
375 // Both R and B are set, as we're extending, in effect, the register bits *and* the operand.
376 //
377 // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. In this case, that's
378 // b01000101, or 0x45.
379 return self.code.appendSlice(&[_]u8{
380 0x45,
381 0x31,
382 0xC0 | (@intCast(u8, @truncate(u3, reg.id())) << 3) | @truncate(u3, reg.id()),
383 });
384 } else {
385 return self.code.appendSlice(&[_]u8{
386 0x31,
387 0xC0 | (@intCast(u8, reg.id()) << 3) | @intCast(u3, reg.id()),
388 });
363 }389 }
364 // Next best case: set eax with 4 bytes390 }
365 // b8 04 03 02 01 mov eax,0x01020304391 if (x <= std.math.maxInt(u32)) {
366 if (x <= std.math.maxInt(u32)) {392 // Next best case: if we set the lower four bytes, the upper four will be zeroed.
393 //
394 // The encoding for `mov IMM32 -> REG` is (0xB8 + R) IMM.
395 if (reg.isExtended()) {
396 // Just as with XORing, we need a REX prefix. This time though, we only
397 // need the B bit set, as we're extending the opcode's register field,
398 // and there is no Mod R/M byte.
399 //
400 // Thus, we need b01000001, or 0x41.
401 try self.code.resize(self.code.items.len + 6);
402 self.code.items[self.code.items.len - 6] = 0x41;
403 } else {
367 try self.code.resize(self.code.items.len + 5);404 try self.code.resize(self.code.items.len + 5);
368 self.code.items[self.code.items.len - 5] = 0xb8;
369 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
370 mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
371 return;
372 }405 }
373 // Worst case: set rax with 8 bytes406 self.code.items[self.code.items.len - 5] = 0xB8 | @intCast(u8, @truncate(u3, reg.id()));
374 // 48 b8 08 07 06 05 04 03 02 01 movabs rax,0x0102030405060708407 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
375 try self.code.resize(self.code.items.len + 10);408 mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
376 self.code.items[self.code.items.len - 10] = 0x48;
377 self.code.items[self.code.items.len - 9] = 0xb8;
378 const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
379 mem.writeIntLittle(u64, imm_ptr, x);
380 return;409 return;
381 },410 }
382 .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rax = embedded_in_code", .{}),411 // Worst case: we need to load the 64-bit register with the IMM. GNU's assemblers calls
383 .register => return self.fail(src, "TODO implement x86_64 genSetReg %rax = register", .{}),412 // this `movabs`, though this is officially just a different variant of the plain `mov`
384 .memory => return self.fail(src, "TODO implement x86_64 genSetReg %rax = memory", .{}),413 // instruction.
414 //
415 // This encoding is, in fact, the *same* as the one used for 32-bit loads. The only
416 // difference is that we set REX.W before the instruction, which extends the load to
417 // 64-bit and uses the full bit-width of the register.
418 //
419 // Since we always need a REX here, let's just check if we also need to set REX.B.
420 //
421 // In this case, the encoding of the REX byte is 0b0100100B
422 const REX = 0x48 | (if (reg.isExtended()) @as(u8, 0x01) else 0);
423 try self.code.resize(self.code.items.len + 10);
424 self.code.items[self.code.items.len - 10] = REX;
425 self.code.items[self.code.items.len - 9] = 0xB8 | @intCast(u8, @truncate(u3, reg.id()));
426 const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
427 mem.writeIntLittle(u64, imm_ptr, x);
385 },428 },
386 .rdx => switch (mcv) {429 .embedded_in_code => |code_offset| {
387 .none, .unreach => unreachable,430 if (reg.size() != 64) {
388 .immediate => |x| {431 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
389 // Setting the edx register zeroes the upper part of rdx, so if the number is small432 }
390 // enough, that is preferable.433 // We need the offset from RIP in a signed i32 twos complement.
391 // Best case: zero434 // The instruction is 7 bytes long and RIP points to the next instruction.
392 // 31 d2 xor edx,edx435 //
393 if (x == 0) {436 // 64-bit LEA is encoded as REX.W 8D /r. If the register is extended, the REX byte is modified,
394 return self.code.appendSlice(&[_]u8{ 0x31, 0xd2 });437 // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three
395 }438 // bits as five.
396 // Next best case: set edx with 4 bytes439 // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id.
397 // ba 04 03 02 01 mov edx,0x1020304440 try self.code.resize(self.code.items.len + 7);
398 if (x <= std.math.maxInt(u32)) {441 const REX = 0x48 | if (reg.isExtended()) @as(u8, 1) else 0;
399 try self.code.resize(self.code.items.len + 5);442 const rip = self.code.items.len;
400 self.code.items[self.code.items.len - 5] = 0xba;443 const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip);
401 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];444 const offset = @intCast(i32, big_offset);
402 mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));445 self.code.items[self.code.items.len - 7] = REX;
403 return;446 self.code.items[self.code.items.len - 6] = 0x8D;
404 }447 self.code.items[self.code.items.len - 5] = 0x5 | (@intCast(u8, @truncate(u3, reg.id())) << 3);
405 // Worst case: set rdx with 8 bytes448 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
406 // 48 ba 08 07 06 05 04 03 02 01 movabs rdx,0x0102030405060708449 mem.writeIntLittle(i32, imm_ptr, offset);
407 try self.code.resize(self.code.items.len + 10);
408 self.code.items[self.code.items.len - 10] = 0x48;
409 self.code.items[self.code.items.len - 9] = 0xba;
410 const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
411 mem.writeIntLittle(u64, imm_ptr, x);
412 return;
413 },
414 .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rdx = embedded_in_code", .{}),
415 .register => return self.fail(src, "TODO implement x86_64 genSetReg %rdx = register", .{}),
416 .memory => return self.fail(src, "TODO implement x86_64 genSetReg %rdx = memory", .{}),
417 },450 },
418 .rdi => switch (mcv) {451 .register => |r| {
419 .none, .unreach => unreachable,452 if (reg.size() != 64) {
420 .immediate => |x| {453 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
421 // Setting the edi register zeroes the upper part of rdi, so if the number is small454 }
422 // enough, that is preferable.455 const src_reg = @intToEnum(Reg(arch), @intCast(u8, r));
423 // Best case: zero456 // This is a varient of 8B /r. Since we're using 64-bit moves, we require a REX.
424 // 31 ff xor edi,edi457 // This is thus three bytes: REX 0x8B R/M.
425 if (x == 0) {458 // If the destination is extended, the R field must be 1.
426 return self.code.appendSlice(&[_]u8{ 0x31, 0xff });459 // If the *source* is extended, the B field must be 1.
427 }460 // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle
428 // Next best case: set edi with 4 bytes461 // three bits) contain the destination, and the R/M field (the lower three bits) contain the source.
429 // bf 04 03 02 01 mov edi,0x1020304462 const REX = 0x48 | (if (reg.isExtended()) @as(u8, 4) else 0) | (if (src_reg.isExtended()) @as(u8, 1) else 0);
430 if (x <= std.math.maxInt(u32)) {463 const R = 0xC0 | (@intCast(u8, @truncate(u3, reg.id())) << 3) | @truncate(u3, src_reg.id());
431 try self.code.resize(self.code.items.len + 5);464 try self.code.appendSlice(&[_]u8{ REX, 0x8B, R });
432 self.code.items[self.code.items.len - 5] = 0xbf;
433 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
434 mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
435 return;
436 }
437 // Worst case: set rdi with 8 bytes
438 // 48 bf 08 07 06 05 04 03 02 01 movabs rax,0x0102030405060708
439 try self.code.resize(self.code.items.len + 10);
440 self.code.items[self.code.items.len - 10] = 0x48;
441 self.code.items[self.code.items.len - 9] = 0xbf;
442 const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
443 mem.writeIntLittle(u64, imm_ptr, x);
444 return;
445 },
446 .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rdi = embedded_in_code", .{}),
447 .register => return self.fail(src, "TODO implement x86_64 genSetReg %rdi = register", .{}),
448 .memory => return self.fail(src, "TODO implement x86_64 genSetReg %rdi = memory", .{}),
449 },465 },
450 .rsi => switch (mcv) {466 .memory => |x| {
451 .none, .unreach => unreachable,467 if (reg.size() != 64) {
452 .immediate => |x| {468 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
453 // Setting the edi register zeroes the upper part of rdi, so if the number is small469 }
454 // enough, that is preferable.470 if (x <= std.math.maxInt(u32)) {
455 // Best case: zero471 // Moving from memory to a register is a variant of `8B /r`.
456 // 31 f6 xor esi,esi472 // Since we're using 64-bit moves, we require a REX.
457 if (x == 0) {473 // This variant also requires a SIB, as it would otherwise be RIP-relative.
458 return self.code.appendSlice(&[_]u8{ 0x31, 0xf6 });474 // We want mode zero with the lower three bits set to four to indicate an SIB with no other displacement.
459 }475 // The SIB must be 0x25, to indicate a disp32 with no scaled index.
460 // Next best case: set esi with 4 bytes476 // 0b00RRR100, where RRR is the lower three bits of the register ID.
461 // be 40 30 20 10 mov esi,0x10203040477 // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32.
462 if (x <= std.math.maxInt(u32)) {478 try self.code.resize(self.code.items.len + 8);
463 try self.code.resize(self.code.items.len + 5);479 const REX = 0x48 | if (reg.isExtended()) @as(u8, 1) else 0;
464 self.code.items[self.code.items.len - 5] = 0xbe;480 const r = 0x04 | (@intCast(u8, @truncate(u3, reg.id())) << 3);
465 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];481 self.code.items[self.code.items.len - 8] = REX;
466 mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));482 self.code.items[self.code.items.len - 7] = 0x8B;
467 return;483 self.code.items[self.code.items.len - 6] = r;
468 }484 self.code.items[self.code.items.len - 5] = 0x25;
469 // Worst case: set rsi with 8 bytes
470 // 48 be 80 70 60 50 40 30 20 10 movabs rsi,0x1020304050607080
471
472 try self.code.resize(self.code.items.len + 10);
473 self.code.items[self.code.items.len - 10] = 0x48;
474 self.code.items[self.code.items.len - 9] = 0xbe;
475 const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
476 mem.writeIntLittle(u64, imm_ptr, x);
477 return;
478 },
479 .embedded_in_code => |code_offset| {
480 // Examples:
481 // lea rsi, [rip + 0x01020304]
482 // lea rsi, [rip - 7]
483 // f: 48 8d 35 04 03 02 01 lea rsi,[rip+0x1020304] # 102031a <_start+0x102031a>
484 // 16: 48 8d 35 f9 ff ff ff lea rsi,[rip+0xfffffffffffffff9] # 16 <_start+0x16>
485 //
486 // We need the offset from RIP in a signed i32 twos complement.
487 // The instruction is 7 bytes long and RIP points to the next instruction.
488 try self.code.resize(self.code.items.len + 7);
489 const rip = self.code.items.len;
490 const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip);
491 const offset = @intCast(i32, big_offset);
492 self.code.items[self.code.items.len - 7] = 0x48;
493 self.code.items[self.code.items.len - 6] = 0x8d;
494 self.code.items[self.code.items.len - 5] = 0x35;
495 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];485 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
496 mem.writeIntLittle(i32, imm_ptr, offset);486 mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
497 return;487 } else {
498 },488 // If this is RAX, we can use a direct load; otherwise, we need to load the address, then indirectly load
499 .register => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = register", .{}),489 // the value.
500 .memory => |x| {490 if (reg.id() == 0) {
501 if (x <= std.math.maxInt(u32)) {491 // REX.W 0xA1 moffs64*
502 // 48 8b 34 25 40 30 20 10 mov rsi,QWORD PTR ds:0x10203040492 // moffs64* is a 64-bit offset "relative to segment base", which really just means the
503 try self.code.resize(self.code.items.len + 8);493 // absolute address for all practical purposes.
504 self.code.items[self.code.items.len - 8] = 0x48;494 try self.code.resize(self.code.items.len + 10);
505 self.code.items[self.code.items.len - 7] = 0x8b;495 // REX.W == 0x48
506 self.code.items[self.code.items.len - 6] = 0x34;496 self.code.items[self.code.items.len - 10] = 0x48;
507 self.code.items[self.code.items.len - 5] = 0x25;497 self.code.items[self.code.items.len - 9] = 0xA1;
508 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];498 const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
509 mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));499 mem.writeIntLittle(u64, imm_ptr, x);
510 return;
511 } else {500 } else {
512 return self.fail(src, "TODO implement genSetReg for x86_64 setting rsi to 64-bit memory", .{});501 // This requires two instructions; a move imm as used above, followed by an indirect load using the register
502 // as the address and the register as the destination.
503 //
504 // This cannot be used if the lower three bits of the id are equal to four or five, as there
505 // is no way to possibly encode it. This means that RSP, RBP, R12, and R13 cannot be used with
506 // this instruction.
507 const id3 = @truncate(u3, reg.id());
508 std.debug.assert(id3 != 4 and id3 != 5);
509
510 // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue.
511 try self.genSetReg(src, arch, reg, MCValue{ .immediate = x });
512
513 // Now, the register contains the address of the value to load into it
514 // Currently, we're only allowing 64-bit registers, so we need the `REX.W 8B /r` variant.
515 // TODO: determine whether to allow other sized registers, and if so, handle them properly.
516 // This operation requires three bytes: REX 0x8B R/M
517 //
518 // For this operation, we want R/M mode *zero* (use register indirectly), and the two register
519 // values must match. Thus, it's 00ABCABC where ABC is the lower three bits of the register ID.
520 //
521 // Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both*
522 // register operands need to be marked as extended.
523 const REX = 0x48 | if (reg.isExtended()) @as(u8, 0b0101) else 0;
524 const RM = (@intCast(u8, @truncate(u3, reg.id())) << 3) | @truncate(u3, reg.id());
525 try self.code.appendSlice(&[_]u8{ REX, 0x8B, RM });
513 }526 }
514 },527 }
515 },528 },
516 else => return self.fail(src, "TODO implement genSetReg for x86_64 '{}'", .{@tagName(reg)}),
517 },529 },
518 else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}),530 else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}),
519 }531 }
...@@ -579,113 +591,8 @@ const Function = struct {...@@ -579,113 +591,8 @@ const Function = struct {
579591
580fn Reg(comptime arch: Target.Cpu.Arch) type {592fn Reg(comptime arch: Target.Cpu.Arch) type {
581 return switch (arch) {593 return switch (arch) {
582 .i386 => enum {594 .i386 => Backend.x86.Register,
583 eax,595 .x86_64 => Backend.x86_64.Register,
584 ebx,
585 ecx,
586 edx,
587 ebp,
588 esp,
589 esi,
590 edi,
591
592 ax,
593 bx,
594 cx,
595 dx,
596 bp,
597 sp,
598 si,
599 di,
600
601 ah,
602 bh,
603 ch,
604 dh,
605
606 al,
607 bl,
608 cl,
609 dl,
610 },
611 .x86_64 => enum {
612 rax,
613 rbx,
614 rcx,
615 rdx,
616 rbp,
617 rsp,
618 rsi,
619 rdi,
620 r8,
621 r9,
622 r10,
623 r11,
624 r12,
625 r13,
626 r14,
627 r15,
628
629 eax,
630 ebx,
631 ecx,
632 edx,
633 ebp,
634 esp,
635 esi,
636 edi,
637 r8d,
638 r9d,
639 r10d,
640 r11d,
641 r12d,
642 r13d,
643 r14d,
644 r15d,
645
646 ax,
647 bx,
648 cx,
649 dx,
650 bp,
651 sp,
652 si,
653 di,
654 r8w,
655 r9w,
656 r10w,
657 r11w,
658 r12w,
659 r13w,
660 r14w,
661 r15w,
662
663 ah,
664 bh,
665 ch,
666 dh,
667 bph,
668 sph,
669 sih,
670 dih,
671
672 al,
673 bl,
674 cl,
675 dl,
676 bpl,
677 spl,
678 sil,
679 dil,
680 r8b,
681 r9b,
682 r10b,
683 r11b,
684 r12b,
685 r13b,
686 r14b,
687 r15b,
688 },
689 else => @compileError("TODO add more register enums"),596 else => @compileError("TODO add more register enums"),
690 };597 };
691}598}