Merge pull request #6900 from joachimschmidt557/stage2-aarch64

Add stage2 AArch64 backend
This commit is contained in:
Jakub Konka 2020-11-12 20:41:15 +01:00 committed by GitHub
commit 51717314e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1034 additions and 36 deletions

View File

@ -83,9 +83,9 @@ pub fn generateSymbol(
.wasm64 => unreachable, // has its own code path
.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, debug_output),
.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, debug_output),
.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, debug_output),
.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, debug_output),
.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, debug_output),
//.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 {
.arm, .armeb => {
writeInt(u32, try self.code.addManyAsArray(4), Instruction.bkpt(0).toU32());
},
.aarch64 => {
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.brk(1).toU32());
},
else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}),
}
return .none;
@ -1583,33 +1586,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
}
},
else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),
}
} else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
switch (arch) {
.x86_64 => {
.aarch64 => {
for (info.args) |mc_arg, arg_i| {
const arg = inst.args[arg_i];
const arg_mcv = try self.resolveInst(inst.args[arg_i]);
// Here we do not use setRegOrMem even though the logic is similar, because
// the function call will move the stack pointer, so the offsets are different.
switch (mc_arg) {
.none => continue,
.register => |reg| {
try self.genSetReg(arg.src, reg, arg_mcv);
// TODO interact with the register allocator to mark the instruction as moved.
},
.stack_offset => {
// Here we need to emit instructions like this:
// mov qword ptr [rsp + stack_offset], x
return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
},
.ptr_stack_offset => {
return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
},
.ptr_embedded_in_code => {
return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
},
.undef => unreachable,
.immediate => unreachable,
.unreach => unreachable,
@ -1618,20 +1601,38 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.memory => unreachable,
.compare_flags_signed => unreachable,
.compare_flags_unsigned => unreachable,
.register => |reg| {
try self.genSetReg(arg.src, reg, arg_mcv);
// TODO interact with the register allocator to mark the instruction as moved.
},
.stack_offset => {
return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
},
.ptr_stack_offset => {
return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
},
.ptr_embedded_in_code => {
return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
},
}
}
if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
const func = func_val.func;
const got = &macho_file.sections.items[macho_file.got_section_index.?];
const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64);
// Here, we store the got address in %rax, and then call %rax
// movabsq [addr], %rax
try self.genSetReg(inst.base.src, .rax, .{ .memory = got_addr });
// callq *%rax
try self.code.ensureCapacity(self.code.items.len + 2);
self.code.appendSliceAssumeCapacity(&[2]u8{ 0xff, 0xd0 });
const ptr_bits = self.target.cpu.arch.ptrBitWidth();
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
break :blk @intCast(u32, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * ptr_bytes);
} else if (self.bin_file.cast(link.File.Coff)) |coff_file|
coff_file.offset_table_virtual_address + func.owner_decl.link.coff.offset_table_index * ptr_bytes
else
unreachable;
try self.genSetReg(inst.base.src, .x30, .{ .memory = got_addr });
writeInt(u32, try self.code.addManyAsArray(4), Instruction.blr(.x30).toU32());
} else {
return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
}
@ -1639,8 +1640,67 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
}
},
.aarch64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for aarch64 arch", .{}),
else => unreachable,
else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),
}
} else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
for (info.args) |mc_arg, arg_i| {
const arg = inst.args[arg_i];
const arg_mcv = try self.resolveInst(inst.args[arg_i]);
// Here we do not use setRegOrMem even though the logic is similar, because
// the function call will move the stack pointer, so the offsets are different.
switch (mc_arg) {
.none => continue,
.register => |reg| {
try self.genSetReg(arg.src, reg, arg_mcv);
// TODO interact with the register allocator to mark the instruction as moved.
},
.stack_offset => {
// Here we need to emit instructions like this:
// mov qword ptr [rsp + stack_offset], x
return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
},
.ptr_stack_offset => {
return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
},
.ptr_embedded_in_code => {
return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
},
.undef => unreachable,
.immediate => unreachable,
.unreach => unreachable,
.dead => unreachable,
.embedded_in_code => unreachable,
.memory => unreachable,
.compare_flags_signed => unreachable,
.compare_flags_unsigned => unreachable,
}
}
if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
const func = func_val.func;
const got = &macho_file.sections.items[macho_file.got_section_index.?];
const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64);
switch (arch) {
.x86_64 => {
// Here, we store the got address in %rax, and then call %rax
// movabsq [addr], %rax
try self.genSetReg(inst.base.src, .rax, .{ .memory = got_addr });
// callq *%rax
try self.code.ensureCapacity(self.code.items.len + 2);
self.code.appendSliceAssumeCapacity(&[2]u8{ 0xff, 0xd0 });
},
.aarch64 => {
try self.genSetReg(inst.base.src, .x30, .{ .memory = got_addr });
writeInt(u32, try self.code.addManyAsArray(4), Instruction.blr(.x30).toU32());
},
else => unreachable, // unsupported architecture on MachO
}
} else {
return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
}
} else {
return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
}
} else {
unreachable;
@ -1699,6 +1759,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
try self.code.resize(self.code.items.len + 4);
try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4);
},
.aarch64 => {
// TODO: relocations
writeInt(u32, try self.code.addManyAsArray(4), Instruction.ret(null).toU32());
},
else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}),
}
return .unreach;
@ -2114,6 +2178,47 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return MCValue.none;
}
},
.aarch64 => {
for (inst.inputs) |input, i| {
if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input});
}
const reg_name = input[1 .. input.len - 1];
const reg = parseRegName(reg_name) orelse
return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
const arg = try self.resolveInst(inst.args[i]);
try self.genSetReg(inst.base.src, reg, arg);
}
// TODO move this to lib/std/{elf, macho}.zig, etc.
const is_syscall_inst = switch (self.bin_file.tag) {
.macho => mem.eql(u8, inst.asm_source, "svc #0x80"),
.elf => mem.eql(u8, inst.asm_source, "svc #0"),
else => |tag| return self.fail(inst.base.src, "TODO implement aarch64 support for other syscall instructions for file format: '{}'", .{tag}),
};
if (is_syscall_inst) {
const imm16: u16 = switch (self.bin_file.tag) {
.macho => 0x80,
.elf => 0,
else => unreachable,
};
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.svc(imm16).toU32());
} else {
return self.fail(inst.base.src, "TODO implement support for more aarch64 assembly instructions", .{});
}
if (inst.output) |output| {
if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output});
}
const reg_name = output[2 .. output.len - 1];
const reg = parseRegName(reg_name) orelse
return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
return MCValue{ .register = reg };
} else {
return MCValue.none;
}
},
.riscv64 => {
for (inst.inputs) |input, i| {
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 {
},
else => return self.fail(src, "TODO implement getSetReg for arm {}", .{mcv}),
},
.aarch64 => switch (mcv) {
.dead => unreachable,
.ptr_stack_offset => unreachable,
.ptr_embedded_in_code => unreachable,
.unreach, .none => return, // Nothing to do.
.undef => {
if (!self.wantSafety())
return; // The already existing value will do just fine.
// Write the debug undefined value.
switch (reg.size()) {
32 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa }),
64 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
else => unreachable, // unexpected register size
}
},
.immediate => |x| {
if (x <= math.maxInt(u16)) {
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @intCast(u16, x), 0).toU32());
} else if (x <= math.maxInt(u32)) {
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @truncate(u16, x), 0).toU32());
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 16), 16).toU32());
} else if (x <= math.maxInt(u32)) {
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @truncate(u16, x), 0).toU32());
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @truncate(u16, x >> 16), 16).toU32());
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 32), 32).toU32());
} else {
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movz(reg, @truncate(u16, x), 0).toU32());
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @truncate(u16, x >> 16), 16).toU32());
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @truncate(u16, x >> 32), 32).toU32());
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.movk(reg, @intCast(u16, x >> 48), 48).toU32());
}
},
.register => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}),
.memory => |addr| {
// The value is in memory at a hard-coded address.
// If the type is a pointer, it means the pointer address is at this memory location.
try self.genSetReg(src, reg, .{ .immediate = addr });
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ .rn = reg }).toU32());
},
else => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}),
},
.riscv64 => switch (mcv) {
.dead => unreachable,
.ptr_stack_offset => unreachable,
@ -3007,6 +3153,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.riscv64 => @import("codegen/riscv64.zig"),
.spu_2 => @import("codegen/spu-mk2.zig"),
.arm, .armeb => @import("codegen/arm.zig"),
.aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig"),
else => struct {
pub const Register = enum {
dummy,

689
src/codegen/aarch64.zig Normal file
View File

@ -0,0 +1,689 @@
const std = @import("std");
const DW = std.dwarf;
const assert = std.debug.assert;
const testing = std.testing;
// zig fmt: off
/// General purpose registers in the AArch64 instruction set
pub const Register = enum(u6) {
// 64-bit registers
x0, x1, x2, x3, x4, x5, x6, x7,
x8, x9, x10, x11, x12, x13, x14, x15,
x16, x17, x18, x19, x20, x21, x22, x23,
x24, x25, x26, x27, x28, x29, x30, xzr,
// 32-bit registers
w0, w1, w2, w3, w4, w5, w6, w7,
w8, w9, w10, w11, w12, w13, w14, w15,
w16, w17, w18, w19, w20, w21, w22, w23,
w24, w25, w26, w27, w28, w29, w30, wzr,
pub fn id(self: Register) u5 {
return @truncate(u5, @enumToInt(self));
}
/// Returns the bit-width of the register.
pub fn size(self: Register) u7 {
return switch (@enumToInt(self)) {
0...31 => 64,
32...63 => 32,
};
}
/// Convert from any register to its 64 bit alias.
pub fn to64(self: Register) Register {
return @intToEnum(Register, self.id());
}
/// Convert from any register to its 32 bit alias.
pub fn to32(self: Register) Register {
return @intToEnum(Register, @as(u6, self.id()) + 32);
}
/// Returns the index into `callee_preserved_regs`.
pub fn allocIndex(self: Register) ?u4 {
inline for (callee_preserved_regs) |cpreg, i| {
if (self.id() == cpreg.id()) return i;
}
return null;
}
pub fn dwarfLocOp(self: Register) u8 {
return @as(u8, self.id()) + DW.OP_reg0;
}
};
// zig fmt: on
pub const callee_preserved_regs = [_]Register{
.x19, .x20, .x21, .x22, .x23,
.x24, .x25, .x26, .x27, .x28,
};
pub const c_abi_int_param_regs = [_]Register{ .x0, .x1, .x2, .x3, .x4, .x5, .x6, .x7 };
pub const c_abi_int_return_regs = [_]Register{ .x0, .x1 };
test "Register.id" {
testing.expectEqual(@as(u5, 0), Register.x0.id());
testing.expectEqual(@as(u5, 0), Register.w0.id());
testing.expectEqual(@as(u5, 31), Register.xzr.id());
testing.expectEqual(@as(u5, 31), Register.wzr.id());
}
test "Register.size" {
testing.expectEqual(@as(u7, 64), Register.x19.size());
testing.expectEqual(@as(u7, 32), Register.w3.size());
}
test "Register.to64/to32" {
testing.expectEqual(Register.x0, Register.w0.to64());
testing.expectEqual(Register.x0, Register.x0.to64());
testing.expectEqual(Register.w3, Register.w3.to32());
testing.expectEqual(Register.w3, Register.x3.to32());
}
// zig fmt: off
/// Scalar floating point registers in the aarch64 instruction set
pub const FloatingPointRegister = enum(u8) {
// 128-bit registers
q0, q1, q2, q3, q4, q5, q6, q7,
q8, q9, q10, q11, q12, q13, q14, q15,
q16, q17, q18, q19, q20, q21, q22, q23,
q24, q25, q26, q27, q28, q29, q30, q31,
// 64-bit registers
d0, d1, d2, d3, d4, d5, d6, d7,
d8, d9, d10, d11, d12, d13, d14, d15,
d16, d17, d18, d19, d20, d21, d22, d23,
d24, d25, d26, d27, d28, d29, d30, d31,
// 32-bit registers
s0, s1, s2, s3, s4, s5, s6, s7,
s8, s9, s10, s11, s12, s13, s14, s15,
s16, s17, s18, s19, s20, s21, s22, s23,
s24, s25, s26, s27, s28, s29, s30, s31,
// 16-bit registers
h0, h1, h2, h3, h4, h5, h6, h7,
h8, h9, h10, h11, h12, h13, h14, h15,
h16, h17, h18, h19, h20, h21, h22, h23,
h24, h25, h26, h27, h28, h29, h30, h31,
// 8-bit registers
b0, b1, b2, b3, b4, b5, b6, b7,
b8, b9, b10, b11, b12, b13, b14, b15,
b16, b17, b18, b19, b20, b21, b22, b23,
b24, b25, b26, b27, b28, b29, b30, b31,
pub fn id(self: FloatingPointRegister) u5 {
return @truncate(u5, @enumToInt(self));
}
/// Returns the bit-width of the register.
pub fn size(self: FloatingPointRegister) u8 {
return switch (@enumToInt(self)) {
0...31 => 128,
32...63 => 64,
64...95 => 32,
96...127 => 16,
128...159 => 8,
else => unreachable,
};
}
/// Convert from any register to its 128 bit alias.
pub fn to128(self: FloatingPointRegister) FloatingPointRegister {
return @intToEnum(FloatingPointRegister, self.id());
}
/// Convert from any register to its 64 bit alias.
pub fn to64(self: FloatingPointRegister) FloatingPointRegister {
return @intToEnum(FloatingPointRegister, @as(u8, self.id()) + 32);
}
/// Convert from any register to its 32 bit alias.
pub fn to32(self: FloatingPointRegister) FloatingPointRegister {
return @intToEnum(FloatingPointRegister, @as(u8, self.id()) + 64);
}
/// Convert from any register to its 16 bit alias.
pub fn to16(self: FloatingPointRegister) FloatingPointRegister {
return @intToEnum(FloatingPointRegister, @as(u8, self.id()) + 96);
}
/// Convert from any register to its 8 bit alias.
pub fn to8(self: FloatingPointRegister) FloatingPointRegister {
return @intToEnum(FloatingPointRegister, @as(u8, self.id()) + 128);
}
};
// zig fmt: on
test "FloatingPointRegister.id" {
testing.expectEqual(@as(u5, 0), FloatingPointRegister.b0.id());
testing.expectEqual(@as(u5, 0), FloatingPointRegister.h0.id());
testing.expectEqual(@as(u5, 0), FloatingPointRegister.s0.id());
testing.expectEqual(@as(u5, 0), FloatingPointRegister.d0.id());
testing.expectEqual(@as(u5, 0), FloatingPointRegister.q0.id());
testing.expectEqual(@as(u5, 2), FloatingPointRegister.q2.id());
testing.expectEqual(@as(u5, 31), FloatingPointRegister.d31.id());
}
test "FloatingPointRegister.size" {
testing.expectEqual(@as(u8, 128), FloatingPointRegister.q1.size());
testing.expectEqual(@as(u8, 64), FloatingPointRegister.d2.size());
testing.expectEqual(@as(u8, 32), FloatingPointRegister.s3.size());
testing.expectEqual(@as(u8, 16), FloatingPointRegister.h4.size());
testing.expectEqual(@as(u8, 8), FloatingPointRegister.b5.size());
}
test "FloatingPointRegister.toX" {
testing.expectEqual(FloatingPointRegister.q1, FloatingPointRegister.q1.to128());
testing.expectEqual(FloatingPointRegister.q2, FloatingPointRegister.b2.to128());
testing.expectEqual(FloatingPointRegister.q3, FloatingPointRegister.h3.to128());
testing.expectEqual(FloatingPointRegister.d0, FloatingPointRegister.q0.to64());
testing.expectEqual(FloatingPointRegister.s1, FloatingPointRegister.d1.to32());
testing.expectEqual(FloatingPointRegister.h2, FloatingPointRegister.s2.to16());
testing.expectEqual(FloatingPointRegister.b3, FloatingPointRegister.h3.to8());
}
/// Represents an instruction in the AArch64 instruction set
pub const Instruction = union(enum) {
MoveWideImmediate: packed struct {
rd: u5,
imm16: u16,
hw: u2,
fixed: u6 = 0b100101,
opc: u2,
sf: u1,
},
LoadStoreRegister: packed struct {
rt: u5,
rn: u5,
offset: u12,
opc: u2,
op1: u2,
fixed: u4 = 0b111_0,
size: u2,
},
LoadLiteral: packed struct {
rt: u5,
imm19: u19,
fixed: u6 = 0b011_0_00,
opc: u2,
},
ExceptionGeneration: packed struct {
ll: u2,
op2: u3,
imm16: u16,
opc: u3,
fixed: u8 = 0b1101_0100,
},
UnconditionalBranchRegister: packed struct {
op4: u5,
rn: u5,
op3: u6,
op2: u5,
opc: u4,
fixed: u7 = 0b1101_011,
},
UnconditionalBranchImmediate: packed struct {
imm26: u26,
fixed: u5 = 0b00101,
op: u1,
},
pub fn toU32(self: Instruction) u32 {
return switch (self) {
.MoveWideImmediate => |v| @bitCast(u32, v),
.LoadStoreRegister => |v| @bitCast(u32, v),
.LoadLiteral => |v| @bitCast(u32, v),
.ExceptionGeneration => |v| @bitCast(u32, v),
.UnconditionalBranchRegister => |v| @bitCast(u32, v),
.UnconditionalBranchImmediate => |v| @bitCast(u32, v),
};
}
/// Represents the offset operand of a load or store instruction.
/// Data can be loaded from memory with either an immediate offset
/// or an offset that is stored in some register.
pub const Offset = union(enum) {
Immediate: union(enum) {
PostIndex: i9,
PreIndex: i9,
Unsigned: u12,
},
Register: struct {
rm: u5,
shift: union(enum) {
Uxtw: u2,
Lsl: u2,
Sxtw: u2,
Sxtx: u2,
},
},
pub const none = Offset{
.Immediate = .{ .Unsigned = 0 },
};
pub fn toU12(self: Offset) u12 {
return switch (self) {
.Immediate => |imm_type| switch (imm_type) {
.PostIndex => |v| (@intCast(u12, @bitCast(u9, v)) << 2) + 1,
.PreIndex => |v| (@intCast(u12, @bitCast(u9, v)) << 2) + 3,
.Unsigned => |v| v,
},
.Register => |r| switch (r.shift) {
.Uxtw => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 16 + 2050,
.Lsl => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 24 + 2050,
.Sxtw => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 48 + 2050,
.Sxtx => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 56 + 2050,
},
};
}
pub fn imm(offset: u12) Offset {
return Offset{
.Immediate = .{ .Unsigned = offset },
};
}
pub fn imm_post_index(offset: i9) Offset {
return Offset{
.Immediate = .{ .PostIndex = offset },
};
}
pub fn imm_pre_index(offset: i9) Offset {
return Offset{
.Immediate = .{ .PreIndex = offset },
};
}
pub fn reg(rm: Register) Offset {
return Offset{
.Register = .{
.rm = rm.id(),
.shift = .{
.Lsl = 0,
},
},
};
}
pub fn reg_uxtw(rm: Register, shift: u2) Offset {
assert(rm.size() == 32 and (shift == 0 or shift == 2));
return Offset{
.Register = .{
.rm = rm.id(),
.shift = .{
.Uxtw = shift,
},
},
};
}
pub fn reg_lsl(rm: Register, shift: u2) Offset {
assert(rm.size() == 64 and (shift == 0 or shift == 3));
return Offset{
.Register = .{
.rm = rm.id(),
.shift = .{
.Lsl = shift,
},
},
};
}
pub fn reg_sxtw(rm: Register, shift: u2) Offset {
assert(rm.size() == 32 and (shift == 0 or shift == 2));
return Offset{
.Register = .{
.rm = rm.id(),
.shift = .{
.Sxtw = shift,
},
},
};
}
pub fn reg_sxtx(rm: Register, shift: u2) Offset {
assert(rm.size() == 64 and (shift == 0 or shift == 3));
return Offset{
.Register = .{
.rm = rm.id(),
.shift = .{
.Sxtx = shift,
},
},
};
}
};
// Helper functions for assembly syntax functions
fn moveWideImmediate(
opc: u2,
rd: Register,
imm16: u16,
shift: u6,
) Instruction {
switch (rd.size()) {
32 => {
assert(shift % 16 == 0 and shift <= 16);
return Instruction{
.MoveWideImmediate = .{
.rd = rd.id(),
.imm16 = imm16,
.hw = @intCast(u2, shift / 16),
.opc = opc,
.sf = 0,
},
};
},
64 => {
assert(shift % 16 == 0 and shift <= 48);
return Instruction{
.MoveWideImmediate = .{
.rd = rd.id(),
.imm16 = imm16,
.hw = @intCast(u2, shift / 16),
.opc = opc,
.sf = 1,
},
};
},
else => unreachable, // unexpected register size
}
}
fn loadStoreRegister(rt: Register, rn: Register, offset: Offset, load: bool) Instruction {
const off = offset.toU12();
const op1: u2 = blk: {
switch (offset) {
.Immediate => |imm| switch (imm) {
.Unsigned => break :blk 0b01,
else => {},
},
else => {},
}
break :blk 0b00;
};
const opc: u2 = if (load) 0b01 else 0b00;
switch (rt.size()) {
32 => {
return Instruction{
.LoadStoreRegister = .{
.rt = rt.id(),
.rn = rn.id(),
.offset = offset.toU12(),
.opc = opc,
.op1 = op1,
.size = 0b10,
},
};
},
64 => {
return Instruction{
.LoadStoreRegister = .{
.rt = rt.id(),
.rn = rn.id(),
.offset = offset.toU12(),
.opc = opc,
.op1 = op1,
.size = 0b11,
},
};
},
else => unreachable, // unexpected register size
}
}
fn loadLiteral(rt: Register, imm19: u19) Instruction {
switch (rt.size()) {
32 => {
return Instruction{
.LoadLiteral = .{
.rt = rt.id(),
.imm19 = imm19,
.opc = 0b00,
},
};
},
64 => {
return Instruction{
.LoadLiteral = .{
.rt = rt.id(),
.imm19 = imm19,
.opc = 0b01,
},
};
},
else => unreachable, // unexpected register size
}
}
fn exceptionGeneration(
opc: u3,
op2: u3,
ll: u2,
imm16: u16,
) Instruction {
return Instruction{
.ExceptionGeneration = .{
.ll = ll,
.op2 = op2,
.imm16 = imm16,
.opc = opc,
},
};
}
fn unconditionalBranchRegister(
opc: u4,
op2: u5,
op3: u6,
rn: Register,
op4: u5,
) Instruction {
assert(rn.size() == 64);
return Instruction{
.UnconditionalBranchRegister = .{
.op4 = op4,
.rn = rn.id(),
.op3 = op3,
.op2 = op2,
.opc = opc,
},
};
}
fn unconditionalBranchImmediate(
op: u1,
offset: i28,
) Instruction {
return Instruction{
.UnconditionalBranchImmediate = .{
.imm26 = @bitCast(u26, @intCast(i26, offset >> 2)),
.op = op,
},
};
}
// Move wide (immediate)
pub fn movn(rd: Register, imm16: u16, shift: u6) Instruction {
return moveWideImmediate(0b00, rd, imm16, shift);
}
pub fn movz(rd: Register, imm16: u16, shift: u6) Instruction {
return moveWideImmediate(0b10, rd, imm16, shift);
}
pub fn movk(rd: Register, imm16: u16, shift: u6) Instruction {
return moveWideImmediate(0b11, rd, imm16, shift);
}
// Load or store register
pub const LdrArgs = struct {
rn: ?Register = null,
offset: Offset = Offset.none,
literal: ?u19 = null,
};
pub fn ldr(rt: Register, args: LdrArgs) Instruction {
if (args.rn) |rn| {
return loadStoreRegister(rt, rn, args.offset, true);
} else {
return loadLiteral(rt, args.literal.?);
}
}
pub const StrArgs = struct {
offset: Offset = Offset.none,
};
pub fn str(rt: Register, rn: Register, args: StrArgs) Instruction {
return loadStoreRegister(rt, rn, args.offset, false);
}
// Exception generation
pub fn svc(imm16: u16) Instruction {
return exceptionGeneration(0b000, 0b000, 0b01, imm16);
}
pub fn hvc(imm16: u16) Instruction {
return exceptionGeneration(0b000, 0b000, 0b10, imm16);
}
pub fn smc(imm16: u16) Instruction {
return exceptionGeneration(0b000, 0b000, 0b11, imm16);
}
pub fn brk(imm16: u16) Instruction {
return exceptionGeneration(0b001, 0b000, 0b00, imm16);
}
pub fn hlt(imm16: u16) Instruction {
return exceptionGeneration(0b010, 0b000, 0b00, imm16);
}
// Unconditional branch (register)
pub fn br(rn: Register) Instruction {
return unconditionalBranchRegister(0b0000, 0b11111, 0b000000, rn, 0b00000);
}
pub fn blr(rn: Register) Instruction {
return unconditionalBranchRegister(0b0001, 0b11111, 0b000000, rn, 0b00000);
}
pub fn ret(rn: ?Register) Instruction {
return unconditionalBranchRegister(0b0010, 0b11111, 0b000000, rn orelse .x30, 0b00000);
}
// Unconditional branch (immediate)
pub fn b(offset: i28) Instruction {
return unconditionalBranchImmediate(0, offset);
}
pub fn bl(offset: i28) Instruction {
return unconditionalBranchImmediate(1, offset);
}
};
test "" {
testing.refAllDecls(@This());
}
test "serialize instructions" {
const Testcase = struct {
inst: Instruction,
expected: u32,
};
const testcases = [_]Testcase{
.{ // movz x1 #4
.inst = Instruction.movz(.x1, 4, 0),
.expected = 0b1_10_100101_00_0000000000000100_00001,
},
.{ // movz x1, #4, lsl 16
.inst = Instruction.movz(.x1, 4, 16),
.expected = 0b1_10_100101_01_0000000000000100_00001,
},
.{ // movz x1, #4, lsl 32
.inst = Instruction.movz(.x1, 4, 32),
.expected = 0b1_10_100101_10_0000000000000100_00001,
},
.{ // movz x1, #4, lsl 48
.inst = Instruction.movz(.x1, 4, 48),
.expected = 0b1_10_100101_11_0000000000000100_00001,
},
.{ // movz w1, #4
.inst = Instruction.movz(.w1, 4, 0),
.expected = 0b0_10_100101_00_0000000000000100_00001,
},
.{ // movz w1, #4, lsl 16
.inst = Instruction.movz(.w1, 4, 16),
.expected = 0b0_10_100101_01_0000000000000100_00001,
},
.{ // svc #0
.inst = Instruction.svc(0),
.expected = 0b1101_0100_000_0000000000000000_00001,
},
.{ // svc #0x80 ; typical on Darwin
.inst = Instruction.svc(0x80),
.expected = 0b1101_0100_000_0000000010000000_00001,
},
.{ // ret
.inst = Instruction.ret(null),
.expected = 0b1101_011_00_10_11111_0000_00_11110_00000,
},
.{ // bl #0x10
.inst = Instruction.bl(0x10),
.expected = 0b1_00101_00_0000_0000_0000_0000_0000_0100,
},
.{ // ldr x2, [x1]
.inst = Instruction.ldr(.x2, .{ .rn = .x1 }),
.expected = 0b11_111_0_01_01_000000000000_00001_00010,
},
.{ // ldr x2, [x1, #1]!
.inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.imm_pre_index(1) }),
.expected = 0b11_111_0_00_01_0_000000001_11_00001_00010,
},
.{ // ldr x2, [x1], #-1
.inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.imm_post_index(-1) }),
.expected = 0b11_111_0_00_01_0_111111111_01_00001_00010,
},
.{ // ldr x2, [x1], (x3)
.inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.reg(.x3) }),
.expected = 0b11_111_0_00_01_1_00011_011_0_10_00001_00010,
},
.{ // ldr x2, label
.inst = Instruction.ldr(.x2, .{ .literal = 0x1 }),
.expected = 0b01_011_0_00_0000000000000000001_00010,
},
.{ // str x2, [x1]
.inst = Instruction.str(.x2, .x1, .{}),
.expected = 0b11_111_0_01_00_000000000000_00001_00010,
},
.{ // str x2, [x1], (x3)
.inst = Instruction.str(.x2, .x1, .{ .offset = Instruction.Offset.reg(.x3) }),
.expected = 0b11_111_0_00_00_1_00011_011_0_10_00001_00010,
},
};
for (testcases) |case| {
const actual = case.inst.toU32();
testing.expectEqual(case.expected, actual);
}
}

