aboutsummaryrefslogtreecommitdiff
path: root/src/codegen.zig
diff options
context:
space:
mode:
authorJakub Konka <kubkon@jakubkonka.com>2020-11-12 20:41:15 +0100
committerGitHub <noreply@github.com>2020-11-12 20:41:15 +0100
commit51717314e4c955f696919aea6f17d758b4b6430f (patch)
tree75cd10021baaa9e07dab727c66a875a5ef60f58b /src/codegen.zig
parent2d42532fec06d25a1d56b2696e59f52922d6168a (diff)
parentc6d46a9b82f04aea439320f0ae8e8a4ed187040e (diff)
downloadzig-51717314e4c955f696919aea6f17d758b4b6430f.tar.gz
zig-51717314e4c955f696919aea6f17d758b4b6430f.zip
Merge pull request #6900 from joachimschmidt557/stage2-aarch64
Add stage2 AArch64 backend
Diffstat (limited to 'src/codegen.zig')
-rw-r--r--src/codegen.zig207
1 files changed, 177 insertions, 30 deletions
diff --git a/src/codegen.zig b/src/codegen.zig
index ce509eb952..53d065d26c 100644
--- a/src/codegen.zig
+++ b/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,25 +1586,26 @@ 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,
+ .undef => unreachable,
+ .immediate => unreachable,
+ .unreach => unreachable,
+ .dead => unreachable,
+ .embedded_in_code => unreachable,
+ .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 => {
- // 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 => {
@@ -1610,28 +1614,25 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.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);
- // 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,