mirror of
https://github.com/ziglang/zig.git
synced 2026-02-19 07:48:31 +00:00
Merge pull request #6900 from joachimschmidt557/stage2-aarch64
Add stage2 AArch64 backend
This commit is contained in:
commit
51717314e4
217
src/codegen.zig
217
src/codegen.zig
@ -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
689
src/codegen/aarch64.zig
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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
160
test/stage2/aarch64.zig
Normal 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",
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user