View File

@ -818,7 +818,8 @@ pub const Type = extern union {
.fn_ccc_void_no_args, // represents machine code; not a pointer
.function, // represents machine code; not a pointer
=> return switch (target.cpu.arch) {
.arm => 4,
.arm, .armeb => 4,
.aarch64, .aarch64_32, .aarch64_be => 4,
.riscv64 => 2,
else => 1,
},

160
test/stage2/aarch64.zig Normal file
View File

@ -0,0 +1,160 @@
const std = @import("std");
const TestContext = @import("../../src/test.zig").TestContext;
const macos_aarch64 = std.zig.CrossTarget{
.cpu_arch = .aarch64,
.os_tag = .macos,
};
const linux_aarch64 = std.zig.CrossTarget{
.cpu_arch = .aarch64,
.os_tag = .linux,
};
pub fn addCases(ctx: *TestContext) !void {
// TODO enable when we add codesigning to the self-hosted linker
// related to #6971
if (false) {
var case = ctx.exe("hello world with updates", macos_aarch64);
// Regular old hello world
case.addCompareOutput(
\\export fn _start() noreturn {
\\ print();
\\
\\ exit();
\\}
\\
\\fn print() void {
\\ asm volatile ("svc #0x80"
\\ :
\\ : [number] "{x16}" (4),
\\ [arg1] "{x0}" (1),
\\ [arg2] "{x1}" (@ptrToInt("Hello, World!\n")),
\\ [arg3] "{x2}" (14)
\\ : "memory"
\\ );
\\ return;
\\}
\\
\\fn exit() noreturn {
\\ asm volatile ("svc #0x80"
\\ :
\\ : [number] "{x16}" (1),
\\ [arg1] "{x0}" (0)
\\ : "memory"
\\ );
\\ unreachable;
\\}
,
"Hello, World!\n",
);
// Now change the message only
case.addCompareOutput(
\\export fn _start() noreturn {
\\ print();
\\
\\ exit();
\\}
\\
\\fn print() void {
\\ asm volatile ("svc #0x80"
\\ :
\\ : [number] "{x16}" (4),
\\ [arg1] "{x0}" (1),
\\ [arg2] "{x1}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")),
\\ [arg3] "{x2}" (104)
\\ : "memory"
\\ );
\\ return;
\\}
\\
\\fn exit() noreturn {
\\ asm volatile ("svc #0x80"
\\ :
\\ : [number] "{x16}" (1),
\\ [arg1] "{x0}" (0)
\\ : "memory"
\\ );
\\ unreachable;
\\}
,
"What is up? This is a longer message that will force the data to be relocated in virtual address space.\n",
);
// Now we print it twice.
case.addCompareOutput(
\\export fn _start() noreturn {
\\ print();
\\ print();
\\
\\ exit();
\\}
\\
\\fn print() void {
\\ asm volatile ("svc #0x80"
\\ :
\\ : [number] "{x16}" (4),
\\ [arg1] "{x0}" (1),
\\ [arg2] "{x1}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")),
\\ [arg3] "{x2}" (104)
\\ : "memory"
\\ );
\\ return;
\\}
\\
\\fn exit() noreturn {
\\ asm volatile ("svc #0x80"
\\ :
\\ : [number] "{x16}" (1),
\\ [arg1] "{x0}" (0)
\\ : "memory"
\\ );
\\ unreachable;
\\}
,
\\What is up? This is a longer message that will force the data to be relocated in virtual address space.
\\What is up? This is a longer message that will force the data to be relocated in virtual address space.
\\
);
}
{
var case = ctx.exe("hello world", linux_aarch64);
// Regular old hello world
case.addCompareOutput(
\\export fn _start() noreturn {
\\ print();
\\ exit();
\\}
\\
\\fn doNothing() void {}
\\
\\fn answer() u64 {
\\ return 0x1234abcd1234abcd;
\\}
\\
\\fn print() void {
\\ asm volatile ("svc #0"
\\ :
\\ : [number] "{x8}" (64),
\\ [arg1] "{x0}" (1),
\\ [arg2] "{x1}" (@ptrToInt("Hello, World!\n")),
\\ [arg3] "{x2}" ("Hello, World!\n".len)
\\ : "memory", "cc"
\\ );
\\}
\\
\\fn exit() noreturn {
\\ asm volatile ("svc #0"
\\ :
\\ : [number] "{x8}" (93),
\\ [arg1] "{x0}" (0)
\\ : "memory", "cc"
\\ );
\\ unreachable;
\\}
,
"Hello, World!\n",
);
}
}

View File

@ -31,6 +31,7 @@ pub fn addCases(ctx: *TestContext) !void {
try @import("cbe.zig").addCases(ctx);
try @import("spu-ii.zig").addCases(ctx);
try @import("arm.zig").addCases(ctx);
try @import("aarch64.zig").addCases(ctx);
{
var case = ctx.exe("hello world with updates", linux_x64);