Merge pull request #10066 from joachimschmidt557/stage2-aarch64-mir

stage2 AArch64: introduce MIR
This commit is contained in:
Jakub Konka
2021-10-31 18:54:19 +01:00
committed by GitHub
4 changed files with 876 additions and 569 deletions

File diff suppressed because it is too large Load Diff

419
src/arch/aarch64/Emit.zig Normal file
View File

@@ -0,0 +1,419 @@
//! This file contains the functionality for lowering AArch64 MIR into
//! machine code
const Emit = @This();
const std = @import("std");
const math = std.math;
const Mir = @import("Mir.zig");
const bits = @import("bits.zig");
const link = @import("../../link.zig");
const Module = @import("../../Module.zig");
const ErrorMsg = Module.ErrorMsg;
const assert = std.debug.assert;
const DW = std.dwarf;
const leb128 = std.leb;
const Instruction = bits.Instruction;
const Register = bits.Register;
const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
mir: Mir,
bin_file: *link.File,
debug_output: DebugInfoOutput,
target: *const std.Target,
err_msg: ?*ErrorMsg = null,
src_loc: Module.SrcLoc,
code: *std.ArrayList(u8),
prev_di_line: u32,
prev_di_column: u32,
/// Relative to the beginning of `code`.
prev_di_pc: usize,
const InnerError = error{
OutOfMemory,
EmitFail,
};
pub fn emitMir(
emit: *Emit,
) !void {
const mir_tags = emit.mir.instructions.items(.tag);
for (mir_tags) |tag, index| {
const inst = @intCast(u32, index);
switch (tag) {
.add_immediate => try emit.mirAddSubtractImmediate(inst),
.sub_immediate => try emit.mirAddSubtractImmediate(inst),
.b => try emit.mirBranch(inst),
.bl => try emit.mirBranch(inst),
.blr => try emit.mirUnconditionalBranchRegister(inst),
.ret => try emit.mirUnconditionalBranchRegister(inst),
.brk => try emit.mirExceptionGeneration(inst),
.svc => try emit.mirExceptionGeneration(inst),
.call_extern => try emit.mirCallExtern(inst),
.dbg_line => try emit.mirDbgLine(inst),
.dbg_prologue_end => try emit.mirDebugPrologueEnd(),
.dbg_epilogue_begin => try emit.mirDebugEpilogueBegin(),
.load_memory => try emit.mirLoadMemory(inst),
.ldp => try emit.mirLoadStoreRegisterPair(inst),
.stp => try emit.mirLoadStoreRegisterPair(inst),
.ldr => try emit.mirLoadStoreRegister(inst),
.ldrb => try emit.mirLoadStoreRegister(inst),
.ldrh => try emit.mirLoadStoreRegister(inst),
.str => try emit.mirLoadStoreRegister(inst),
.strb => try emit.mirLoadStoreRegister(inst),
.strh => try emit.mirLoadStoreRegister(inst),
.mov_register => try emit.mirMoveRegister(inst),
.mov_to_from_sp => try emit.mirMoveRegister(inst),
.movk => try emit.mirMoveWideImmediate(inst),
.movz => try emit.mirMoveWideImmediate(inst),
.nop => try emit.mirNop(),
}
}
}
fn writeInstruction(emit: *Emit, instruction: Instruction) !void {
const endian = emit.target.cpu.arch.endian();
std.mem.writeInt(u32, try emit.code.addManyAsArray(4), instruction.toU32(), endian);
}
fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
@setCold(true);
assert(emit.err_msg == null);
emit.err_msg = try ErrorMsg.create(emit.bin_file.allocator, emit.src_loc, format, args);
return error.EmitFail;
}
fn moveImmediate(emit: *Emit, reg: Register, imm64: u64) !void {
try emit.writeInstruction(Instruction.movz(reg, @truncate(u16, imm64), 0));
if (imm64 > math.maxInt(u16)) {
try emit.writeInstruction(Instruction.movk(reg, @truncate(u16, imm64 >> 16), 16));
}
if (imm64 > math.maxInt(u32)) {
try emit.writeInstruction(Instruction.movk(reg, @truncate(u16, imm64 >> 32), 32));
}
if (imm64 > math.maxInt(u48)) {
try emit.writeInstruction(Instruction.movk(reg, @truncate(u16, imm64 >> 48), 48));
}
}
fn dbgAdvancePCAndLine(self: *Emit, line: u32, column: u32) !void {
const delta_line = @intCast(i32, line) - @intCast(i32, self.prev_di_line);
const delta_pc: usize = self.code.items.len - self.prev_di_pc;
switch (self.debug_output) {
.dwarf => |dbg_out| {
// TODO Look into using the DWARF special opcodes to compress this data.
// It lets you emit single-byte opcodes that add different numbers to
// both the PC and the line number at the same time.
try dbg_out.dbg_line.ensureUnusedCapacity(11);
dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_pc);
leb128.writeULEB128(dbg_out.dbg_line.writer(), delta_pc) catch unreachable;
if (delta_line != 0) {
dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_line);
leb128.writeILEB128(dbg_out.dbg_line.writer(), delta_line) catch unreachable;
}
dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.copy);
self.prev_di_pc = self.code.items.len;
self.prev_di_line = line;
self.prev_di_column = column;
self.prev_di_pc = self.code.items.len;
},
.plan9 => |dbg_out| {
if (delta_pc <= 0) return; // only do this when the pc changes
// we have already checked the target in the linker to make sure it is compatable
const quant = @import("../../link/Plan9/aout.zig").getPCQuant(self.target.cpu.arch) catch unreachable;
// increasing the line number
try @import("../../link/Plan9.zig").changeLine(dbg_out.dbg_line, delta_line);
// increasing the pc
const d_pc_p9 = @intCast(i64, delta_pc) - quant;
if (d_pc_p9 > 0) {
// minus one because if its the last one, we want to leave space to change the line which is one quanta
try dbg_out.dbg_line.append(@intCast(u8, @divExact(d_pc_p9, quant) + 128) - quant);
if (dbg_out.pcop_change_index.*) |pci|
dbg_out.dbg_line.items[pci] += 1;
dbg_out.pcop_change_index.* = @intCast(u32, dbg_out.dbg_line.items.len - 1);
} else if (d_pc_p9 == 0) {
// we don't need to do anything, because adding the quant does it for us
} else unreachable;
if (dbg_out.start_line.* == null)
dbg_out.start_line.* = self.prev_di_line;
dbg_out.end_line.* = line;
// only do this if the pc changed
self.prev_di_line = line;
self.prev_di_column = column;
self.prev_di_pc = self.code.items.len;
},
.none => {},
}
}
fn mirAddSubtractImmediate(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const rr_imm12_sh = emit.mir.instructions.items(.data)[inst].rr_imm12_sh;
switch (tag) {
.add_immediate => try emit.writeInstruction(Instruction.add(
rr_imm12_sh.rd,
rr_imm12_sh.rn,
rr_imm12_sh.imm12,
rr_imm12_sh.sh == 1,
)),
.sub_immediate => try emit.writeInstruction(Instruction.sub(
rr_imm12_sh.rd,
rr_imm12_sh.rn,
rr_imm12_sh.imm12,
rr_imm12_sh.sh == 1,
)),
else => unreachable,
}
}
fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const target_inst = emit.mir.instructions.items(.data)[inst].inst;
_ = tag;
_ = target_inst;
switch (tag) {
.b => return emit.fail("Implement mirBranch", .{}),
.bl => return emit.fail("Implement mirBranch", .{}),
else => unreachable,
}
}
fn mirUnconditionalBranchRegister(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const reg = emit.mir.instructions.items(.data)[inst].reg;
switch (tag) {
.blr => try emit.writeInstruction(Instruction.blr(reg)),
.ret => try emit.writeInstruction(Instruction.ret(reg)),
else => unreachable,
}
}
fn mirExceptionGeneration(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const imm16 = emit.mir.instructions.items(.data)[inst].imm16;
switch (tag) {
.brk => try emit.writeInstruction(Instruction.brk(imm16)),
.svc => try emit.writeInstruction(Instruction.svc(imm16)),
else => unreachable,
}
}
fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const dbg_line_column = emit.mir.instructions.items(.data)[inst].dbg_line_column;
switch (tag) {
.dbg_line => try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column),
else => unreachable,
}
}
fn mirDebugPrologueEnd(self: *Emit) !void {
switch (self.debug_output) {
.dwarf => |dbg_out| {
try dbg_out.dbg_line.append(DW.LNS.set_prologue_end);
try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column);
},
.plan9 => {},
.none => {},
}
}
fn mirDebugEpilogueBegin(self: *Emit) !void {
switch (self.debug_output) {
.dwarf => |dbg_out| {
try dbg_out.dbg_line.append(DW.LNS.set_epilogue_begin);
try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column);
},
.plan9 => {},
.none => {},
}
}
fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) !void {
assert(emit.mir.instructions.items(.tag)[inst] == .call_extern);
const n_strx = emit.mir.instructions.items(.data)[inst].extern_fn;
if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
const offset = blk: {
const offset = @intCast(u32, emit.code.items.len);
// bl
try emit.writeInstruction(Instruction.bl(0));
break :blk offset;
};
// Add relocation to the decl.
try macho_file.active_decl.?.link.macho.relocs.append(emit.bin_file.allocator, .{
.offset = offset,
.target = .{ .global = n_strx },
.addend = 0,
.subtractor = null,
.pcrel = true,
.length = 2,
.@"type" = @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_BRANCH26),
});
} else {
return emit.fail("Implement call_extern for linking backends != MachO", .{});
}
}
fn mirLoadMemory(emit: *Emit, inst: Mir.Inst.Index) !void {
assert(emit.mir.instructions.items(.tag)[inst] == .load_memory);
const payload = emit.mir.instructions.items(.data)[inst].payload;
const load_memory = emit.mir.extraData(Mir.LoadMemory, payload).data;
const reg = @intToEnum(Register, load_memory.register);
const addr = load_memory.addr;
if (emit.bin_file.options.pie) {
// PC-relative displacement to the entry in the GOT table.
// adrp
const offset = @intCast(u32, emit.code.items.len);
try emit.writeInstruction(Instruction.adrp(reg, 0));
// ldr reg, reg, offset
try emit.writeInstruction(Instruction.ldr(reg, .{
.register = .{
.rn = reg,
.offset = Instruction.LoadStoreOffset.imm(0),
},
}));
if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
// TODO I think the reloc might be in the wrong place.
const decl = macho_file.active_decl.?;
// Page reloc for adrp instruction.
try decl.link.macho.relocs.append(emit.bin_file.allocator, .{
.offset = offset,
.target = .{ .local = addr },
.addend = 0,
.subtractor = null,
.pcrel = true,
.length = 2,
.@"type" = @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_GOT_LOAD_PAGE21),
});
// Pageoff reloc for adrp instruction.
try decl.link.macho.relocs.append(emit.bin_file.allocator, .{
.offset = offset + 4,
.target = .{ .local = addr },
.addend = 0,
.subtractor = null,
.pcrel = false,
.length = 2,
.@"type" = @enumToInt(std.macho.reloc_type_arm64.ARM64_RELOC_GOT_LOAD_PAGEOFF12),
});
} else {
return emit.fail("TODO implement load_memory for PIE GOT indirection on this platform", .{});
}
} else {
// 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 emit.moveImmediate(reg, addr);
try emit.writeInstruction(Instruction.ldr(
reg,
.{ .register = .{ .rn = reg, .offset = Instruction.LoadStoreOffset.none } },
));
}
}
fn mirLoadStoreRegisterPair(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const load_store_register_pair = emit.mir.instructions.items(.data)[inst].load_store_register_pair;
switch (tag) {
.stp => try emit.writeInstruction(Instruction.stp(
load_store_register_pair.rt,
load_store_register_pair.rt2,
load_store_register_pair.rn,
load_store_register_pair.offset,
)),
.ldp => try emit.writeInstruction(Instruction.ldp(
load_store_register_pair.rt,
load_store_register_pair.rt2,
load_store_register_pair.rn,
load_store_register_pair.offset,
)),
else => unreachable,
}
}
fn mirLoadStoreRegister(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const load_store_register = emit.mir.instructions.items(.data)[inst].load_store_register;
switch (tag) {
.ldr => try emit.writeInstruction(Instruction.ldr(
load_store_register.rt,
.{ .register = .{ .rn = load_store_register.rn, .offset = load_store_register.offset } },
)),
.ldrb => try emit.writeInstruction(Instruction.ldrb(
load_store_register.rt,
load_store_register.rn,
.{ .offset = load_store_register.offset },
)),
.ldrh => try emit.writeInstruction(Instruction.ldrh(
load_store_register.rt,
load_store_register.rn,
.{ .offset = load_store_register.offset },
)),
.str => try emit.writeInstruction(Instruction.str(
load_store_register.rt,
load_store_register.rn,
.{ .offset = load_store_register.offset },
)),
.strb => try emit.writeInstruction(Instruction.strb(
load_store_register.rt,
load_store_register.rn,
.{ .offset = load_store_register.offset },
)),
.strh => try emit.writeInstruction(Instruction.strh(
load_store_register.rt,
load_store_register.rn,
.{ .offset = load_store_register.offset },
)),
else => unreachable,
}
}
fn mirMoveRegister(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const rr = emit.mir.instructions.items(.data)[inst].rr;
switch (tag) {
.mov_register => try emit.writeInstruction(Instruction.orr(rr.rd, .xzr, rr.rn, Instruction.Shift.none)),
.mov_to_from_sp => try emit.writeInstruction(Instruction.add(rr.rd, rr.rn, 0, false)),
else => unreachable,
}
}
fn mirMoveWideImmediate(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const r_imm16_sh = emit.mir.instructions.items(.data)[inst].r_imm16_sh;
switch (tag) {
.movz => try emit.writeInstruction(Instruction.movz(r_imm16_sh.rd, r_imm16_sh.imm16, @as(u6, r_imm16_sh.hw) << 4)),
.movk => try emit.writeInstruction(Instruction.movk(r_imm16_sh.rd, r_imm16_sh.imm16, @as(u6, r_imm16_sh.hw) << 4)),
else => unreachable,
}
}
fn mirNop(emit: *Emit) !void {
try emit.writeInstruction(Instruction.nop());
}

