| author | |
| committer | |
| log | 51717314e4c955f696919aea6f17d758b4b6430f |
| tree | 75cd10021baaa9e07dab727c66a875a5ef60f58b |
| parent | 2d42532fec06d25a1d56b2696e59f52922d6168a |
| parent | c6d46a9b82f04aea439320f0ae8e8a4ed187040e |
| signature |
Add stage2 AArch64 backend5 files changed, 1029 insertions(+), 31 deletions(-)
src/codegen.zig+177-30| ... | @@ -83,9 +83,9 @@ pub fn generateSymbol( | ... | @@ -83,9 +83,9 @@ pub fn generateSymbol( |
| 83 | .wasm64 => unreachable, // has its own code path | 83 | .wasm64 => unreachable, // has its own code path |
| 84 | .arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, debug_output), | 84 | .arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, debug_output), |
| 85 | .armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, debug_output), | 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), | 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), | 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), | 88 | .aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, debug_output), |
| 89 | //.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, debug_output), | 89 | //.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, debug_output), |
| 90 | //.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, debug_output), | 90 | //.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, debug_output), |
| 91 | //.bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code, debug_output), | 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,6 +1380,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1380 | .arm, .armeb => { | 1380 | .arm, .armeb => { |
| 1381 | writeInt(u32, try self.code.addManyAsArray(4), Instruction.bkpt(0).toU32()); | 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 | else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), | 1386 | else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), |
| 1384 | } | 1387 | } |
| 1385 | return .none; | 1388 | return .none; |
| ... | @@ -1583,25 +1586,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1583,25 +1586,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1583 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); | 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}), | 1589 | .aarch64 => { |
| 1587 | } | ||
| 1588 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { | ||
| 1589 | switch (arch) { | ||
| 1590 | .x86_64 => { | ||
| 1591 | for (info.args) |mc_arg, arg_i| { | 1590 | for (info.args) |mc_arg, arg_i| { |
| 1592 | const arg = inst.args[arg_i]; | 1591 | const arg = inst.args[arg_i]; |
| 1593 | const arg_mcv = try self.resolveInst(inst.args[arg_i]); | 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 | 1593 | |
| 1595 | // the function call will move the stack pointer, so the offsets are different. | ||
| 1596 | switch (mc_arg) { | 1594 | switch (mc_arg) { |
| 1597 | .none => continue, | 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 | .register => |reg| { | 1604 | .register => |reg| { |
| 1599 | try self.genSetReg(arg.src, reg, arg_mcv); | 1605 | try self.genSetReg(arg.src, reg, arg_mcv); |
| 1600 | // TODO interact with the register allocator to mark the instruction as moved. | 1606 | // TODO interact with the register allocator to mark the instruction as moved. |
| 1601 | }, | 1607 | }, |
| 1602 | .stack_offset => { | 1608 | .stack_offset => { |
| 1603 | // Here we need to emit instructions like this: | ||
| 1604 | // mov qword ptr [rsp + stack_offset], x | ||
| 1605 | return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); | 1609 | return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); |
| 1606 | }, | 1610 | }, |
| 1607 | .ptr_stack_offset => { | 1611 | .ptr_stack_offset => { |
| ... | @@ -1610,28 +1614,25 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1610,28 +1614,25 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1610 | .ptr_embedded_in_code => { | 1614 | .ptr_embedded_in_code => { |
| 1611 | return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{}); | 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 | if (inst.func.cast(ir.Inst.Constant)) |func_inst| { | 1620 | if (inst.func.cast(ir.Inst.Constant)) |func_inst| { |
| 1625 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { | 1621 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { |
| 1626 | const func = func_val.func; | 1622 | const func = func_val.func; |
| 1627 | const got = &macho_file.sections.items[macho_file.got_section_index.?]; | 1623 | const ptr_bits = self.target.cpu.arch.ptrBitWidth(); |
| 1628 | const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64); | 1624 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 1629 | // Here, we store the got address in %rax, and then call %rax | 1625 | const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { |
| 1630 | // movabsq [addr], %rax | 1626 | const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; |
| 1631 | try self.genSetReg(inst.base.src, .rax, .{ .memory = got_addr }); | 1627 | break :blk @intCast(u32, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * ptr_bytes); |
| 1632 | // callq *%rax | 1628 | } else if (self.bin_file.cast(link.File.Coff)) |coff_file| |
| 1633 | try self.code.ensureCapacity(self.code.items.len + 2); | 1629 | coff_file.offset_table_virtual_address + func.owner_decl.link.coff.offset_table_index * ptr_bytes |
| 1634 | self.code.appendSliceAssumeCapacity(&[2]u8{ 0xff, 0xd0 }); | 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 | } else { | 1636 | } else { |
| 1636 | return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); | 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,8 +1640,67 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1639 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); | 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 => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), |
| 1643 | else => unreachable, | 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 | } else { | 1705 | } else { |
| 1646 | unreachable; | 1706 | unreachable; |
| ... | @@ -1699,6 +1759,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1699,6 +1759,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1699 | try self.code.resize(self.code.items.len + 4); | 1759 | try self.code.resize(self.code.items.len + 4); |
| 1700 | try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4); | 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 | else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), | 1766 | else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), |
| 1703 | } | 1767 | } |
| 1704 | return .unreach; | 1768 | return .unreach; |
| ... | @@ -2114,6 +2178,47 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -2114,6 +2178,47 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2114 | return MCValue.none; | 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 | .riscv64 => { | 2222 | .riscv64 => { |
| 2118 | for (inst.inputs) |input, i| { | 2223 | for (inst.inputs) |input, i| { |
| 2119 | if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { | 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,6 +2553,47 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2448 | }, | 2553 | }, |
| 2449 | else => return self.fail(src, "TODO implement getSetReg for arm {}", .{mcv}), | 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 | .riscv64 => switch (mcv) { | 2597 | .riscv64 => switch (mcv) { |
| 2452 | .dead => unreachable, | 2598 | .dead => unreachable, |
| 2453 | .ptr_stack_offset => unreachable, | 2599 | .ptr_stack_offset => unreachable, |
| ... | @@ -3007,6 +3153,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -3007,6 +3153,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3007 | .riscv64 => @import("codegen/riscv64.zig"), | 3153 | .riscv64 => @import("codegen/riscv64.zig"), |
| 3008 | .spu_2 => @import("codegen/spu-mk2.zig"), | 3154 | .spu_2 => @import("codegen/spu-mk2.zig"), |
| 3009 | .arm, .armeb => @import("codegen/arm.zig"), | 3155 | .arm, .armeb => @import("codegen/arm.zig"), |
| 3156 | .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig"), | ||
| 3010 | else => struct { | 3157 | else => struct { |
| 3011 | pub const Register = enum { | 3158 | pub const Register = enum { |
| 3012 | dummy, | 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,7 +818,8 @@ pub const Type = extern union { |
| 818 | .fn_ccc_void_no_args, // represents machine code; not a pointer | 818 | .fn_ccc_void_no_args, // represents machine code; not a pointer |
| 819 | .function, // represents machine code; not a pointer | 819 | .function, // represents machine code; not a pointer |
| 820 | => return switch (target.cpu.arch) { | 820 | => return switch (target.cpu.arch) { |
| 821 | .arm => 4, | 821 | .arm, .armeb => 4, |
| 822 | .aarch64, .aarch64_32, .aarch64_be => 4, | ||
| 822 | .riscv64 => 2, | 823 | .riscv64 => 2, |
| 823 | else => 1, | 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,6 +31,7 @@ pub fn addCases(ctx: *TestContext) !void { |
| 31 | try @import("cbe.zig").addCases(ctx); | 31 | try @import("cbe.zig").addCases(ctx); |
| 32 | try @import("spu-ii.zig").addCases(ctx); | 32 | try @import("spu-ii.zig").addCases(ctx); |
| 33 | try @import("arm.zig").addCases(ctx); | 33 | try @import("arm.zig").addCases(ctx); |
| 34 | try @import("aarch64.zig").addCases(ctx); | ||
| 34 | 35 | ||
| 35 | { | 36 | { |
| 36 | var case = ctx.exe("hello world with updates", linux_x64); | 37 | var case = ctx.exe("hello world with updates", linux_x64); |