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 {...@@ -328,6 +328,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
328 self.free_registers |= @as(FreeRegInt, 1) << shift;328 self.free_registers |= @as(FreeRegInt, 1) << shift;
329 }329 }
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
331 fn deinit(self: *Branch, gpa: *Allocator) void {344 fn deinit(self: *Branch, gpa: *Allocator) void {
332 self.inst_table.deinit(gpa);345 self.inst_table.deinit(gpa);
333 self.registers.deinit(gpa);346 self.registers.deinit(gpa);
...@@ -502,8 +515,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -502,8 +515,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
502 entry.value = .dead;515 entry.value = .dead;
503 switch (prev_value) {516 switch (prev_value) {
504 .register => |reg| {517 .register => |reg| {
505 _ = branch.registers.remove(reg);518 const reg64 = reg.to64();
506 branch.markRegFree(reg);519 _ = branch.registers.remove(reg64);
520 branch.markRegFree(reg64);
507 },521 },
508 else => {}, // TODO process stack allocation death522 else => {}, // TODO process stack allocation death
509 }523 }
...@@ -582,30 +596,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -582,30 +596,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
582 self.stack_align = abi_align;596 self.stack_align = abi_align;
583 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];597 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.599 // Make sure the type can fit in a register before we try to allocate one.
586 const free_index = @ctz(FreeRegInt, branch.free_registers);600 const ptr_bits = arch.ptrBitWidth();
587 if (free_index >= callee_preserved_regs.len) {601 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
588 const stack_offset = try self.allocMem(inst, abi_size, abi_align);602 if (abi_size <= ptr_bytes) {
589 return MCValue{ .stack_offset = stack_offset };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 }
590 }607 }
591 branch.free_registers &= ~(@as(FreeRegInt, 1) << free_index);608 const stack_offset = try self.allocMem(inst, abi_size, abi_align);
592 const reg = callee_preserved_regs[free_index];609 return MCValue{ .stack_offset = stack_offset };
593 try branch.registers.putNoClobber(self.gpa, reg, .{ .inst = inst });
594 return MCValue{ .register = reg };
595 }610 }
596611
597 /// Does not "move" the instruction.612 /// Does not "move" the instruction.
598 fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue {613 fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue {
599 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];614 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
600 try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);615 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);617 const reg = branch.allocReg(inst) orelse
604 if (free_index >= callee_preserved_regs.len)
605 return self.fail(inst.src, "TODO implement spilling register to stack", .{});618 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 });
609 const old_mcv = branch.inst_table.get(inst).?;619 const old_mcv = branch.inst_table.get(inst).?;
610 const new_mcv: MCValue = .{ .register = reg };620 const new_mcv: MCValue = .{ .register = reg };
611 try self.genSetReg(inst.src, reg, old_mcv);621 try self.genSetReg(inst.src, reg, old_mcv);
...@@ -1131,7 +1141,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1131,7 +1141,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1131 // test reg, 11141 // test reg, 1
1132 // TODO detect al, ax, eax1142 // TODO detect al, ax, eax
1133 try self.code.ensureCapacity(self.code.items.len + 4);1143 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 });
1135 self.code.appendSliceAssumeCapacity(&[_]u8{1147 self.code.appendSliceAssumeCapacity(&[_]u8{
1136 0xf6,1148 0xf6,
1137 @as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()),1149 @as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()),
...@@ -1319,7 +1331,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1319,7 +1331,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1319 if (!self.wantSafety())1331 if (!self.wantSafety())
1320 return; // The already existing value will do just fine.1332 return; // The already existing value will do just fine.
1321 // TODO Upgrade this to a memset call when we have that available.1333 // 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 }
1323 },1341 },
1324 .compare_flags_unsigned => |op| {1342 .compare_flags_unsigned => |op| {
1325 return self.fail(src, "TODO implement set stack variable with compare flags value (unsigned)", .{});1343 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 {...@@ -1328,24 +1346,35 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1328 return self.fail(src, "TODO implement set stack variable with compare flags value (signed)", .{});1346 return self.fail(src, "TODO implement set stack variable with compare flags value (signed)", .{});
1329 },1347 },
1330 .immediate => |x_big| {1348 .immediate => |x_big| {
1331 if (ty.abiSize(self.target.*) != 4) {1349 if (stack_offset > 128) {
1332 // TODO after fixing this, need to update the undef case above1350 return self.fail(src, "TODO implement set stack variable with large stack offset", .{});
1333 return self.fail(src, "TODO implement set non 4 abi size stack variable with immediate", .{});
1334 }1351 }
1335 try self.code.ensureCapacity(self.code.items.len + 7);1352 try self.code.ensureCapacity(self.code.items.len + 8);
1336 if (x_big <= math.maxInt(u32)) {1353 switch (ty.abiSize(self.target.*)) {
1337 const x = @intCast(u32, x_big);1354 1 => {
1338 if (stack_offset > 128) {1355 return self.fail(src, "TODO implement set abi_size=1 stack variable with immediate", .{});
1339 return self.fail(src, "TODO implement set stack variable with large stack offset", .{});1356 },
1340 }1357 2 => {
1341 // We have a positive stack offset value but we want a twos complement negative1358 return self.fail(src, "TODO implement set abi_size=2 stack variable with immediate", .{});
1342 // offset from rbp, which is at the top of the stack frame.1359 },
1343 const negative_offset = @intCast(i8, -@intCast(i32, stack_offset));1360 4 => {
1344 const twos_comp = @bitCast(u8, negative_offset);1361 const x = @intCast(u32, x_big);
1345 // mov DWORD PTR [rbp+offset], immediate1362 // We have a positive stack offset value but we want a twos complement negative
1346 self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp });1363 // offset from rbp, which is at the top of the stack frame.
1347 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);1364 const negative_offset = @intCast(i8, -@intCast(i32, stack_offset));
1348 } else {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 {
1349 return self.fail(src, "TODO implement set stack variable with large immediate", .{});1378 return self.fail(src, "TODO implement set stack variable with large immediate", .{});
1350 }1379 }
1351 },1380 },
...@@ -1407,7 +1436,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1407,7 +1436,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1407 },1436 },
1408 .compare_flags_unsigned => |op| {1437 .compare_flags_unsigned => |op| {
1409 try self.code.ensureCapacity(self.code.items.len + 3);1438 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 });
1411 const opcode: u8 = switch (op) {1442 const opcode: u8 = switch (op) {
1412 .gte => 0x93,1443 .gte => 0x93,
1413 .gt => 0x97,1444 .gt => 0x97,
...@@ -1423,9 +1454,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1423,9 +1454,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1423 return self.fail(src, "TODO set register with compare flags value (signed)", .{});1454 return self.fail(src, "TODO set register with compare flags value (signed)", .{});
1424 },1455 },
1425 .immediate => |x| {1456 .immediate => |x| {
1426 if (reg.size() != 64) {
1427 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
1428 }
1429 // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit1457 // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit
1430 // register is the fastest way to zero a register.1458 // register is the fastest way to zero a register.
1431 if (x == 0) {1459 if (x == 0) {
...@@ -1478,16 +1506,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1478,16 +1506,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1478 //1506 //
1479 // In this case, the encoding of the REX byte is 0b0100100B1507 // In this case, the encoding of the REX byte is 0b0100100B
1480 try self.code.ensureCapacity(self.code.items.len + 10);1508 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() });
1482 self.code.items.len += 9;1510 self.code.items.len += 9;
1483 self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111);1511 self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111);
1484 const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];1512 const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
1485 mem.writeIntLittle(u64, imm_ptr, x);1513 mem.writeIntLittle(u64, imm_ptr, x);
1486 },1514 },
1487 .embedded_in_code => |code_offset| {1515 .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 }
1491 // We need the offset from RIP in a signed i32 twos complement.1516 // We need the offset from RIP in a signed i32 twos complement.
1492 // The instruction is 7 bytes long and RIP points to the next instruction.1517 // The instruction is 7 bytes long and RIP points to the next instruction.
1493 try self.code.ensureCapacity(self.code.items.len + 7);1518 try self.code.ensureCapacity(self.code.items.len + 7);
...@@ -1495,7 +1520,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1495,7 +1520,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1495 // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three1520 // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three
1496 // bits as five.1521 // bits as five.
1497 // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id.1522 // 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() });
1499 self.code.items.len += 6;1524 self.code.items.len += 6;
1500 const rip = self.code.items.len;1525 const rip = self.code.items.len;
1501 const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip);1526 const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip);
...@@ -1507,12 +1532,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1507,12 +1532,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1507 },1532 },
1508 .register => |src_reg| {1533 .register => |src_reg| {
1509 // If the registers are the same, nothing to do.1534 // If the registers are the same, nothing to do.
1510 if (src_reg == reg)1535 if (src_reg.id() == reg.id())
1511 return;1536 return;
15121537
1513 if (reg.size() != 64) {
1514 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
1515 }
1516 // This is a variant of 8B /r. Since we're using 64-bit moves, we require a REX.1538 // This is a variant of 8B /r. Since we're using 64-bit moves, we require a REX.
1517 // This is thus three bytes: REX 0x8B R/M.1539 // This is thus three bytes: REX 0x8B R/M.
1518 // If the destination is extended, the R field must be 1.1540 // If the destination is extended, the R field must be 1.
...@@ -1520,14 +1542,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1520,14 +1542,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1520 // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle1542 // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle
1521 // three bits) contain the destination, and the R/M field (the lower three bits) contain the source.1543 // three bits) contain the destination, and the R/M field (the lower three bits) contain the source.
1522 try self.code.ensureCapacity(self.code.items.len + 3);1544 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() });
1524 const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @as(u8, src_reg.id() & 0b111);1546 const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @as(u8, src_reg.id() & 0b111);
1525 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R });1547 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R });
1526 },1548 },
1527 .memory => |x| {1549 .memory => |x| {
1528 if (reg.size() != 64) {
1529 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
1530 }
1531 if (x <= math.maxInt(u32)) {1550 if (x <= math.maxInt(u32)) {
1532 // Moving from memory to a register is a variant of `8B /r`.1551 // Moving from memory to a register is a variant of `8B /r`.
1533 // Since we're using 64-bit moves, we require a REX.1552 // Since we're using 64-bit moves, we require a REX.
...@@ -1537,7 +1556,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1537,7 +1556,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1537 // 0b00RRR100, where RRR is the lower three bits of the register ID.1556 // 0b00RRR100, where RRR is the lower three bits of the register ID.
1538 // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32.1557 // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32.
1539 try self.code.ensureCapacity(self.code.items.len + 8);1558 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() });
1541 self.code.appendSliceAssumeCapacity(&[_]u8{1560 self.code.appendSliceAssumeCapacity(&[_]u8{
1542 0x8B,1561 0x8B,
1543 0x04 | (@as(u8, reg.id() & 0b111) << 3), // R1562 0x04 | (@as(u8, reg.id() & 0b111) << 3), // R
...@@ -1580,18 +1599,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1580,18 +1599,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1580 //1599 //
1581 // Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both*1600 // Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both*
1582 // register operands need to be marked as extended.1601 // 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() });
1584 const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id());1603 const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id());
1585 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM });1604 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM });
1586 }1605 }
1587 }1606 }
1588 },1607 },
1589 .stack_offset => |off| {1608 .stack_offset => |off| {
1590 if (reg.size() != 64) {
1591 return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
1592 }
1593 try self.code.ensureCapacity(self.code.items.len + 7);1609 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() });
1595 const reg_id: u8 = @truncate(u3, reg.id());1611 const reg_id: u8 = @truncate(u3, reg.id());
1596 if (off <= 128) {1612 if (off <= 128) {
1597 // Example: 48 8b 4d 7f mov rcx,QWORD PTR [rbp+0x7f]1613 // Example: 48 8b 4d 7f mov rcx,QWORD PTR [rbp+0x7f]
...@@ -1750,11 +1766,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1750,11 +1766,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1750 for (param_types) |ty, i| {1766 for (param_types) |ty, i| {
1751 switch (ty.zigTypeTag()) {1767 switch (ty.zigTypeTag()) {
1752 .Bool, .Int => {1768 .Bool, .Int => {
1769 const param_size = @intCast(u32, ty.abiSize(self.target.*));
1753 if (next_int_reg >= c_abi_int_param_regs.len) {1770 if (next_int_reg >= c_abi_int_param_regs.len) {
1754 result.args[i] = .{ .stack_offset = next_stack_offset };1771 result.args[i] = .{ .stack_offset = next_stack_offset };
1755 next_stack_offset += @intCast(u32, ty.abiSize(self.target.*));1772 next_stack_offset += param_size;
1756 } else {1773 } 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 };
1758 next_int_reg += 1;1779 next_int_reg += 1;
1759 }1780 }
1760 },1781 },
...@@ -1778,7 +1799,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1778,7 +1799,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1778 .x86_64 => switch (cc) {1799 .x86_64 => switch (cc) {
1779 .Naked => unreachable,1800 .Naked => unreachable,
1780 .Unspecified, .C => {1801 .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 };
1782 },1805 },
1783 else => return self.fail(src, "TODO implement function return values for {}", .{cc}),1806 else => return self.fail(src, "TODO implement function return values for {}", .{cc}),
1784 },1807 },
...@@ -1825,5 +1848,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1825,5 +1848,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1825 fn parseRegName(name: []const u8) ?Register {1848 fn parseRegName(name: []const u8) ?Register {
1826 return std.meta.stringToEnum(Register, name);1849 return std.meta.stringToEnum(Register, name);
1827 }1850 }
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 }
1828 };1865 };
1829}1866}
src-self-hosted/codegen/x86_64.zig+20
...@@ -81,6 +81,26 @@ pub const Register = enum(u8) {...@@ -81,6 +81,26 @@ pub const Register = enum(u8) {
81 else => null,81 else => null,
82 };82 };
83 }83 }
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 }
84};104};
85105
86// zig fmt: on106// zig fmt: on
test/stage2/compare_output.zig+34
...@@ -363,5 +363,39 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -363,5 +363,39 @@ pub fn addCases(ctx: *TestContext) !void {
363 ,363 ,
364 "",364 "",
365 );365 );
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 );
366 }400 }
367}401}