authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-29 02:10:35-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-29 02:29:37-07:00
log606f157a6b6001b2623d28275a892c1a8ee3a646
tree1c2f992a3cd091bdf92ab8407bff8ca1d206ab82
parent1bbfa36b76271e907cac88e83cec8dee1e3d69f7

stage2: register-aliasing-aware codegen

* unify duplicated register allocation codepath * support the x86_64 concept of register aliasing * slightly improved memset codegen, supports sizes 1, 2, 4, 8

3 files changed, 153 insertions(+), 62 deletions(-)

src-self-hosted/codegen.zig+99-62
......@@ -328,6 +328,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
328328 self.free_registers |= @as(FreeRegInt, 1) << shift;
329329 }
330330
331 /// Before calling, must ensureCapacity + 1 on branch.registers.
332 /// Returns `null` if all registers are allocated.
333 fn allocReg(self: *Branch, inst: *ir.Inst) ?Register {
334 const free_index = @ctz(FreeRegInt, self.free_registers);
335 if (free_index >= callee_preserved_regs.len) {
336 return null;
337 }
338 self.free_registers &= ~(@as(FreeRegInt, 1) << free_index);
339 const reg = callee_preserved_regs[free_index];
340 self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst });
341 return reg;
342 }
343
331344 fn deinit(self: *Branch, gpa: *Allocator) void {
332345 self.inst_table.deinit(gpa);
333346 self.registers.deinit(gpa);
......@@ -502,8 +515,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
502515 entry.value = .dead;
503516 switch (prev_value) {
504517 .register => |reg| {
505 _ = branch.registers.remove(reg);
506 branch.markRegFree(reg);
518 const reg64 = reg.to64();
519 _ = branch.registers.remove(reg64);
520 branch.markRegFree(reg64);
507521 },
508522 else => {}, // TODO process stack allocation death
509523 }
......@@ -582,30 +596,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
582596 self.stack_align = abi_align;
583597 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
584598
585 // TODO Make sure the type can fit in a register before we try to allocate one.
586 const free_index = @ctz(FreeRegInt, branch.free_registers);
587 if (free_index >= callee_preserved_regs.len) {
588 const stack_offset = try self.allocMem(inst, abi_size, abi_align);
589 return MCValue{ .stack_offset = stack_offset };
599 // Make sure the type can fit in a register before we try to allocate one.
600 const ptr_bits = arch.ptrBitWidth();
601 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
602 if (abi_size <= ptr_bytes) {
603 try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
604 if (branch.allocReg(inst)) |reg| {
605 return MCValue{ .register = registerAlias(reg, abi_size) };
606 }
590607 }
591 branch.free_registers &= ~(@as(FreeRegInt, 1) << free_index);
592 const reg = callee_preserved_regs[free_index];
593 try branch.registers.putNoClobber(self.gpa, reg, .{ .inst = inst });
594 return MCValue{ .register = reg };
608 const stack_offset = try self.allocMem(inst, abi_size, abi_align);
609 return MCValue{ .stack_offset = stack_offset };
595610 }
596611
597612 /// Does not "move" the instruction.
598613 fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue {
599614 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
600615 try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
601 try branch.inst_table.ensureCapacity(self.gpa, branch.inst_table.items().len + 1);
602616
603 const free_index = @ctz(FreeRegInt, branch.free_registers);
604 if (free_index >= callee_preserved_regs.len)
617 const reg = branch.allocReg(inst) orelse
605618 return self.fail(inst.src, "TODO implement spilling register to stack", .{});
606 branch.free_registers &= ~(@as(FreeRegInt, 1) << free_index);
607 const reg = callee_preserved_regs[free_index];
608 branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst });
609619 const old_mcv = branch.inst_table.get(inst).?;
610620 const new_mcv: MCValue = .{ .register = reg };
611621 try self.genSetReg(inst.src, reg, old_mcv);
......@@ -1131,7 +1141,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
11311141 // test reg, 1
11321142 // TODO detect al, ax, eax
11331143 try self.code.ensureCapacity(self.code.items.len + 4);
1134 self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 });
1144 // TODO audit this codegen: we force w = true here to make
1145 // the value affect the big register
1146 self.rex(.{ .b = reg.isExtended(), .w = true });
11351147 self.code.appendSliceAssumeCapacity(&[_]u8{
11361148 0xf6,
11371149 @as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()),
......@@ -1319,7 +1331,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
13191331 if (!self.wantSafety())
13201332 return; // The already existing value will do just fine.
13211333 // TODO Upgrade this to a memset call when we have that available.
1322 return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaa });
1334 switch (ty.abiSize(self.target.*)) {
1335 1 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaa }),
1336 2 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaa }),
1337 4 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
1338 8 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
1339 else => return self.fail(src, "TODO implement memset", .{}),
1340 }
13231341 },
13241342 .compare_flags_unsigned => |op| {
13251343 return self.fail(src, "TODO implement set stack variable with compare flags value (unsigned)", .{});
......@@ -1328,24 +1346,35 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
13281346 return self.fail(src, "TODO implement set stack variable with compare flags value (signed)", .{});
13291347 },
13301348 .immediate => |x_big| {
1331 if (ty.abiSize(self.target.*) != 4) {
1332 // TODO after fixing this, need to update the undef case above
1333 return self.fail(src, "TODO implement set non 4 abi size stack variable with immediate", .{});
1349 if (stack_offset > 128) {
1350 return self.fail(src, "TODO implement set stack variable with large stack offset", .{});
13341351 }
1335 try self.code.ensureCapacity(self.code.items.len + 7);
1336 if (x_big <= math.maxInt(u32)) {
1337 const x = @intCast(u32, x_big);
1338 if (stack_offset > 128) {
1339 return self.fail(src, "TODO implement set stack variable with large stack offset", .{});
1340 }
1341 // We have a positive stack offset value but we want a twos complement negative
1342 // offset from rbp, which is at the top of the stack frame.
1343 const negative_offset = @intCast(i8, -@intCast(i32, stack_offset));
1344 const twos_comp = @bitCast(u8, negative_offset);
1345 // mov DWORD PTR [rbp+offset], immediate
1346 self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp });
1347 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);
1348 } else {
1352 try self.code.ensureCapacity(self.code.items.len + 8);
1353 switch (ty.abiSize(self.target.*)) {
1354 1 => {
1355 return self.fail(src, "TODO implement set abi_size=1 stack variable with immediate", .{});
1356 },
1357 2 => {
1358 return self.fail(src, "TODO implement set abi_size=2 stack variable with immediate", .{});
1359 },
1360 4 => {
1361 const x = @intCast(u32, x_big);
1362 // We have a positive stack offset value but we want a twos complement negative
1363 // offset from rbp, which is at the top of the stack frame.
1364 const negative_offset = @intCast(i8, -@intCast(i32, stack_offset));
1365 const twos_comp = @bitCast(u8, negative_offset);
1366 // mov DWORD PTR [rbp+offset], immediate
1367 self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp });
1368 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);
1369 },
1370 8 => {
1371 return self.fail(src, "TODO implement set abi_size=8 stack variable with immediate", .{});
1372 },
1373 else => {
1374 return self.fail(src, "TODO implement set abi_size=large stack variable with immediate", .{});
1375 },
1376 }
1377 if (x_big <= math.maxInt(u32)) {} else {
13491378 return self.fail(src, "TODO implement set stack variable with large immediate", .{});
13501379 }
13511380 },
......@@ -1407,7 +1436,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
14071436 },
14081437 .compare_flags_unsigned => |op| {
14091438 try self.code.ensureCapacity(self.code.items.len + 3);
1410 self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 });
1439 // TODO audit this codegen: we force w = true here to make
1440 // the value affect the big register
1441 self.rex(.{ .b = reg.isExtended(), .w = true });
14111442 const opcode: u8 = switch (op) {
14121443 .gte => 0x93,
14131444 .gt => 0x97,
......@@ -1423,9 +1454,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
14231454 return self.fail(src, "TODO set register with compare flags value (signed)", .{});
14241455 },
14251456 .immediate => |x| {
1426 if (reg.size() != 64) {
1427 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
1428 }
14291457 // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit
14301458 // register is the fastest way to zero a register.
14311459 if (x == 0) {
......@@ -1478,16 +1506,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
14781506 //
14791507 // In this case, the encoding of the REX byte is 0b0100100B
14801508 try self.code.ensureCapacity(self.code.items.len + 10);
1481 self.rex(.{ .w = true, .b = reg.isExtended() });
1509 self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() });
14821510 self.code.items.len += 9;
14831511 self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111);
14841512 const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
14851513 mem.writeIntLittle(u64, imm_ptr, x);
14861514 },
14871515 .embedded_in_code => |code_offset| {
1488 if (reg.size() != 64) {
1489 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
1490 }
14911516 // We need the offset from RIP in a signed i32 twos complement.
14921517 // The instruction is 7 bytes long and RIP points to the next instruction.
14931518 try self.code.ensureCapacity(self.code.items.len + 7);
......@@ -1495,7 +1520,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
14951520 // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three
14961521 // bits as five.
14971522 // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id.
1498 self.rex(.{ .w = true, .b = reg.isExtended() });
1523 self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() });
14991524 self.code.items.len += 6;
15001525 const rip = self.code.items.len;
15011526 const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip);
......@@ -1507,12 +1532,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
15071532 },
15081533 .register => |src_reg| {
15091534 // If the registers are the same, nothing to do.
1510 if (src_reg == reg)
1535 if (src_reg.id() == reg.id())
15111536 return;
15121537
1513 if (reg.size() != 64) {
1514 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
1515 }
15161538 // This is a variant of 8B /r. Since we're using 64-bit moves, we require a REX.
15171539 // This is thus three bytes: REX 0x8B R/M.
15181540 // If the destination is extended, the R field must be 1.
......@@ -1520,14 +1542,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
15201542 // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle
15211543 // three bits) contain the destination, and the R/M field (the lower three bits) contain the source.
15221544 try self.code.ensureCapacity(self.code.items.len + 3);
1523 self.rex(.{ .w = true, .r = reg.isExtended(), .b = src_reg.isExtended() });
1545 self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended(), .b = src_reg.isExtended() });
15241546 const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @as(u8, src_reg.id() & 0b111);
15251547 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R });
15261548 },
15271549 .memory => |x| {
1528 if (reg.size() != 64) {
1529 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
1530 }
15311550 if (x <= math.maxInt(u32)) {
15321551 // Moving from memory to a register is a variant of `8B /r`.
15331552 // Since we're using 64-bit moves, we require a REX.
......@@ -1537,7 +1556,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
15371556 // 0b00RRR100, where RRR is the lower three bits of the register ID.
15381557 // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32.
15391558 try self.code.ensureCapacity(self.code.items.len + 8);
1540 self.rex(.{ .w = true, .b = reg.isExtended() });
1559 self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() });
15411560 self.code.appendSliceAssumeCapacity(&[_]u8{
15421561 0x8B,
15431562 0x04 | (@as(u8, reg.id() & 0b111) << 3), // R
......@@ -1580,18 +1599,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
15801599 //
15811600 // Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both*
15821601 // register operands need to be marked as extended.
1583 self.rex(.{ .w = true, .b = reg.isExtended(), .r = reg.isExtended() });
1602 self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended(), .r = reg.isExtended() });
15841603 const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id());
15851604 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM });
15861605 }
15871606 }
15881607 },
15891608 .stack_offset => |off| {
1590 if (reg.size() != 64) {
1591 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
1592 }
15931609 try self.code.ensureCapacity(self.code.items.len + 7);
1594 self.rex(.{ .w = true, .r = reg.isExtended() });
1610 self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() });
15951611 const reg_id: u8 = @truncate(u3, reg.id());
15961612 if (off <= 128) {
15971613 // Example: 48 8b 4d 7f mov rcx,QWORD PTR [rbp+0x7f]
......@@ -1750,11 +1766,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
17501766 for (param_types) |ty, i| {
17511767 switch (ty.zigTypeTag()) {
17521768 .Bool, .Int => {
1769 const param_size = @intCast(u32, ty.abiSize(self.target.*));
17531770 if (next_int_reg >= c_abi_int_param_regs.len) {
17541771 result.args[i] = .{ .stack_offset = next_stack_offset };
1755 next_stack_offset += @intCast(u32, ty.abiSize(self.target.*));
1772 next_stack_offset += param_size;
17561773 } else {
1757 result.args[i] = .{ .register = c_abi_int_param_regs[next_int_reg] };
1774 const aliased_reg = registerAlias(
1775 c_abi_int_param_regs[next_int_reg],
1776 param_size,
1777 );
1778 result.args[i] = .{ .register = aliased_reg };
17581779 next_int_reg += 1;
17591780 }
17601781 },
......@@ -1778,7 +1799,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
17781799 .x86_64 => switch (cc) {
17791800 .Naked => unreachable,
17801801 .Unspecified, .C => {
1781 result.return_value = .{ .register = c_abi_int_return_regs[0] };
1802 const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*));
1803 const aliased_reg = registerAlias(c_abi_int_return_regs[0], ret_ty_size);
1804 result.return_value = .{ .register = aliased_reg };
17821805 },
17831806 else => return self.fail(src, "TODO implement function return values for {}", .{cc}),
17841807 },
......@@ -1825,5 +1848,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
18251848 fn parseRegName(name: []const u8) ?Register {
18261849 return std.meta.stringToEnum(Register, name);
18271850 }
1851
1852 fn registerAlias(reg: Register, size_bytes: u32) Register {
1853 switch (arch) {
1854 // For x86_64 we have to pick a smaller register alias depending on abi size.
1855 .x86_64 => switch (size_bytes) {
1856 1 => return reg.to8(),
1857 2 => return reg.to16(),
1858 4 => return reg.to32(),
1859 8 => return reg.to64(),
1860 else => unreachable,
1861 },
1862 else => return reg,
1863 }
1864 }
18281865 };
18291866}
src-self-hosted/codegen/x86_64.zig+20
......@@ -81,6 +81,26 @@ pub const Register = enum(u8) {
8181 else => null,
8282 };
8383 }
84
85 /// Convert from any register to its 64 bit alias.
86 pub fn to64(self: Register) Register {
87 return @intToEnum(Register, self.id());
88 }
89
90 /// Convert from any register to its 32 bit alias.
91 pub fn to32(self: Register) Register {
92 return @intToEnum(Register, @as(u8, self.id()) + 16);
93 }
94
95 /// Convert from any register to its 16 bit alias.
96 pub fn to16(self: Register) Register {
97 return @intToEnum(Register, @as(u8, self.id()) + 32);
98 }
99
100 /// Convert from any register to its 8 bit alias.
101 pub fn to8(self: Register) Register {
102 return @intToEnum(Register, @as(u8, self.id()) + 48);
103 }
84104};
85105
86106// zig fmt: on
test/stage2/compare_output.zig+34
......@@ -363,5 +363,39 @@ pub fn addCases(ctx: *TestContext) !void {
363363 ,
364364 "",
365365 );
366
367 // Local mutable variables.
368 case.addCompareOutput(
369 \\export fn _start() noreturn {
370 \\ assert(add(3, 4) == 7);
371 \\ assert(add(20, 10) == 30);
372 \\
373 \\ exit();
374 \\}
375 \\
376 \\fn add(a: u32, b: u32) u32 {
377 \\ var x: u32 = undefined;
378 \\ x = 0;
379 \\ x += a;
380 \\ x += b;
381 \\ return x;
382 \\}
383 \\
384 \\pub fn assert(ok: bool) void {
385 \\ if (!ok) unreachable; // assertion failure
386 \\}
387 \\
388 \\fn exit() noreturn {
389 \\ asm volatile ("syscall"
390 \\ :
391 \\ : [number] "{rax}" (231),
392 \\ [arg1] "{rdi}" (0)
393 \\ : "rcx", "r11", "memory"
394 \\ );
395 \\ unreachable;
396 \\}
397 ,
398 "",
399 );
366400 }
367401}