| author | |
| committer | |
| log | 51717314e4c955f696919aea6f17d758b4b6430f |
| tree | 75cd10021baaa9e07dab727c66a875a5ef60f58b |
| parent | 2d42532fec06d25a1d56b2696e59f52922d6168a |
| parent | c6d46a9b82f04aea439320f0ae8e8a4ed187040e |
| signature | Signed by PGP key 4AEE18F83AFDEB23 |
Add stage2 AArch64 backend5 files changed, 1029 insertions(+), 31 deletions(-)
src/codegen.zig+177-30| ... | ... | @@ -83,9 +83,9 @@ pub fn generateSymbol( |
| 83 | 83 | .wasm64 => unreachable, // has its own code path |
| 84 | 84 | .arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, debug_output), |
| 85 | 85 | .armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, debug_output), |
| 86 | //.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, debug_output), | |
| 87 | //.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, debug_output), | |
| 88 | //.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, debug_output), | |
| 86 | .aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, debug_output), | |
| 87 | .aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, debug_output), | |
| 88 | .aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, debug_output), | |
| 89 | 89 | //.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, debug_output), |
| 90 | 90 | //.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, debug_output), |
| 91 | 91 | //.bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code, debug_output), |
| ... | ... | @@ -1380,6 +1380,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1380 | 1380 | .arm, .armeb => { |
| 1381 | 1381 | writeInt(u32, try self.code.addManyAsArray(4), Instruction.bkpt(0).toU32()); |
| 1382 | 1382 | }, |
| 1383 | .aarch64 => { | |
| 1384 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.brk(1).toU32()); | |
| 1385 | }, | |
| 1383 | 1386 | else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), |
| 1384 | 1387 | } |
| 1385 | 1388 | return .none; |
| ... | ... | @@ -1583,25 +1586,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1583 | 1586 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); |
| 1584 | 1587 | } |
| 1585 | 1588 | }, |
| 1586 | else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), | |
| 1587 | } | |
| 1588 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { | |
| 1589 | switch (arch) { | |
| 1590 | .x86_64 => { | |
| 1589 | .aarch64 => { | |
| 1591 | 1590 | for (info.args) |mc_arg, arg_i| { |
| 1592 | 1591 | const arg = inst.args[arg_i]; |
| 1593 | 1592 | const arg_mcv = try self.resolveInst(inst.args[arg_i]); |
| 1594 | // Here we do not use setRegOrMem even though the logic is similar, because | |
| 1595 | // the function call will move the stack pointer, so the offsets are different. | |
| 1593 | ||
| 1596 | 1594 | switch (mc_arg) { |
| 1597 | 1595 | .none => continue, |
| 1596 | .undef => unreachable, | |
| 1597 | .immediate => unreachable, | |
| 1598 | .unreach => unreachable, | |
| 1599 | .dead => unreachable, | |
| 1600 | .embedded_in_code => unreachable, | |
| 1601 | .memory => unreachable, | |
| 1602 | .compare_flags_signed => unreachable, | |
| 1603 | .compare_flags_unsigned => unreachable, | |
| 1598 | 1604 | .register => |reg| { |
| 1599 | 1605 | try self.genSetReg(arg.src, reg, arg_mcv); |
| 1600 | 1606 | // TODO interact with the register allocator to mark the instruction as moved. |
| 1601 | 1607 | }, |
| 1602 | 1608 | .stack_offset => { |
| 1603 | // Here we need to emit instructions like this: | |
| 1604 | // mov qword ptr [rsp + stack_offset], x | |
| 1605 | 1609 | return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); |
| 1606 | 1610 | }, |
| 1607 | 1611 | .ptr_stack_offset => { |
| ... | ... | @@ -1610,28 +1614,25 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1610 | 1614 | .ptr_embedded_in_code => { |
| 1611 | 1615 | return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{}); |
| 1612 | 1616 | }, |
| 1613 | .undef => unreachable, | |
| 1614 | .immediate => unreachable, | |
| 1615 | .unreach => unreachable, | |
| 1616 | .dead => unreachable, | |
| 1617 | .embedded_in_code => unreachable, | |
| 1618 | .memory => unreachable, | |
| 1619 | .compare_flags_signed => unreachable, | |
| 1620 | .compare_flags_unsigned => unreachable, | |
| 1621 | 1617 | } |
| 1622 | 1618 | } |
| 1623 | 1619 | |
| 1624 | 1620 | if (inst.func.cast(ir.Inst.Constant)) |func_inst| { |
| 1625 | 1621 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { |
| 1626 | 1622 | const func = func_val.func; |
| 1627 | const got = &macho_file.sections.items[macho_file.got_section_index.?]; | |
| 1628 | const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64); | |
| 1629 | // Here, we store the got address in %rax, and then call %rax | |
| 1630 | // movabsq [addr], %rax | |
| 1631 | try self.genSetReg(inst.base.src, .rax, .{ .memory = got_addr }); | |
| 1632 | // callq *%rax | |
| 1633 | try self.code.ensureCapacity(self.code.items.len + 2); | |
| 1634 | self.code.appendSliceAssumeCapacity(&[2]u8{ 0xff, 0xd0 }); | |
| 1623 | const ptr_bits = self.target.cpu.arch.ptrBitWidth(); | |
| 1624 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); | |
| 1625 | const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { | |
| 1626 | const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; | |
| 1627 | break :blk @intCast(u32, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * ptr_bytes); | |
| 1628 | } else if (self.bin_file.cast(link.File.Coff)) |coff_file| | |
| 1629 | coff_file.offset_table_virtual_address + func.owner_decl.link.coff.offset_table_index * ptr_bytes | |
| 1630 | else | |
| 1631 | unreachable; | |
| 1632 | ||
| 1633 | try self.genSetReg(inst.base.src, .x30, .{ .memory = got_addr }); | |
| 1634 | ||
| 1635 | writeInt(u32, try self.code.addManyAsArray(4), Instruction.blr(.x30).toU32()); | |
| 1635 | 1636 | } else { |
| 1636 | 1637 | return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); |
| 1637 | 1638 | } |
| ... | ... | @@ -1639,8 +1640,67 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1639 | 1640 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); |
| 1640 | 1641 | } |
| 1641 | 1642 | }, |
| 1642 | .aarch64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for aarch64 arch", .{}), | |
| 1643 | else => unreachable, | |
| 1643 | else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), | |
| 1644 | } | |
| 1645 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { | |
| 1646 | for (info.args) |mc_arg, arg_i| { | |
| 1647 | const arg = inst.args[arg_i]; | |
| 1648 | const arg_mcv = try self.resolveInst(inst.args[arg_i]); | |
| 1649 | // Here we do not use setRegOrMem even though the logic is similar, because | |
| 1650 | // the function call will move the stack pointer, so the offsets are different. | |
| 1651 | switch (mc_arg) { | |
| 1652 | .none => continue, | |
| 1653 | .register => |reg| { | |
| 1654 | try self.genSetReg(arg.src, reg, arg_mcv); | |
| 1655 | // TODO interact with the register allocator to mark the instruction as moved. | |
| 1656 | }, | |
| 1657 | .stack_offset => { | |
| 1658 | // Here we need to emit instructions like this: | |
| 1659 | // mov qword ptr [rsp + stack_offset], x | |
| 1660 | return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); | |
| 1661 | }, | |
| 1662 | .ptr_stack_offset => { | |
| 1663 | return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{}); | |
| 1664 | }, | |
| 1665 | .ptr_embedded_in_code => { | |
| 1666 | return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{}); | |
| 1667 | }, | |
| 1668 | .undef => unreachable, | |
| 1669 | .immediate => unreachable, | |
| 1670 | .unreach => unreachable, | |
| 1671 | .dead => unreachable, | |
| 1672 | .embedded_in_code => unreachable, | |
| 1673 | .memory => unreachable, | |
| 1674 | .compare_flags_signed => unreachable, | |
| 1675 | .compare_flags_unsigned => unreachable, | |
| 1676 | } | |
| 1677 | } | |
| 1678 | ||
| 1679 | if (inst.func.cast(ir.Inst.Constant)) |func_inst| { | |
| 1680 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { | |
| 1681 | const func = func_val.func; | |
| 1682 | const got = &macho_file.sections.items[macho_file.got_section_index.?]; | |
| 1683 | const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64); | |
| 1684 | switch (arch) { | |
| 1685 | .x86_64 => { | |
| 1686 | // Here, we store the got address in %rax, and then call %rax | |
| 1687 | // movabsq [addr], %rax | |
| 1688 | try self.genSetReg(inst.base.src, .rax, .{ .memory = got_addr }); | |
| 1689 | // callq *%rax | |
| 1690 | try self.code.ensureCapacity(self.code.items.len + 2); | |
| 1691 | self.code.appendSliceAssumeCapacity(&[2]u8{ 0xff, 0xd0 }); | |
| 1692 | }, | |
| 1693 | .aarch64 => { | |
| 1694 | try self.genSetReg(inst.base.src, .x30, .{ .memory = got_addr }); | |
| 1695 | writeInt(u32, try self.code.addManyAsArray(4), Instruction.blr(.x30).toU32()); | |
| 1696 | }, | |
| 1697 | else => unreachable, // unsupported architecture on MachO | |
| 1698 | } | |
| 1699 | } else { | |
| 1700 | return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); | |
| 1701 | } | |
| 1702 | } else { | |
| 1703 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); | |
| 1644 | 1704 | } |
| 1645 | 1705 | } else { |
| 1646 | 1706 | unreachable; |
| ... | ... | @@ -1699,6 +1759,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1699 | 1759 | try self.code.resize(self.code.items.len + 4); |
| 1700 | 1760 | try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4); |
| 1701 | 1761 | }, |
| 1762 | .aarch64 => { | |
| 1763 | // TODO: relocations | |
| 1764 | writeInt(u32, try self.code.addManyAsArray(4), Instruction.ret(null).toU32()); | |
| 1765 | }, | |
| 1702 | 1766 | else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), |
| 1703 | 1767 | } |
| 1704 | 1768 | return .unreach; |
| ... | ... | @@ -2114,6 +2178,47 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2114 | 2178 | return MCValue.none; |
| 2115 | 2179 | } |
| 2116 | 2180 | }, |
| 2181 | .aarch64 => { | |
| 2182 | for (inst.inputs) |input, i| { | |
| 2183 | if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { | |
| 2184 | return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); | |
| 2185 | } | |
| 2186 | const reg_name = input[1 .. input.len - 1]; | |
| 2187 | const reg = parseRegName(reg_name) orelse | |
| 2188 | return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); | |
| 2189 | const arg = try self.resolveInst(inst.args[i]); | |
| 2190 | try self.genSetReg(inst.base.src, reg, arg); | |
| 2191 | } | |
| 2192 | ||
| 2193 | // TODO move this to lib/std/{elf, macho}.zig, etc. | |
| 2194 | const is_syscall_inst = switch (self.bin_file.tag) { | |
| 2195 | .macho => mem.eql(u8, inst.asm_source, "svc #0x80"), | |
| 2196 | .elf => mem.eql(u8, inst.asm_source, "svc #0"), | |
| 2197 | else => |tag| return self.fail(inst.base.src, "TODO implement aarch64 support for other syscall instructions for file format: '{}'", .{tag}), | |
| 2198 | }; | |
| 2199 | if (is_syscall_inst) { | |
| 2200 | const imm16: u16 = switch (self.bin_file.tag) { | |
| 2201 | .macho => 0x80, | |
| 2202 | .elf => 0, | |
| 2203 | else => unreachable, | |
| 2204 | }; | |
| 2205 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.svc(imm16).toU32()); | |
| 2206 | } else { | |
| 2207 | return self.fail(inst.base.src, "TODO implement support for more aarch64 assembly instructions", .{}); | |
| 2208 | } | |
| 2209 | ||
| 2210 | if (inst.output) |output| { | |
| 2211 | if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { | |
| 2212 | return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); | |
| 2213 | } | |
| 2214 | const reg_name = output[2 .. output.len - 1]; | |
| 2215 | const reg = parseRegName(reg_name) orelse | |
| 2216 | return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); | |
| 2217 | return MCValue{ .register = reg }; | |
| 2218 | } else { | |
| 2219 | return MCValue.none; | |
| 2220 | } | |
| 2221 | }, | |
| 2117 | 2222 | .riscv64 => { |
| 2118 | 2223 | for (inst.inputs) |input, i| { |
| 2119 | 2224 | if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { |
| ... | ... | @@ -2448,6 +2553,47 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2448 | 2553 | }, |
| 2449 | 2554 | else => return self.fail(src, "TODO implement getSetReg for arm {}", .{mcv}), |
| 2450 | 2555 | }, |
| 2556 | .aarch64 => switch (mcv) { | |
| 2557 | .dead => unreachable, | |
| 2558 | .ptr_stack_offset => unreachable, | |
| 2559 | .ptr_embedded_in_code => unreachable, | |
| 2560 | .unreach, .none => return, // Nothing to do. | |
| 2561 | .undef => { | |
| 2562 | if (!self.wantSafety()) | |
| 2563 | return; // The already existing value will do just fine. | |
| 2564 | // Write the debug undefined value. | |
| 2565 | switch (reg.size()) { | |
| 2566 | 32 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa }), | |
| 2567 | 64 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }), | |
| 2568 | else => unreachable, // unexpected register size | |
| 2569 | } | |
| 2570 | }, | |
| 2571 | .immediate => |x| { | |
| 2572 | if (x <= math.maxInt(u16)) { | |
| 2573 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @intCast(u16, x), 0).toU32()); | |
| 2574 | } else if (x <= math.maxInt(u32)) { | |
| 2575 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @truncate(u16, x), 0).toU32()); | |
| 2576 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 16), 16).toU32()); | |
| 2577 | } else if (x <= math.maxInt(u32)) { | |
| 2578 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @truncate(u16, x), 0).toU32()); | |
| 2579 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @truncate(u16, x >> 16), 16).toU32()); | |
| 2580 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 32), 32).toU32()); | |
| 2581 | } else { | |
| 2582 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @truncate(u16, x), 0).toU32()); | |
| 2583 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @truncate(u16, x >> 16), 16).toU32()); | |
| 2584 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @truncate(u16, x >> 32), 32).toU32()); | |
| 2585 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 48), 48).toU32()); | |
| 2586 | } | |
| 2587 | }, | |
| 2588 | .register => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), | |
| 2589 | .memory => |addr| { | |
| 2590 | // The value is in memory at a hard-coded address. | |
| 2591 | // If the type is a pointer, it means the pointer address is at this memory location. | |
| 2592 | try self.genSetReg(src, reg, .{ .immediate = addr }); | |
| 2593 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ .rn = reg }).toU32()); | |
| 2594 | }, | |
| 2595 | else => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), | |
| 2596 | }, | |
| 2451 | 2597 | .riscv64 => switch (mcv) { |
| 2452 | 2598 | .dead => unreachable, |
| 2453 | 2599 | .ptr_stack_offset => unreachable, |
| ... | ... | @@ -3007,6 +3153,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3007 | 3153 | .riscv64 => @import("codegen/riscv64.zig"), |
| 3008 | 3154 | .spu_2 => @import("codegen/spu-mk2.zig"), |
| 3009 | 3155 | .arm, .armeb => @import("codegen/arm.zig"), |
| 3156 | .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig"), | |
| 3010 | 3157 | else => struct { |
| 3011 | 3158 | pub const Register = enum { |
| 3012 | 3159 | dummy, |
src/codegen/aarch64.zig created+689| ... | ... | @@ -0,0 +1,689 @@ |
| 1 | const std = @import("std"); | |
| 2 | const DW = std.dwarf; | |
| 3 | const assert = std.debug.assert; | |
| 4 | const testing = std.testing; | |
| 5 | ||
| 6 | // zig fmt: off | |
| 7 | ||
| 8 | /// General purpose registers in the AArch64 instruction set | |
| 9 | pub const Register = enum(u6) { | |
| 10 | // 64-bit registers | |
| 11 | x0, x1, x2, x3, x4, x5, x6, x7, | |
| 12 | x8, x9, x10, x11, x12, x13, x14, x15, | |
| 13 | x16, x17, x18, x19, x20, x21, x22, x23, | |
| 14 | x24, x25, x26, x27, x28, x29, x30, xzr, | |
| 15 | ||
| 16 | // 32-bit registers | |
| 17 | w0, w1, w2, w3, w4, w5, w6, w7, | |
| 18 | w8, w9, w10, w11, w12, w13, w14, w15, | |
| 19 | w16, w17, w18, w19, w20, w21, w22, w23, | |
| 20 | w24, w25, w26, w27, w28, w29, w30, wzr, | |
| 21 | ||
| 22 | pub fn id(self: Register) u5 { | |
| 23 | return @truncate(u5, @enumToInt(self)); | |
| 24 | } | |
| 25 | ||
| 26 | /// Returns the bit-width of the register. | |
| 27 | pub fn size(self: Register) u7 { | |
| 28 | return switch (@enumToInt(self)) { | |
| 29 | 0...31 => 64, | |
| 30 | 32...63 => 32, | |
| 31 | }; | |
| 32 | } | |
| 33 | ||
| 34 | /// Convert from any register to its 64 bit alias. | |
| 35 | pub fn to64(self: Register) Register { | |
| 36 | return @intToEnum(Register, self.id()); | |
| 37 | } | |
| 38 | ||
| 39 | /// Convert from any register to its 32 bit alias. | |
| 40 | pub fn to32(self: Register) Register { | |
| 41 | return @intToEnum(Register, @as(u6, self.id()) + 32); | |
| 42 | } | |
| 43 | ||
| 44 | /// Returns the index into `callee_preserved_regs`. | |
| 45 | pub fn allocIndex(self: Register) ?u4 { | |
| 46 | inline for (callee_preserved_regs) |cpreg, i| { | |
| 47 | if (self.id() == cpreg.id()) return i; | |
| 48 | } | |
| 49 | return null; | |
| 50 | } | |
| 51 | ||
| 52 | pub fn dwarfLocOp(self: Register) u8 { | |
| 53 | return @as(u8, self.id()) + DW.OP_reg0; | |
| 54 | } | |
| 55 | }; | |
| 56 | ||
| 57 | // zig fmt: on | |
| 58 | ||
| 59 | pub const callee_preserved_regs = [_]Register{ | |
| 60 | .x19, .x20, .x21, .x22, .x23, | |
| 61 | .x24, .x25, .x26, .x27, .x28, | |
| 62 | }; | |
| 63 | ||
| 64 | pub const c_abi_int_param_regs = [_]Register{ .x0, .x1, .x2, .x3, .x4, .x5, .x6, .x7 }; | |
| 65 | pub const c_abi_int_return_regs = [_]Register{ .x0, .x1 }; | |
| 66 | ||
| 67 | test "Register.id" { | |
| 68 | testing.expectEqual(@as(u5, 0), Register.x0.id()); | |
| 69 | testing.expectEqual(@as(u5, 0), Register.w0.id()); | |
| 70 | ||
| 71 | testing.expectEqual(@as(u5, 31), Register.xzr.id()); | |
| 72 | testing.expectEqual(@as(u5, 31), Register.wzr.id()); | |
| 73 | } | |
| 74 | ||
| 75 | test "Register.size" { | |
| 76 | testing.expectEqual(@as(u7, 64), Register.x19.size()); | |
| 77 | testing.expectEqual(@as(u7, 32), Register.w3.size()); | |
| 78 | } | |
| 79 | ||
| 80 | test "Register.to64/to32" { | |
| 81 | testing.expectEqual(Register.x0, Register.w0.to64()); | |
| 82 | testing.expectEqual(Register.x0, Register.x0.to64()); | |
| 83 | ||
| 84 | testing.expectEqual(Register.w3, Register.w3.to32()); | |
| 85 | testing.expectEqual(Register.w3, Register.x3.to32()); | |
| 86 | } | |
| 87 | ||
| 88 | // zig fmt: off | |
| 89 | ||
| 90 | /// Scalar floating point registers in the aarch64 instruction set | |
| 91 | pub const FloatingPointRegister = enum(u8) { | |
| 92 | // 128-bit registers | |
| 93 | q0, q1, q2, q3, q4, q5, q6, q7, | |
| 94 | q8, q9, q10, q11, q12, q13, q14, q15, | |
| 95 | q16, q17, q18, q19, q20, q21, q22, q23, | |
| 96 | q24, q25, q26, q27, q28, q29, q30, q31, | |
| 97 | ||
| 98 | // 64-bit registers | |
| 99 | d0, d1, d2, d3, d4, d5, d6, d7, | |
| 100 | d8, d9, d10, d11, d12, d13, d14, d15, | |
| 101 | d16, d17, d18, d19, d20, d21, d22, d23, | |
| 102 | d24, d25, d26, d27, d28, d29, d30, d31, | |
| 103 | ||
| 104 | // 32-bit registers | |
| 105 | s0, s1, s2, s3, s4, s5, s6, s7, | |
| 106 | s8, s9, s10, s11, s12, s13, s14, s15, | |
| 107 | s16, s17, s18, s19, s20, s21, s22, s23, | |
| 108 | s24, s25, s26, s27, s28, s29, s30, s31, | |
| 109 | ||
| 110 | // 16-bit registers | |
| 111 | h0, h1, h2, h3, h4, h5, h6, h7, | |
| 112 | h8, h9, h10, h11, h12, h13, h14, h15, | |
| 113 | h16, h17, h18, h19, h20, h21, h22, h23, | |
| 114 | h24, h25, h26, h27, h28, h29, h30, h31, | |
| 115 | ||
| 116 | // 8-bit registers | |
| 117 | b0, b1, b2, b3, b4, b5, b6, b7, | |
| 118 | b8, b9, b10, b11, b12, b13, b14, b15, | |
| 119 | b16, b17, b18, b19, b20, b21, b22, b23, | |
| 120 | b24, b25, b26, b27, b28, b29, b30, b31, | |
| 121 | ||
| 122 | pub fn id(self: FloatingPointRegister) u5 { | |
| 123 | return @truncate(u5, @enumToInt(self)); | |
| 124 | } | |
| 125 | ||
| 126 | /// Returns the bit-width of the register. | |
| 127 | pub fn size(self: FloatingPointRegister) u8 { | |
| 128 | return switch (@enumToInt(self)) { | |
| 129 | 0...31 => 128, | |
| 130 | 32...63 => 64, | |
| 131 | 64...95 => 32, | |
| 132 | 96...127 => 16, | |
| 133 | 128...159 => 8, | |
| 134 | else => unreachable, | |
| 135 | }; | |
| 136 | } | |
| 137 | ||
| 138 | /// Convert from any register to its 128 bit alias. | |
| 139 | pub fn to128(self: FloatingPointRegister) FloatingPointRegister { | |
| 140 | return @intToEnum(FloatingPointRegister, self.id()); | |
| 141 | } | |
| 142 | ||
| 143 | /// Convert from any register to its 64 bit alias. | |
| 144 | pub fn to64(self: FloatingPointRegister) FloatingPointRegister { | |
| 145 | return @intToEnum(FloatingPointRegister, @as(u8, self.id()) + 32); | |
| 146 | } | |
| 147 | ||
| 148 | /// Convert from any register to its 32 bit alias. | |
| 149 | pub fn to32(self: FloatingPointRegister) FloatingPointRegister { | |
| 150 | return @intToEnum(FloatingPointRegister, @as(u8, self.id()) + 64); | |
| 151 | } | |
| 152 | ||
| 153 | /// Convert from any register to its 16 bit alias. | |
| 154 | pub fn to16(self: FloatingPointRegister) FloatingPointRegister { | |
| 155 | return @intToEnum(FloatingPointRegister, @as(u8, self.id()) + 96); | |
| 156 | } | |
| 157 | ||
| 158 | /// Convert from any register to its 8 bit alias. | |
| 159 | pub fn to8(self: FloatingPointRegister) FloatingPointRegister { | |
| 160 | return @intToEnum(FloatingPointRegister, @as(u8, self.id()) + 128); | |
| 161 | } | |
| 162 | }; | |
| 163 | ||
| 164 | // zig fmt: on | |
| 165 | ||
| 166 | test "FloatingPointRegister.id" { | |
| 167 | testing.expectEqual(@as(u5, 0), FloatingPointRegister.b0.id()); | |
| 168 | testing.expectEqual(@as(u5, 0), FloatingPointRegister.h0.id()); | |
| 169 | testing.expectEqual(@as(u5, 0), FloatingPointRegister.s0.id()); | |
| 170 | testing.expectEqual(@as(u5, 0), FloatingPointRegister.d0.id()); | |
| 171 | testing.expectEqual(@as(u5, 0), FloatingPointRegister.q0.id()); | |
| 172 | ||
| 173 | testing.expectEqual(@as(u5, 2), FloatingPointRegister.q2.id()); | |
| 174 | testing.expectEqual(@as(u5, 31), FloatingPointRegister.d31.id()); | |
| 175 | } | |
| 176 | ||
| 177 | test "FloatingPointRegister.size" { | |
| 178 | testing.expectEqual(@as(u8, 128), FloatingPointRegister.q1.size()); | |
| 179 | testing.expectEqual(@as(u8, 64), FloatingPointRegister.d2.size()); | |
| 180 | testing.expectEqual(@as(u8, 32), FloatingPointRegister.s3.size()); | |
| 181 | testing.expectEqual(@as(u8, 16), FloatingPointRegister.h4.size()); | |
| 182 | testing.expectEqual(@as(u8, 8), FloatingPointRegister.b5.size()); | |
| 183 | } | |
| 184 | ||
| 185 | test "FloatingPointRegister.toX" { | |
| 186 | testing.expectEqual(FloatingPointRegister.q1, FloatingPointRegister.q1.to128()); | |
| 187 | testing.expectEqual(FloatingPointRegister.q2, FloatingPointRegister.b2.to128()); | |
| 188 | testing.expectEqual(FloatingPointRegister.q3, FloatingPointRegister.h3.to128()); | |
| 189 | ||
| 190 | testing.expectEqual(FloatingPointRegister.d0, FloatingPointRegister.q0.to64()); | |
| 191 | testing.expectEqual(FloatingPointRegister.s1, FloatingPointRegister.d1.to32()); | |
| 192 | testing.expectEqual(FloatingPointRegister.h2, FloatingPointRegister.s2.to16()); | |
| 193 | testing.expectEqual(FloatingPointRegister.b3, FloatingPointRegister.h3.to8()); | |
| 194 | } | |
| 195 | ||
| 196 | /// Represents an instruction in the AArch64 instruction set | |
| 197 | pub const Instruction = union(enum) { | |
| 198 | MoveWideImmediate: packed struct { | |
| 199 | rd: u5, | |
| 200 | imm16: u16, | |
| 201 | hw: u2, | |
| 202 | fixed: u6 = 0b100101, | |
| 203 | opc: u2, | |
| 204 | sf: u1, | |
| 205 | }, | |
| 206 | LoadStoreRegister: packed struct { | |
| 207 | rt: u5, | |
| 208 | rn: u5, | |
| 209 | offset: u12, | |
| 210 | opc: u2, | |
| 211 | op1: u2, | |
| 212 | fixed: u4 = 0b111_0, | |
| 213 | size: u2, | |
| 214 | }, | |
| 215 | LoadLiteral: packed struct { | |
| 216 | rt: u5, | |
| 217 | imm19: u19, | |
| 218 | fixed: u6 = 0b011_0_00, | |
| 219 | opc: u2, | |
| 220 | }, | |
| 221 | ExceptionGeneration: packed struct { | |
| 222 | ll: u2, | |
| 223 | op2: u3, | |
| 224 | imm16: u16, | |
| 225 | opc: u3, | |
| 226 | fixed: u8 = 0b1101_0100, | |
| 227 | }, | |
| 228 | UnconditionalBranchRegister: packed struct { | |
| 229 | op4: u5, | |
| 230 | rn: u5, | |
| 231 | op3: u6, | |
| 232 | op2: u5, | |
| 233 | opc: u4, | |
| 234 | fixed: u7 = 0b1101_011, | |
| 235 | }, | |
| 236 | UnconditionalBranchImmediate: packed struct { | |
| 237 | imm26: u26, | |
| 238 | fixed: u5 = 0b00101, | |
| 239 | op: u1, | |
| 240 | }, | |
| 241 | ||
| 242 | pub fn toU32(self: Instruction) u32 { | |
| 243 | return switch (self) { | |
| 244 | .MoveWideImmediate => |v| @bitCast(u32, v), | |
| 245 | .LoadStoreRegister => |v| @bitCast(u32, v), | |
| 246 | .LoadLiteral => |v| @bitCast(u32, v), | |
| 247 | .ExceptionGeneration => |v| @bitCast(u32, v), | |
| 248 | .UnconditionalBranchRegister => |v| @bitCast(u32, v), | |
| 249 | .UnconditionalBranchImmediate => |v| @bitCast(u32, v), | |
| 250 | }; | |
| 251 | } | |
| 252 | ||
| 253 | /// Represents the offset operand of a load or store instruction. | |
| 254 | /// Data can be loaded from memory with either an immediate offset | |
| 255 | /// or an offset that is stored in some register. | |
| 256 | pub const Offset = union(enum) { | |
| 257 | Immediate: union(enum) { | |
| 258 | PostIndex: i9, | |
| 259 | PreIndex: i9, | |
| 260 | Unsigned: u12, | |
| 261 | }, | |
| 262 | Register: struct { | |
| 263 | rm: u5, | |
| 264 | shift: union(enum) { | |
| 265 | Uxtw: u2, | |
| 266 | Lsl: u2, | |
| 267 | Sxtw: u2, | |
| 268 | Sxtx: u2, | |
| 269 | }, | |
| 270 | }, | |
| 271 | ||
| 272 | pub const none = Offset{ | |
| 273 | .Immediate = .{ .Unsigned = 0 }, | |
| 274 | }; | |
| 275 | ||
| 276 | pub fn toU12(self: Offset) u12 { | |
| 277 | return switch (self) { | |
| 278 | .Immediate => |imm_type| switch (imm_type) { | |
| 279 | .PostIndex => |v| (@intCast(u12, @bitCast(u9, v)) << 2) + 1, | |
| 280 | .PreIndex => |v| (@intCast(u12, @bitCast(u9, v)) << 2) + 3, | |
| 281 | .Unsigned => |v| v, | |
| 282 | }, | |
| 283 | .Register => |r| switch (r.shift) { | |
| 284 | .Uxtw => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 16 + 2050, | |
| 285 | .Lsl => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 24 + 2050, | |
| 286 | .Sxtw => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 48 + 2050, | |
| 287 | .Sxtx => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 56 + 2050, | |
| 288 | }, | |
| 289 | }; | |
| 290 | } | |
| 291 | ||
| 292 | pub fn imm(offset: u12) Offset { | |
| 293 | return Offset{ | |
| 294 | .Immediate = .{ .Unsigned = offset }, | |
| 295 | }; | |
| 296 | } | |
| 297 | ||
| 298 | pub fn imm_post_index(offset: i9) Offset { | |
| 299 | return Offset{ | |
| 300 | .Immediate = .{ .PostIndex = offset }, | |
| 301 | }; | |
| 302 | } | |
| 303 | ||
| 304 | pub fn imm_pre_index(offset: i9) Offset { | |
| 305 | return Offset{ | |
| 306 | .Immediate = .{ .PreIndex = offset }, | |
| 307 | }; | |
| 308 | } | |
| 309 | ||
| 310 | pub fn reg(rm: Register) Offset { | |
| 311 | return Offset{ | |
| 312 | .Register = .{ | |
| 313 | .rm = rm.id(), | |
| 314 | .shift = .{ | |
| 315 | .Lsl = 0, | |
| 316 | }, | |
| 317 | }, | |
| 318 | }; | |
| 319 | } | |
| 320 | ||
| 321 | pub fn reg_uxtw(rm: Register, shift: u2) Offset { | |
| 322 | assert(rm.size() == 32 and (shift == 0 or shift == 2)); | |
| 323 | return Offset{ | |
| 324 | .Register = .{ | |
| 325 | .rm = rm.id(), | |
| 326 | .shift = .{ | |
| 327 | .Uxtw = shift, | |
| 328 | }, | |
| 329 | }, | |
| 330 | }; | |
| 331 | } | |
| 332 | ||
| 333 | pub fn reg_lsl(rm: Register, shift: u2) Offset { | |
| 334 | assert(rm.size() == 64 and (shift == 0 or shift == 3)); | |
| 335 | return Offset{ | |
| 336 | .Register = .{ | |
| 337 | .rm = rm.id(), | |
| 338 | .shift = .{ | |
| 339 | .Lsl = shift, | |
| 340 | }, | |
| 341 | }, | |
| 342 | }; | |
| 343 | } | |
| 344 | ||
| 345 | pub fn reg_sxtw(rm: Register, shift: u2) Offset { | |
| 346 | assert(rm.size() == 32 and (shift == 0 or shift == 2)); | |
| 347 | return Offset{ | |
| 348 | .Register = .{ | |
| 349 | .rm = rm.id(), | |
| 350 | .shift = .{ | |
| 351 | .Sxtw = shift, | |
| 352 | }, | |
| 353 | }, | |
| 354 | }; | |
| 355 | } | |
| 356 | ||
| 357 | pub fn reg_sxtx(rm: Register, shift: u2) Offset { | |
| 358 | assert(rm.size() == 64 and (shift == 0 or shift == 3)); | |
| 359 | return Offset{ | |
| 360 | .Register = .{ | |
| 361 | .rm = rm.id(), | |
| 362 | .shift = .{ | |
| 363 | .Sxtx = shift, | |
| 364 | }, | |
| 365 | }, | |
| 366 | }; | |
| 367 | } | |
| 368 | }; | |
| 369 | ||
| 370 | // Helper functions for assembly syntax functions | |
| 371 | ||
| 372 | fn moveWideImmediate( | |
| 373 | opc: u2, | |
| 374 | rd: Register, | |
| 375 | imm16: u16, | |
| 376 | shift: u6, | |
| 377 | ) Instruction { | |
| 378 | switch (rd.size()) { | |
| 379 | 32 => { | |
| 380 | assert(shift % 16 == 0 and shift <= 16); | |
| 381 | return Instruction{ | |
| 382 | .MoveWideImmediate = .{ | |
| 383 | .rd = rd.id(), | |
| 384 | .imm16 = imm16, | |
| 385 | .hw = @intCast(u2, shift / 16), | |
| 386 | .opc = opc, | |
| 387 | .sf = 0, | |
| 388 | }, | |
| 389 | }; | |
| 390 | }, | |
| 391 | 64 => { | |
| 392 | assert(shift % 16 == 0 and shift <= 48); | |
| 393 | return Instruction{ | |
| 394 | .MoveWideImmediate = .{ | |
| 395 | .rd = rd.id(), | |
| 396 | .imm16 = imm16, | |
| 397 | .hw = @intCast(u2, shift / 16), | |
| 398 | .opc = opc, | |
| 399 | .sf = 1, | |
| 400 | }, | |
| 401 | }; | |
| 402 | }, | |
| 403 | else => unreachable, // unexpected register size | |
| 404 | } | |
| 405 | } | |
| 406 | ||
| 407 | fn loadStoreRegister(rt: Register, rn: Register, offset: Offset, load: bool) Instruction { | |
| 408 | const off = offset.toU12(); | |
| 409 | const op1: u2 = blk: { | |
| 410 | switch (offset) { | |
| 411 | .Immediate => |imm| switch (imm) { | |
| 412 | .Unsigned => break :blk 0b01, | |
| 413 | else => {}, | |
| 414 | }, | |
| 415 | else => {}, | |
| 416 | } | |
| 417 | break :blk 0b00; | |
| 418 | }; | |
| 419 | const opc: u2 = if (load) 0b01 else 0b00; | |
| 420 | switch (rt.size()) { | |
| 421 | 32 => { | |
| 422 | return Instruction{ | |
| 423 | .LoadStoreRegister = .{ | |
| 424 | .rt = rt.id(), | |
| 425 | .rn = rn.id(), | |
| 426 | .offset = offset.toU12(), | |
| 427 | .opc = opc, | |
| 428 | .op1 = op1, | |
| 429 | .size = 0b10, | |
| 430 | }, | |
| 431 | }; | |
| 432 | }, | |
| 433 | 64 => { | |
| 434 | return Instruction{ | |
| 435 | .LoadStoreRegister = .{ | |
| 436 | .rt = rt.id(), | |
| 437 | .rn = rn.id(), | |
| 438 | .offset = offset.toU12(), | |
| 439 | .opc = opc, | |
| 440 | .op1 = op1, | |
| 441 | .size = 0b11, | |
| 442 | }, | |
| 443 | }; | |
| 444 | }, | |
| 445 | else => unreachable, // unexpected register size | |
| 446 | } | |
| 447 | } | |
| 448 | ||
| 449 | fn loadLiteral(rt: Register, imm19: u19) Instruction { | |
| 450 | switch (rt.size()) { | |
| 451 | 32 => { | |
| 452 | return Instruction{ | |
| 453 | .LoadLiteral = .{ | |
| 454 | .rt = rt.id(), | |
| 455 | .imm19 = imm19, | |
| 456 | .opc = 0b00, | |
| 457 | }, | |
| 458 | }; | |
| 459 | }, | |
| 460 | 64 => { | |
| 461 | return Instruction{ | |
| 462 | .LoadLiteral = .{ | |
| 463 | .rt = rt.id(), | |
| 464 | .imm19 = imm19, | |
| 465 | .opc = 0b01, | |
| 466 | }, | |
| 467 | }; | |
| 468 | }, | |
| 469 | else => unreachable, // unexpected register size | |
| 470 | } | |
| 471 | } | |
| 472 | ||
| 473 | fn exceptionGeneration( | |
| 474 | opc: u3, | |
| 475 | op2: u3, | |
| 476 | ll: u2, | |
| 477 | imm16: u16, | |
| 478 | ) Instruction { | |
| 479 | return Instruction{ | |
| 480 | .ExceptionGeneration = .{ | |
| 481 | .ll = ll, | |
| 482 | .op2 = op2, | |
| 483 | .imm16 = imm16, | |
| 484 | .opc = opc, | |
| 485 | }, | |
| 486 | }; | |
| 487 | } | |
| 488 | ||
| 489 | fn unconditionalBranchRegister( | |
| 490 | opc: u4, | |
| 491 | op2: u5, | |
| 492 | op3: u6, | |
| 493 | rn: Register, | |
| 494 | op4: u5, | |
| 495 | ) Instruction { | |
| 496 | assert(rn.size() == 64); | |
| 497 | ||
| 498 | return Instruction{ | |
| 499 | .UnconditionalBranchRegister = .{ | |
| 500 | .op4 = op4, | |
| 501 | .rn = rn.id(), | |
| 502 | .op3 = op3, | |
| 503 | .op2 = op2, | |
| 504 | .opc = opc, | |
| 505 | }, | |
| 506 | }; | |
| 507 | } | |
| 508 | ||
| 509 | fn unconditionalBranchImmediate( | |
| 510 | op: u1, | |
| 511 | offset: i28, | |
| 512 | ) Instruction { | |
| 513 | return Instruction{ | |
| 514 | .UnconditionalBranchImmediate = .{ | |
| 515 | .imm26 = @bitCast(u26, @intCast(i26, offset >> 2)), | |
| 516 | .op = op, | |
| 517 | }, | |
| 518 | }; | |
| 519 | } | |
| 520 | ||
| 521 | // Move wide (immediate) | |
| 522 | ||
| 523 | pub fn movn(rd: Register, imm16: u16, shift: u6) Instruction { | |
| 524 | return moveWideImmediate(0b00, rd, imm16, shift); | |
| 525 | } | |
| 526 | ||
| 527 | pub fn movz(rd: Register, imm16: u16, shift: u6) Instruction { | |
| 528 | return moveWideImmediate(0b10, rd, imm16, shift); | |
| 529 | } | |
| 530 | ||
| 531 | pub fn movk(rd: Register, imm16: u16, shift: u6) Instruction { | |
| 532 | return moveWideImmediate(0b11, rd, imm16, shift); | |
| 533 | } | |
| 534 | ||
| 535 | // Load or store register | |
| 536 | ||
| 537 | pub const LdrArgs = struct { | |
| 538 | rn: ?Register = null, | |
| 539 | offset: Offset = Offset.none, | |
| 540 | literal: ?u19 = null, | |
| 541 | }; | |
| 542 | pub fn ldr(rt: Register, args: LdrArgs) Instruction { | |
| 543 | if (args.rn) |rn| { | |
| 544 | return loadStoreRegister(rt, rn, args.offset, true); | |
| 545 | } else { | |
| 546 | return loadLiteral(rt, args.literal.?); | |
| 547 | } | |
| 548 | } | |
| 549 | ||
| 550 | pub const StrArgs = struct { | |
| 551 | offset: Offset = Offset.none, | |
| 552 | }; | |
| 553 | pub fn str(rt: Register, rn: Register, args: StrArgs) Instruction { | |
| 554 | return loadStoreRegister(rt, rn, args.offset, false); | |
| 555 | } | |
| 556 | ||
| 557 | // Exception generation | |
| 558 | ||
| 559 | pub fn svc(imm16: u16) Instruction { | |
| 560 | return exceptionGeneration(0b000, 0b000, 0b01, imm16); | |
| 561 | } | |
| 562 | ||
| 563 | pub fn hvc(imm16: u16) Instruction { | |
| 564 | return exceptionGeneration(0b000, 0b000, 0b10, imm16); | |
| 565 | } | |
| 566 | ||
| 567 | pub fn smc(imm16: u16) Instruction { | |
| 568 | return exceptionGeneration(0b000, 0b000, 0b11, imm16); | |
| 569 | } | |
| 570 | ||
| 571 | pub fn brk(imm16: u16) Instruction { | |
| 572 | return exceptionGeneration(0b001, 0b000, 0b00, imm16); | |
| 573 | } | |
| 574 | ||
| 575 | pub fn hlt(imm16: u16) Instruction { | |
| 576 | return exceptionGeneration(0b010, 0b000, 0b00, imm16); | |
| 577 | } | |
| 578 | ||
| 579 | // Unconditional branch (register) | |
| 580 | ||
| 581 | pub fn br(rn: Register) Instruction { | |
| 582 | return unconditionalBranchRegister(0b0000, 0b11111, 0b000000, rn, 0b00000); | |
| 583 | } | |
| 584 | ||
| 585 | pub fn blr(rn: Register) Instruction { | |
| 586 | return unconditionalBranchRegister(0b0001, 0b11111, 0b000000, rn, 0b00000); | |
| 587 | } | |
| 588 | ||
| 589 | pub fn ret(rn: ?Register) Instruction { | |
| 590 | return unconditionalBranchRegister(0b0010, 0b11111, 0b000000, rn orelse .x30, 0b00000); | |
| 591 | } | |
| 592 | ||
| 593 | // Unconditional branch (immediate) | |
| 594 | ||
| 595 | pub fn b(offset: i28) Instruction { | |
| 596 | return unconditionalBranchImmediate(0, offset); | |
| 597 | } | |
| 598 | ||
| 599 | pub fn bl(offset: i28) Instruction { | |
| 600 | return unconditionalBranchImmediate(1, offset); | |
| 601 | } | |
| 602 | }; | |
| 603 | ||
| 604 | test "" { | |
| 605 | testing.refAllDecls(@This()); | |
| 606 | } | |
| 607 | ||
| 608 | test "serialize instructions" { | |
| 609 | const Testcase = struct { | |
| 610 | inst: Instruction, | |
| 611 | expected: u32, | |
| 612 | }; | |
| 613 | ||
| 614 | const testcases = [_]Testcase{ | |
| 615 | .{ // movz x1 #4 | |
| 616 | .inst = Instruction.movz(.x1, 4, 0), | |
| 617 | .expected = 0b1_10_100101_00_0000000000000100_00001, | |
| 618 | }, | |
| 619 | .{ // movz x1, #4, lsl 16 | |
| 620 | .inst = Instruction.movz(.x1, 4, 16), | |
| 621 | .expected = 0b1_10_100101_01_0000000000000100_00001, | |
| 622 | }, | |
| 623 | .{ // movz x1, #4, lsl 32 | |
| 624 | .inst = Instruction.movz(.x1, 4, 32), | |
| 625 | .expected = 0b1_10_100101_10_0000000000000100_00001, | |
| 626 | }, | |
| 627 | .{ // movz x1, #4, lsl 48 | |
| 628 | .inst = Instruction.movz(.x1, 4, 48), | |
| 629 | .expected = 0b1_10_100101_11_0000000000000100_00001, | |
| 630 | }, | |
| 631 | .{ // movz w1, #4 | |
| 632 | .inst = Instruction.movz(.w1, 4, 0), | |
| 633 | .expected = 0b0_10_100101_00_0000000000000100_00001, | |
| 634 | }, | |
| 635 | .{ // movz w1, #4, lsl 16 | |
| 636 | .inst = Instruction.movz(.w1, 4, 16), | |
| 637 | .expected = 0b0_10_100101_01_0000000000000100_00001, | |
| 638 | }, | |
| 639 | .{ // svc #0 | |
| 640 | .inst = Instruction.svc(0), | |
| 641 | .expected = 0b1101_0100_000_0000000000000000_00001, | |
| 642 | }, | |
| 643 | .{ // svc #0x80 ; typical on Darwin | |
| 644 | .inst = Instruction.svc(0x80), | |
| 645 | .expected = 0b1101_0100_000_0000000010000000_00001, | |
| 646 | }, | |
| 647 | .{ // ret | |
| 648 | .inst = Instruction.ret(null), | |
| 649 | .expected = 0b1101_011_00_10_11111_0000_00_11110_00000, | |
| 650 | }, | |
| 651 | .{ // bl #0x10 | |
| 652 | .inst = Instruction.bl(0x10), | |
| 653 | .expected = 0b1_00101_00_0000_0000_0000_0000_0000_0100, | |
| 654 | }, | |
| 655 | .{ // ldr x2, [x1] | |
| 656 | .inst = Instruction.ldr(.x2, .{ .rn = .x1 }), | |
| 657 | .expected = 0b11_111_0_01_01_000000000000_00001_00010, | |
| 658 | }, | |
| 659 | .{ // ldr x2, [x1, #1]! | |
| 660 | .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.imm_pre_index(1) }), | |
| 661 | .expected = 0b11_111_0_00_01_0_000000001_11_00001_00010, | |
| 662 | }, | |
| 663 | .{ // ldr x2, [x1], #-1 | |
| 664 | .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.imm_post_index(-1) }), | |
| 665 | .expected = 0b11_111_0_00_01_0_111111111_01_00001_00010, | |
| 666 | }, | |
| 667 | .{ // ldr x2, [x1], (x3) | |
| 668 | .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.reg(.x3) }), | |
| 669 | .expected = 0b11_111_0_00_01_1_00011_011_0_10_00001_00010, | |
| 670 | }, | |
| 671 | .{ // ldr x2, label | |
| 672 | .inst = Instruction.ldr(.x2, .{ .literal = 0x1 }), | |
| 673 | .expected = 0b01_011_0_00_0000000000000000001_00010, | |
| 674 | }, | |
| 675 | .{ // str x2, [x1] | |
| 676 | .inst = Instruction.str(.x2, .x1, .{}), | |
| 677 | .expected = 0b11_111_0_01_00_000000000000_00001_00010, | |
| 678 | }, | |
| 679 | .{ // str x2, [x1], (x3) | |
| 680 | .inst = Instruction.str(.x2, .x1, .{ .offset = Instruction.Offset.reg(.x3) }), | |
| 681 | .expected = 0b11_111_0_00_00_1_00011_011_0_10_00001_00010, | |
| 682 | }, | |
| 683 | }; | |
| 684 | ||
| 685 | for (testcases) |case| { | |
| 686 | const actual = case.inst.toU32(); | |
| 687 | testing.expectEqual(case.expected, actual); | |
| 688 | } | |
| 689 | } |
src/type.zig+2-1| ... | ... | @@ -818,7 +818,8 @@ pub const Type = extern union { |
| 818 | 818 | .fn_ccc_void_no_args, // represents machine code; not a pointer |
| 819 | 819 | .function, // represents machine code; not a pointer |
| 820 | 820 | => return switch (target.cpu.arch) { |
| 821 | .arm => 4, | |
| 821 | .arm, .armeb => 4, | |
| 822 | .aarch64, .aarch64_32, .aarch64_be => 4, | |
| 822 | 823 | .riscv64 => 2, |
| 823 | 824 | else => 1, |
| 824 | 825 | }, |
test/stage2/aarch64.zig created+160| ... | ... | @@ -0,0 +1,160 @@ |
| 1 | const std = @import("std"); | |
| 2 | const TestContext = @import("../../src/test.zig").TestContext; | |
| 3 | ||
| 4 | const macos_aarch64 = std.zig.CrossTarget{ | |
| 5 | .cpu_arch = .aarch64, | |
| 6 | .os_tag = .macos, | |
| 7 | }; | |
| 8 | ||
| 9 | const linux_aarch64 = std.zig.CrossTarget{ | |
| 10 | .cpu_arch = .aarch64, | |
| 11 | .os_tag = .linux, | |
| 12 | }; | |
| 13 | ||
| 14 | pub fn addCases(ctx: *TestContext) !void { | |
| 15 | // TODO enable when we add codesigning to the self-hosted linker | |
| 16 | // related to #6971 | |
| 17 | if (false) { | |
| 18 | var case = ctx.exe("hello world with updates", macos_aarch64); | |
| 19 | ||
| 20 | // Regular old hello world | |
| 21 | case.addCompareOutput( | |
| 22 | \\export fn _start() noreturn { | |
| 23 | \\ print(); | |
| 24 | \\ | |
| 25 | \\ exit(); | |
| 26 | \\} | |
| 27 | \\ | |
| 28 | \\fn print() void { | |
| 29 | \\ asm volatile ("svc #0x80" | |
| 30 | \\ : | |
| 31 | \\ : [number] "{x16}" (4), | |
| 32 | \\ [arg1] "{x0}" (1), | |
| 33 | \\ [arg2] "{x1}" (@ptrToInt("Hello, World!\n")), | |
| 34 | \\ [arg3] "{x2}" (14) | |
| 35 | \\ : "memory" | |
| 36 | \\ ); | |
| 37 | \\ return; | |
| 38 | \\} | |
| 39 | \\ | |
| 40 | \\fn exit() noreturn { | |
| 41 | \\ asm volatile ("svc #0x80" | |
| 42 | \\ : | |
| 43 | \\ : [number] "{x16}" (1), | |
| 44 | \\ [arg1] "{x0}" (0) | |
| 45 | \\ : "memory" | |
| 46 | \\ ); | |
| 47 | \\ unreachable; | |
| 48 | \\} | |
| 49 | , | |
| 50 | "Hello, World!\n", | |
| 51 | ); | |
| 52 | // Now change the message only | |
| 53 | case.addCompareOutput( | |
| 54 | \\export fn _start() noreturn { | |
| 55 | \\ print(); | |
| 56 | \\ | |
| 57 | \\ exit(); | |
| 58 | \\} | |
| 59 | \\ | |
| 60 | \\fn print() void { | |
| 61 | \\ asm volatile ("svc #0x80" | |
| 62 | \\ : | |
| 63 | \\ : [number] "{x16}" (4), | |
| 64 | \\ [arg1] "{x0}" (1), | |
| 65 | \\ [arg2] "{x1}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")), | |
| 66 | \\ [arg3] "{x2}" (104) | |
| 67 | \\ : "memory" | |
| 68 | \\ ); | |
| 69 | \\ return; | |
| 70 | \\} | |
| 71 | \\ | |
| 72 | \\fn exit() noreturn { | |
| 73 | \\ asm volatile ("svc #0x80" | |
| 74 | \\ : | |
| 75 | \\ : [number] "{x16}" (1), | |
| 76 | \\ [arg1] "{x0}" (0) | |
| 77 | \\ : "memory" | |
| 78 | \\ ); | |
| 79 | \\ unreachable; | |
| 80 | \\} | |
| 81 | , | |
| 82 | "What is up? This is a longer message that will force the data to be relocated in virtual address space.\n", | |
| 83 | ); | |
| 84 | // Now we print it twice. | |
| 85 | case.addCompareOutput( | |
| 86 | \\export fn _start() noreturn { | |
| 87 | \\ print(); | |
| 88 | \\ print(); | |
| 89 | \\ | |
| 90 | \\ exit(); | |
| 91 | \\} | |
| 92 | \\ | |
| 93 | \\fn print() void { | |
| 94 | \\ asm volatile ("svc #0x80" | |
| 95 | \\ : | |
| 96 | \\ : [number] "{x16}" (4), | |
| 97 | \\ [arg1] "{x0}" (1), | |
| 98 | \\ [arg2] "{x1}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")), | |
| 99 | \\ [arg3] "{x2}" (104) | |
| 100 | \\ : "memory" | |
| 101 | \\ ); | |
| 102 | \\ return; | |
| 103 | \\} | |
| 104 | \\ | |
| 105 | \\fn exit() noreturn { | |
| 106 | \\ asm volatile ("svc #0x80" | |
| 107 | \\ : | |
| 108 | \\ : [number] "{x16}" (1), | |
| 109 | \\ [arg1] "{x0}" (0) | |
| 110 | \\ : "memory" | |
| 111 | \\ ); | |
| 112 | \\ unreachable; | |
| 113 | \\} | |
| 114 | , | |
| 115 | \\What is up? This is a longer message that will force the data to be relocated in virtual address space. | |
| 116 | \\What is up? This is a longer message that will force the data to be relocated in virtual address space. | |
| 117 | \\ | |
| 118 | ); | |
| 119 | } | |
| 120 | ||
| 121 | { | |
| 122 | var case = ctx.exe("hello world", linux_aarch64); | |
| 123 | // Regular old hello world | |
| 124 | case.addCompareOutput( | |
| 125 | \\export fn _start() noreturn { | |
| 126 | \\ print(); | |
| 127 | \\ exit(); | |
| 128 | \\} | |
| 129 | \\ | |
| 130 | \\fn doNothing() void {} | |
| 131 | \\ | |
| 132 | \\fn answer() u64 { | |
| 133 | \\ return 0x1234abcd1234abcd; | |
| 134 | \\} | |
| 135 | \\ | |
| 136 | \\fn print() void { | |
| 137 | \\ asm volatile ("svc #0" | |
| 138 | \\ : | |
| 139 | \\ : [number] "{x8}" (64), | |
| 140 | \\ [arg1] "{x0}" (1), | |
| 141 | \\ [arg2] "{x1}" (@ptrToInt("Hello, World!\n")), | |
| 142 | \\ [arg3] "{x2}" ("Hello, World!\n".len) | |
| 143 | \\ : "memory", "cc" | |
| 144 | \\ ); | |
| 145 | \\} | |
| 146 | \\ | |
| 147 | \\fn exit() noreturn { | |
| 148 | \\ asm volatile ("svc #0" | |
| 149 | \\ : | |
| 150 | \\ : [number] "{x8}" (93), | |
| 151 | \\ [arg1] "{x0}" (0) | |
| 152 | \\ : "memory", "cc" | |
| 153 | \\ ); | |
| 154 | \\ unreachable; | |
| 155 | \\} | |
| 156 | , | |
| 157 | "Hello, World!\n", | |
| 158 | ); | |
| 159 | } | |
| 160 | } |
test/stage2/test.zig+1| ... | ... | @@ -31,6 +31,7 @@ pub fn addCases(ctx: *TestContext) !void { |
| 31 | 31 | try @import("cbe.zig").addCases(ctx); |
| 32 | 32 | try @import("spu-ii.zig").addCases(ctx); |
| 33 | 33 | try @import("arm.zig").addCases(ctx); |
| 34 | try @import("aarch64.zig").addCases(ctx); | |
| 34 | 35 | |
| 35 | 36 | { |
| 36 | 37 | var case = ctx.exe("hello world with updates", linux_x64); |