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;
1111const Target = std.Target;
1212const Allocator = mem.Allocator;
1313
14const Backend = @import("backend.zig");
15
1416pub const Result = union(enum) {
1517 /// The `code` parameter passed to `generateSymbol` has the value appended.
1618 appended: void,
......@@ -348,172 +350,182 @@ const Function = struct {
348350 }
349351 }
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 {
352354 switch (arch) {
353 .x86_64 => switch (reg) {
354 .rax => switch (mcv) {
355 .none, .unreach => unreachable,
356 .immediate => |x| {
357 // Setting the eax register zeroes the upper part of rax, so if the number is small
358 // enough, that is preferable.
359 // Best case: zero
360 // 31 c0 xor eax,eax
361 if (x == 0) {
362 return self.code.appendSlice(&[_]u8{ 0x31, 0xc0 });
355 .x86_64 => switch (mcv) {
356 .none, .unreach => unreachable,
357 .immediate => |x| {
358 if (reg.size() != 64) {
359 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
360 }
361 // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit
362 // register is the fastest way to zero a register.
363 if (x == 0) {
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 });
363389 }
364 // Next best case: set eax with 4 bytes
365 // b8 04 03 02 01 mov eax,0x01020304
366 if (x <= std.math.maxInt(u32)) {
390 }
391 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 {
367404 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;
372405 }
373 // Worst case: set rax with 8 bytes
374 // 48 b8 08 07 06 05 04 03 02 01 movabs rax,0x0102030405060708
375 try self.code.resize(self.code.items.len + 10);
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);
406 self.code.items[self.code.items.len - 5] = 0xB8 | @intCast(u8, @truncate(u3, reg.id()));
407 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
408 mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
380409 return;
381 },
382 .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rax = embedded_in_code", .{}),
383 .register => return self.fail(src, "TODO implement x86_64 genSetReg %rax = register", .{}),
384 .memory => return self.fail(src, "TODO implement x86_64 genSetReg %rax = memory", .{}),
410 }
411 // Worst case: we need to load the 64-bit register with the IMM. GNU's assemblers calls
412 // this `movabs`, though this is officially just a different variant of the plain `mov`
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);
385428 },
386 .rdx => switch (mcv) {
387 .none, .unreach => unreachable,
388 .immediate => |x| {
389 // Setting the edx register zeroes the upper part of rdx, so if the number is small
390 // enough, that is preferable.
391 // Best case: zero
392 // 31 d2 xor edx,edx
393 if (x == 0) {
394 return self.code.appendSlice(&[_]u8{ 0x31, 0xd2 });
395 }
396 // Next best case: set edx with 4 bytes
397 // ba 04 03 02 01 mov edx,0x1020304
398 if (x <= std.math.maxInt(u32)) {
399 try self.code.resize(self.code.items.len + 5);
400 self.code.items[self.code.items.len - 5] = 0xba;
401 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
402 mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
403 return;
404 }
405 // Worst case: set rdx with 8 bytes
406 // 48 ba 08 07 06 05 04 03 02 01 movabs rdx,0x0102030405060708
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", .{}),
429 .embedded_in_code => |code_offset| {
430 if (reg.size() != 64) {
431 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
432 }
433 // We need the offset from RIP in a signed i32 twos complement.
434 // The instruction is 7 bytes long and RIP points to the next instruction.
435 //
436 // 64-bit LEA is encoded as REX.W 8D /r. If the register is extended, the REX byte is modified,
437 // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three
438 // bits as five.
439 // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id.
440 try self.code.resize(self.code.items.len + 7);
441 const REX = 0x48 | if (reg.isExtended()) @as(u8, 1) else 0;
442 const rip = self.code.items.len;
443 const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip);
444 const offset = @intCast(i32, big_offset);
445 self.code.items[self.code.items.len - 7] = REX;
446 self.code.items[self.code.items.len - 6] = 0x8D;
447 self.code.items[self.code.items.len - 5] = 0x5 | (@intCast(u8, @truncate(u3, reg.id())) << 3);
448 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
449 mem.writeIntLittle(i32, imm_ptr, offset);
417450 },
418 .rdi => switch (mcv) {
419 .none, .unreach => unreachable,
420 .immediate => |x| {
421 // Setting the edi register zeroes the upper part of rdi, so if the number is small
422 // enough, that is preferable.
423 // Best case: zero
424 // 31 ff xor edi,edi
425 if (x == 0) {
426 return self.code.appendSlice(&[_]u8{ 0x31, 0xff });
427 }
428 // Next best case: set edi with 4 bytes
429 // bf 04 03 02 01 mov edi,0x1020304
430 if (x <= std.math.maxInt(u32)) {
431 try self.code.resize(self.code.items.len + 5);
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", .{}),
451 .register => |r| {
452 if (reg.size() != 64) {
453 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
454 }
455 const src_reg = @intToEnum(Reg(arch), @intCast(u8, r));
456 // This is a varient of 8B /r. Since we're using 64-bit moves, we require a REX.
457 // This is thus three bytes: REX 0x8B R/M.
458 // If the destination is extended, the R field must be 1.
459 // If the *source* is extended, the B field must be 1.
460 // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle
461 // three bits) contain the destination, and the R/M field (the lower three bits) contain the source.
462 const REX = 0x48 | (if (reg.isExtended()) @as(u8, 4) else 0) | (if (src_reg.isExtended()) @as(u8, 1) else 0);
463 const R = 0xC0 | (@intCast(u8, @truncate(u3, reg.id())) << 3) | @truncate(u3, src_reg.id());
464 try self.code.appendSlice(&[_]u8{ REX, 0x8B, R });
449465 },
450 .rsi => switch (mcv) {
451 .none, .unreach => unreachable,
452 .immediate => |x| {
453 // Setting the edi register zeroes the upper part of rdi, so if the number is small
454 // enough, that is preferable.
455 // Best case: zero
456 // 31 f6 xor esi,esi
457 if (x == 0) {
458 return self.code.appendSlice(&[_]u8{ 0x31, 0xf6 });
459 }
460 // Next best case: set esi with 4 bytes
461 // be 40 30 20 10 mov esi,0x10203040
462 if (x <= std.math.maxInt(u32)) {
463 try self.code.resize(self.code.items.len + 5);
464 self.code.items[self.code.items.len - 5] = 0xbe;
465 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
466 mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
467 return;
468 }
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;
466 .memory => |x| {
467 if (reg.size() != 64) {
468 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
469 }
470 if (x <= std.math.maxInt(u32)) {
471 // Moving from memory to a register is a variant of `8B /r`.
472 // Since we're using 64-bit moves, we require a REX.
473 // This variant also requires a SIB, as it would otherwise be RIP-relative.
474 // We want mode zero with the lower three bits set to four to indicate an SIB with no other displacement.
475 // The SIB must be 0x25, to indicate a disp32 with no scaled index.
476 // 0b00RRR100, where RRR is the lower three bits of the register ID.
477 // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32.
478 try self.code.resize(self.code.items.len + 8);
479 const REX = 0x48 | if (reg.isExtended()) @as(u8, 1) else 0;
480 const r = 0x04 | (@intCast(u8, @truncate(u3, reg.id())) << 3);
481 self.code.items[self.code.items.len - 8] = REX;
482 self.code.items[self.code.items.len - 7] = 0x8B;
483 self.code.items[self.code.items.len - 6] = r;
484 self.code.items[self.code.items.len - 5] = 0x25;
495485 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
496 mem.writeIntLittle(i32, imm_ptr, offset);
497 return;
498 },
499 .register => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = register", .{}),
500 .memory => |x| {
501 if (x <= std.math.maxInt(u32)) {
502 // 48 8b 34 25 40 30 20 10 mov rsi,QWORD PTR ds:0x10203040
503 try self.code.resize(self.code.items.len + 8);
504 self.code.items[self.code.items.len - 8] = 0x48;
505 self.code.items[self.code.items.len - 7] = 0x8b;
506 self.code.items[self.code.items.len - 6] = 0x34;
507 self.code.items[self.code.items.len - 5] = 0x25;
508 const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
509 mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
510 return;
486 mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
487 } else {
488 // If this is RAX, we can use a direct load; otherwise, we need to load the address, then indirectly load
489 // the value.
490 if (reg.id() == 0) {
491 // REX.W 0xA1 moffs64*
492 // moffs64* is a 64-bit offset "relative to segment base", which really just means the
493 // absolute address for all practical purposes.
494 try self.code.resize(self.code.items.len + 10);
495 // REX.W == 0x48
496 self.code.items[self.code.items.len - 10] = 0x48;
497 self.code.items[self.code.items.len - 9] = 0xA1;
498 const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
499 mem.writeIntLittle(u64, imm_ptr, x);
511500 } 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 });
513526 }
514 },
527 }
515528 },
516 else => return self.fail(src, "TODO implement genSetReg for x86_64 '{}'", .{@tagName(reg)}),
517529 },
518530 else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}),
519531 }
......@@ -579,113 +591,8 @@ const Function = struct {
579591
580592fn Reg(comptime arch: Target.Cpu.Arch) type {
581593 return switch (arch) {
582 .i386 => enum {
583 eax,
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 },
594 .i386 => Backend.x86.Register,
595 .x86_64 => Backend.x86_64.Register,
689596 else => @compileError("TODO add more register enums"),
690597 };
691598}