208
src/arch/aarch64/Mir.zig Normal file
View File

@@ -0,0 +1,208 @@
//! Machine Intermediate Representation.
//! This data is produced by AArch64 Codegen or AArch64 assembly parsing
//! These instructions have a 1:1 correspondence with machine code instructions
//! for the target. MIR can be lowered to source-annotated textual assembly code
//! instructions, or it can be lowered to machine code.
//! The main purpose of MIR is to postpone the assignment of offsets until Isel,
//! so that, for example, the smaller encodings of jump instructions can be used.
const Mir = @This();
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const bits = @import("bits.zig");
const Register = bits.Register;
instructions: std.MultiArrayList(Inst).Slice,
/// The meaning of this data is determined by `Inst.Tag` value.
extra: []const u32,
pub const Inst = struct {
tag: Tag,
/// The meaning of this depends on `tag`.
data: Data,
pub const Tag = enum(u16) {
/// Add (immediate)
add_immediate,
/// Branch
b,
/// Branch with Link
bl,
/// Branch with Link to Register
blr,
/// Breakpoint
brk,
/// Pseudo-instruction: Call extern
call_extern,
/// Pseudo-instruction: End of prologue
dbg_prologue_end,
/// Pseudo-instruction: Beginning of epilogue
dbg_epilogue_begin,
/// Pseudo-instruction: Update debug line
dbg_line,
/// Psuedo-instruction: Load memory
///
/// Payload is `LoadMemory`
load_memory,
/// Load Pair of Registers
ldp,
/// Load Register
// TODO: split into ldr_immediate and ldr_register
ldr,
/// Load Register Byte
// TODO: split into ldrb_immediate and ldrb_register
ldrb,
/// Load Register Halfword
// TODO: split into ldrh_immediate and ldrh_register
ldrh,
/// Move (to/from SP)
mov_to_from_sp,
/// Move (register)
mov_register,
/// Move wide with keep
movk,
/// Move wide with zero
movz,
/// No Operation
nop,
/// Return from subroutine
ret,
/// Store Pair of Registers
stp,
/// Store Register
// TODO: split into str_immediate and str_register
str,
/// Store Register Byte
// TODO: split into strb_immediate and strb_register
strb,
/// Store Register Halfword
// TODO: split into strh_immediate and strh_register
strh,
/// Subtract (immediate)
sub_immediate,
/// Supervisor Call
svc,
};
/// The position of an MIR instruction within the `Mir` instructions array.
pub const Index = u32;
/// All instructions have a 4-byte payload, which is contained within
/// this union. `Tag` determines which union field is active, as well as
/// how to interpret the data within.
pub const Data = union {
/// No additional data
///
/// Used by e.g. nop
nop: void,
/// Another instruction.
///
/// Used by e.g. b
inst: Index,
/// An extern function
///
/// Used by e.g. call_extern
extern_fn: u32,
/// A 16-bit immediate value.
///
/// Used by e.g. svc
imm16: u16,
/// Index into `extra`. Meaning of what can be found there is context-dependent.
///
/// Used by e.g. load_memory
payload: u32,
/// A register
///
/// Used by e.g. blr
reg: Register,
/// A register, an unsigned 16-bit immediate, and an optional shift
///
/// Used by e.g. movz
r_imm16_sh: struct {
rd: Register,
imm16: u16,
hw: u2 = 0,
},
/// Two registers
///
/// Used by e.g. mov_register
rr: struct {
rd: Register,
rn: Register,
},
/// Two registers, an unsigned 12-bit immediate, and an optional shift
///
/// Used by e.g. sub_immediate
rr_imm12_sh: struct {
rd: Register,
rn: Register,
imm12: u12,
sh: u1 = 0,
},
/// Three registers and a LoadStoreOffset
///
/// Used by e.g. str_register
load_store_register: struct {
rt: Register,
rn: Register,
offset: bits.Instruction.LoadStoreOffset,
},
/// Three registers and a LoadStorePairOffset
///
/// Used by e.g. stp
load_store_register_pair: struct {
rt: Register,
rt2: Register,
rn: Register,
offset: bits.Instruction.LoadStorePairOffset,
},
/// Debug info: line and column
///
/// Used by e.g. dbg_line
dbg_line_column: struct {
line: u32,
column: u32,
},
};
// Make sure we don't accidentally make instructions bigger than expected.
// Note that in Debug builds, Zig is allowed to insert a secret field for safety checks.
// comptime {
// if (builtin.mode != .Debug) {
// assert(@sizeOf(Inst) == 8);
// }
// }
};
pub fn deinit(mir: *Mir, gpa: *std.mem.Allocator) void {
mir.instructions.deinit(gpa);
gpa.free(mir.extra);
mir.* = undefined;
}
/// Returns the requested data, as well as the new index which is at the start of the
/// trailers for the object.
pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } {
const fields = std.meta.fields(T);
var i: usize = index;
var result: T = undefined;
inline for (fields) |field| {
@field(result, field.name) = switch (field.field_type) {
u32 => mir.extra[i],
i32 => @bitCast(i32, mir.extra[i]),
else => @compileError("bad field type"),
};
i += 1;
}
return .{
.data = result,
.end = i,
};
}
pub const LoadMemory = struct {
register: u32,
addr: u32,
};

View File

@@ -88,9 +88,10 @@ pub fn generateFunction(
.wasm64 => unreachable, // has its own code path
.arm => return Function(.arm).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
.armeb => return Function(.armeb).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
.aarch64 => return @import("arch/aarch64/CodeGen.zig").generate(.aarch64, bin_file, src_loc, func, air, liveness, code, debug_output),
.aarch64_be => return @import("arch/aarch64/CodeGen.zig").generate(.aarch64_be, bin_file, src_loc, func, air, liveness, code, debug_output),
.aarch64_32 => return @import("arch/aarch64/CodeGen.zig").generate(.aarch64_32, bin_file, src_loc, func, air, liveness, code, debug_output),
.aarch64,
.aarch64_be,
.aarch64_32,
=> return @import("arch/aarch64/CodeGen.zig").generate(bin_file, src_loc, func, air, liveness, code, debug_output),
//.arc => return Function(.arc).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
//.avr => return Function(.avr).generate(bin_file, src_loc, func, air, liveness, code, debug_output),
//.bpfel => return Function(.bpfel).generate(bin_file, src_loc, func, air, liveness, code, debug_output),