authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-11-12 20:41:15+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-11-12 20:41:15+01:00
log51717314e4c955f696919aea6f17d758b4b6430f
tree75cd10021baaa9e07dab727c66a875a5ef60f58b
parent2d42532fec06d25a1d56b2696e59f52922d6168a
parentc6d46a9b82f04aea439320f0ae8e8a4ed187040e
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #6900 from joachimschmidt557/stage2-aarch64

Add stage2 AArch64 backend

5 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 path83 .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, because1593
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 }
16231619
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 %rax1625 const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
1630 // movabsq [addr], %rax1626 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 *%rax1628 } 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 @@
1const std = @import("std");
2const DW = std.dwarf;
3const assert = std.debug.assert;
4const testing = std.testing;
5
6// zig fmt: off
7
8/// General purpose registers in the AArch64 instruction set
9pub 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
59pub const callee_preserved_regs = [_]Register{
60 .x19, .x20, .x21, .x22, .x23,
61 .x24, .x25, .x26, .x27, .x28,
62};
63
64pub const c_abi_int_param_regs = [_]Register{ .x0, .x1, .x2, .x3, .x4, .x5, .x6, .x7 };
65pub const c_abi_int_return_regs = [_]Register{ .x0, .x1 };
66
67test "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
75test "Register.size" {
76 testing.expectEqual(@as(u7, 64), Register.x19.size());
77 testing.expectEqual(@as(u7, 32), Register.w3.size());
78}
79
80test "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
91pub 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
166test "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
177test "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
185test "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
197pub 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
604test "" {
605 testing.refAllDecls(@This());
606}
607
608test "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 pointer818 .fn_ccc_void_no_args, // represents machine code; not a pointer
819 .function, // represents machine code; not a pointer819 .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 @@
1const std = @import("std");
2const TestContext = @import("../../src/test.zig").TestContext;
3
4const macos_aarch64 = std.zig.CrossTarget{
5 .cpu_arch = .aarch64,
6 .os_tag = .macos,
7};
8
9const linux_aarch64 = std.zig.CrossTarget{
10 .cpu_arch = .aarch64,
11 .os_tag = .linux,
12};
13
14pub 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);
3435
35 {36 {
36 var case = ctx.exe("hello world with updates", linux_x64);37 var case = ctx.exe("hello world with updates", linux_x64);