mirror of
https://github.com/ziglang/zig.git
synced 2026-02-21 16:54:52 +00:00
stage2 ARM: Introduce MIR
This commit is contained in:
parent
0e8673f534
commit
d94b032e92
File diff suppressed because it is too large
Load Diff
531
src/arch/arm/Emit.zig
Normal file
531
src/arch/arm/Emit.zig
Normal file
@ -0,0 +1,531 @@
|
||||
//! 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 log = std.log.scoped(.aarch64_emit);
|
||||
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,
|
||||
|
||||
/// The branch type of every branch
|
||||
branch_types: std.AutoHashMapUnmanaged(Mir.Inst.Index, BranchType) = .{},
|
||||
/// For every forward branch, maps the target instruction to a list of
|
||||
/// branches which branch to this target instruction
|
||||
branch_forward_origins: std.AutoHashMapUnmanaged(Mir.Inst.Index, std.ArrayListUnmanaged(Mir.Inst.Index)) = .{},
|
||||
/// For backward branches: stores the code offset of the target
|
||||
/// instruction
|
||||
///
|
||||
/// For forward branches: stores the code offset of the branch
|
||||
/// instruction
|
||||
code_offset_mapping: std.AutoHashMapUnmanaged(Mir.Inst.Index, usize) = .{},
|
||||
|
||||
const InnerError = error{
|
||||
OutOfMemory,
|
||||
EmitFail,
|
||||
};
|
||||
|
||||
const BranchType = enum {
|
||||
b,
|
||||
|
||||
fn default(tag: Mir.Inst.Tag) BranchType {
|
||||
return switch (tag) {
|
||||
.b => .b,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn emitMir(
|
||||
emit: *Emit,
|
||||
) !void {
|
||||
const mir_tags = emit.mir.instructions.items(.tag);
|
||||
|
||||
// Find smallest lowerings for branch instructions
|
||||
try emit.lowerBranches();
|
||||
|
||||
// Emit machine code
|
||||
for (mir_tags) |tag, index| {
|
||||
const inst = @intCast(u32, index);
|
||||
switch (tag) {
|
||||
.add => try emit.mirDataProcessing(inst),
|
||||
.@"and" => try emit.mirDataProcessing(inst),
|
||||
.cmp => try emit.mirDataProcessing(inst),
|
||||
.eor => try emit.mirDataProcessing(inst),
|
||||
.mov => try emit.mirDataProcessing(inst),
|
||||
.mvn => try emit.mirDataProcessing(inst),
|
||||
.orr => try emit.mirDataProcessing(inst),
|
||||
.rsb => try emit.mirDataProcessing(inst),
|
||||
.sub => try emit.mirDataProcessing(inst),
|
||||
|
||||
.asr => try emit.mirShift(inst),
|
||||
.lsl => try emit.mirShift(inst),
|
||||
.lsr => try emit.mirShift(inst),
|
||||
|
||||
.b => try emit.mirBranch(inst),
|
||||
|
||||
.bkpt => try emit.mirExceptionGeneration(inst),
|
||||
|
||||
.blx => try emit.mirBranchExchange(inst),
|
||||
.bx => try emit.mirBranchExchange(inst),
|
||||
|
||||
.dbg_line => try emit.mirDbgLine(inst),
|
||||
|
||||
.dbg_prologue_end => try emit.mirDebugPrologueEnd(),
|
||||
|
||||
.dbg_epilogue_begin => try emit.mirDebugEpilogueBegin(),
|
||||
|
||||
.ldr => try emit.mirLoadStore(inst),
|
||||
.ldrb => try emit.mirLoadStore(inst),
|
||||
.str => try emit.mirLoadStore(inst),
|
||||
.strb => try emit.mirLoadStore(inst),
|
||||
|
||||
.ldrh => try emit.mirLoadStoreExtra(inst),
|
||||
.strh => try emit.mirLoadStoreExtra(inst),
|
||||
|
||||
.movw => try emit.mirSpecialMove(inst),
|
||||
.movt => try emit.mirSpecialMove(inst),
|
||||
|
||||
.mul => try emit.mirMultiply(inst),
|
||||
|
||||
.nop => try emit.mirNop(),
|
||||
|
||||
.pop => try emit.mirBlockDataTransfer(inst),
|
||||
.push => try emit.mirBlockDataTransfer(inst),
|
||||
|
||||
.svc => try emit.mirSupervisorCall(inst),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(emit: *Emit) void {
|
||||
var iter = emit.branch_forward_origins.valueIterator();
|
||||
while (iter.next()) |origin_list| {
|
||||
origin_list.deinit(emit.bin_file.allocator);
|
||||
}
|
||||
|
||||
emit.branch_types.deinit(emit.bin_file.allocator);
|
||||
emit.branch_forward_origins.deinit(emit.bin_file.allocator);
|
||||
emit.code_offset_mapping.deinit(emit.bin_file.allocator);
|
||||
emit.* = undefined;
|
||||
}
|
||||
|
||||
fn optimalBranchType(emit: *Emit, tag: Mir.Inst.Tag, offset: i64) !BranchType {
|
||||
assert(std.mem.isAlignedGeneric(i64, offset, 4)); // misaligned offset
|
||||
|
||||
switch (tag) {
|
||||
.b => {
|
||||
if (std.math.cast(i24, @divExact(offset, 4))) |_| {
|
||||
return BranchType.b;
|
||||
} else |_| {
|
||||
return emit.fail("TODO support larger branches", .{});
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn instructionSize(emit: *Emit, inst: Mir.Inst.Index) usize {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
|
||||
if (isBranch(tag)) {
|
||||
switch (emit.branch_types.get(inst).?) {
|
||||
.b => return 4,
|
||||
}
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
.dbg_line,
|
||||
.dbg_epilogue_begin,
|
||||
.dbg_prologue_end,
|
||||
=> return 0,
|
||||
else => return 4,
|
||||
}
|
||||
}
|
||||
|
||||
fn isBranch(tag: Mir.Inst.Tag) bool {
|
||||
return switch (tag) {
|
||||
.b => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn branchTarget(emit: *Emit, inst: Mir.Inst.Index) Mir.Inst.Index {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
|
||||
switch (tag) {
|
||||
.b => return emit.mir.instructions.items(.data)[inst].inst,
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn lowerBranches(emit: *Emit) !void {
|
||||
const mir_tags = emit.mir.instructions.items(.tag);
|
||||
const allocator = emit.bin_file.allocator;
|
||||
|
||||
// First pass: Note down all branches and their target
|
||||
// instructions, i.e. populate branch_types,
|
||||
// branch_forward_origins, and code_offset_mapping
|
||||
//
|
||||
// TODO optimization opportunity: do this in codegen while
|
||||
// generating MIR
|
||||
for (mir_tags) |tag, index| {
|
||||
const inst = @intCast(u32, index);
|
||||
if (isBranch(tag)) {
|
||||
const target_inst = emit.branchTarget(inst);
|
||||
|
||||
// Remember this branch instruction
|
||||
try emit.branch_types.put(allocator, inst, BranchType.default(tag));
|
||||
|
||||
// Forward branches require some extra stuff: We only
|
||||
// know their offset once we arrive at the target
|
||||
// instruction. Therefore, we need to be able to
|
||||
// access the branch instruction when we visit the
|
||||
// target instruction in order to manipulate its type
|
||||
// etc.
|
||||
if (target_inst > inst) {
|
||||
// Remember the branch instruction index
|
||||
try emit.code_offset_mapping.put(allocator, inst, 0);
|
||||
|
||||
if (emit.branch_forward_origins.getPtr(target_inst)) |origin_list| {
|
||||
try origin_list.append(allocator, inst);
|
||||
} else {
|
||||
var origin_list: std.ArrayListUnmanaged(Mir.Inst.Index) = .{};
|
||||
try origin_list.append(allocator, inst);
|
||||
try emit.branch_forward_origins.put(allocator, target_inst, origin_list);
|
||||
}
|
||||
}
|
||||
|
||||
// Remember the target instruction index so that we
|
||||
// update the real code offset in all future passes
|
||||
//
|
||||
// putNoClobber may not be used as the put operation
|
||||
// may clobber the entry when multiple branches branch
|
||||
// to the same target instruction
|
||||
try emit.code_offset_mapping.put(allocator, target_inst, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Further passes: Until all branches are lowered, interate
|
||||
// through all instructions and calculate new offsets and
|
||||
// potentially new branch types
|
||||
var all_branches_lowered = false;
|
||||
while (!all_branches_lowered) {
|
||||
all_branches_lowered = true;
|
||||
var current_code_offset: usize = 0;
|
||||
|
||||
for (mir_tags) |tag, index| {
|
||||
const inst = @intCast(u32, index);
|
||||
|
||||
// If this instruction contained in the code offset
|
||||
// mapping (when it is a target of a branch or if it is a
|
||||
// forward branch), update the code offset
|
||||
if (emit.code_offset_mapping.getPtr(inst)) |offset| {
|
||||
offset.* = current_code_offset;
|
||||
}
|
||||
|
||||
// If this instruction is a backward branch, calculate the
|
||||
// offset, which may potentially update the branch type
|
||||
if (isBranch(tag)) {
|
||||
const target_inst = emit.branchTarget(inst);
|
||||
if (target_inst < inst) {
|
||||
const target_offset = emit.code_offset_mapping.get(target_inst).?;
|
||||
const offset = @intCast(i64, target_offset) - @intCast(i64, current_code_offset + 8);
|
||||
const branch_type = emit.branch_types.getPtr(inst).?;
|
||||
const optimal_branch_type = try emit.optimalBranchType(tag, offset);
|
||||
if (branch_type.* != optimal_branch_type) {
|
||||
branch_type.* = optimal_branch_type;
|
||||
all_branches_lowered = false;
|
||||
}
|
||||
|
||||
log.debug("lowerBranches: branch {} has offset {}", .{ inst, offset });
|
||||
}
|
||||
}
|
||||
|
||||
// If this instruction is the target of one or more
|
||||
// forward branches, calculate the offset, which may
|
||||
// potentially update the branch type
|
||||
if (emit.branch_forward_origins.get(inst)) |origin_list| {
|
||||
for (origin_list.items) |forward_branch_inst| {
|
||||
const branch_tag = emit.mir.instructions.items(.tag)[forward_branch_inst];
|
||||
const forward_branch_inst_offset = emit.code_offset_mapping.get(forward_branch_inst).?;
|
||||
const offset = @intCast(i64, current_code_offset) - @intCast(i64, forward_branch_inst_offset + 8);
|
||||
const branch_type = emit.branch_types.getPtr(forward_branch_inst).?;
|
||||
const optimal_branch_type = try emit.optimalBranchType(branch_tag, offset);
|
||||
if (branch_type.* != optimal_branch_type) {
|
||||
branch_type.* = optimal_branch_type;
|
||||
all_branches_lowered = false;
|
||||
}
|
||||
|
||||
log.debug("lowerBranches: branch {} has offset {}", .{ forward_branch_inst, offset });
|
||||
}
|
||||
}
|
||||
|
||||
// Increment code offset
|
||||
current_code_offset += emit.instructionSize(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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 mirDataProcessing(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const cond = emit.mir.instructions.items(.cond)[inst];
|
||||
const rr_op = emit.mir.instructions.items(.data)[inst].rr_op;
|
||||
|
||||
switch (tag) {
|
||||
.add => try emit.writeInstruction(Instruction.add(cond, rr_op.rd, rr_op.rn, rr_op.op)),
|
||||
.@"and" => try emit.writeInstruction(Instruction.@"and"(cond, rr_op.rd, rr_op.rn, rr_op.op)),
|
||||
.cmp => try emit.writeInstruction(Instruction.cmp(cond, rr_op.rn, rr_op.op)),
|
||||
.eor => try emit.writeInstruction(Instruction.eor(cond, rr_op.rd, rr_op.rn, rr_op.op)),
|
||||
.mov => try emit.writeInstruction(Instruction.mov(cond, rr_op.rd, rr_op.op)),
|
||||
.mvn => try emit.writeInstruction(Instruction.mvn(cond, rr_op.rd, rr_op.op)),
|
||||
.orr => try emit.writeInstruction(Instruction.orr(cond, rr_op.rd, rr_op.rn, rr_op.op)),
|
||||
.rsb => try emit.writeInstruction(Instruction.rsb(cond, rr_op.rd, rr_op.rn, rr_op.op)),
|
||||
.sub => try emit.writeInstruction(Instruction.sub(cond, rr_op.rd, rr_op.rn, rr_op.op)),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn mirShift(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const cond = emit.mir.instructions.items(.cond)[inst];
|
||||
const rr_shift = emit.mir.instructions.items(.data)[inst].rr_shift;
|
||||
|
||||
switch (tag) {
|
||||
.asr => try emit.writeInstruction(Instruction.asr(cond, rr_shift.rd, rr_shift.rm, rr_shift.shift_amount)),
|
||||
.lsl => try emit.writeInstruction(Instruction.lsl(cond, rr_shift.rd, rr_shift.rm, rr_shift.shift_amount)),
|
||||
.lsr => try emit.writeInstruction(Instruction.lsr(cond, rr_shift.rd, rr_shift.rm, rr_shift.shift_amount)),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const cond = emit.mir.instructions.items(.cond)[inst];
|
||||
const target_inst = emit.mir.instructions.items(.data)[inst].inst;
|
||||
|
||||
const offset = @intCast(i64, emit.code_offset_mapping.get(target_inst).?) - @intCast(i64, emit.code.items.len + 8);
|
||||
const branch_type = emit.branch_types.get(inst).?;
|
||||
|
||||
switch (branch_type) {
|
||||
.b => switch (tag) {
|
||||
.b => try emit.writeInstruction(Instruction.b(cond, @intCast(i26, offset))),
|
||||
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) {
|
||||
.bkpt => try emit.writeInstruction(Instruction.bkpt(imm16)),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn mirBranchExchange(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const cond = emit.mir.instructions.items(.cond)[inst];
|
||||
const reg = emit.mir.instructions.items(.data)[inst].reg;
|
||||
|
||||
switch (tag) {
|
||||
.blx => try emit.writeInstruction(Instruction.blx(cond, reg)),
|
||||
.bx => try emit.writeInstruction(Instruction.bx(cond, reg)),
|
||||
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(emit: *Emit) !void {
|
||||
switch (emit.debug_output) {
|
||||
.dwarf => |dbg_out| {
|
||||
try dbg_out.dbg_line.append(DW.LNS.set_prologue_end);
|
||||
try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
|
||||
},
|
||||
.plan9 => {},
|
||||
.none => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn mirDebugEpilogueBegin(emit: *Emit) !void {
|
||||
switch (emit.debug_output) {
|
||||
.dwarf => |dbg_out| {
|
||||
try dbg_out.dbg_line.append(DW.LNS.set_epilogue_begin);
|
||||
try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
|
||||
},
|
||||
.plan9 => {},
|
||||
.none => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn mirLoadStore(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const cond = emit.mir.instructions.items(.cond)[inst];
|
||||
const rr_offset = emit.mir.instructions.items(.data)[inst].rr_offset;
|
||||
|
||||
switch (tag) {
|
||||
.ldr => try emit.writeInstruction(Instruction.ldr(cond, rr_offset.rt, rr_offset.rn, rr_offset.offset)),
|
||||
.ldrb => try emit.writeInstruction(Instruction.ldrb(cond, rr_offset.rt, rr_offset.rn, rr_offset.offset)),
|
||||
.str => try emit.writeInstruction(Instruction.str(cond, rr_offset.rt, rr_offset.rn, rr_offset.offset)),
|
||||
.strb => try emit.writeInstruction(Instruction.strb(cond, rr_offset.rt, rr_offset.rn, rr_offset.offset)),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn mirLoadStoreExtra(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const cond = emit.mir.instructions.items(.cond)[inst];
|
||||
const rr_extra_offset = emit.mir.instructions.items(.data)[inst].rr_extra_offset;
|
||||
|
||||
switch (tag) {
|
||||
.ldrh => try emit.writeInstruction(Instruction.ldrh(cond, rr_extra_offset.rt, rr_extra_offset.rn, rr_extra_offset.offset)),
|
||||
.strh => try emit.writeInstruction(Instruction.strh(cond, rr_extra_offset.rt, rr_extra_offset.rn, rr_extra_offset.offset)),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn mirSpecialMove(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const cond = emit.mir.instructions.items(.cond)[inst];
|
||||
const r_imm16 = emit.mir.instructions.items(.data)[inst].r_imm16;
|
||||
|
||||
switch (tag) {
|
||||
.movw => try emit.writeInstruction(Instruction.movw(cond, r_imm16.rd, r_imm16.imm16)),
|
||||
.movt => try emit.writeInstruction(Instruction.movt(cond, r_imm16.rd, r_imm16.imm16)),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn mirMultiply(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const cond = emit.mir.instructions.items(.cond)[inst];
|
||||
const rrr = emit.mir.instructions.items(.data)[inst].rrr;
|
||||
|
||||
switch (tag) {
|
||||
.mul => try emit.writeInstruction(Instruction.mul(cond, rrr.rd, rrr.rn, rrr.rm)),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn mirNop(emit: *Emit) !void {
|
||||
try emit.writeInstruction(Instruction.nop());
|
||||
}
|
||||
|
||||
fn mirBlockDataTransfer(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const cond = emit.mir.instructions.items(.cond)[inst];
|
||||
const register_list = emit.mir.instructions.items(.data)[inst].register_list;
|
||||
|
||||
switch (tag) {
|
||||
.pop => try emit.writeInstruction(Instruction.ldm(cond, .sp, true, register_list)),
|
||||
.push => try emit.writeInstruction(Instruction.stmdb(cond, .sp, true, register_list)),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn mirSupervisorCall(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const cond = emit.mir.instructions.items(.cond)[inst];
|
||||
const imm24 = emit.mir.instructions.items(.data)[inst].imm24;
|
||||
|
||||
switch (tag) {
|
||||
.svc => try emit.writeInstruction(Instruction.svc(cond, imm24)),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
220
src/arch/arm/Mir.zig
Normal file
220
src/arch/arm/Mir.zig
Normal file
@ -0,0 +1,220 @@
|
||||
//! Machine Intermediate Representation.
|
||||
//! This data is produced by ARM Codegen or ARM 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,
|
||||
cond: bits.Condition,
|
||||
/// The meaning of this depends on `tag`.
|
||||
data: Data,
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
/// Add
|
||||
add,
|
||||
/// Bitwise AND
|
||||
@"and",
|
||||
/// Arithmetic Shift Right
|
||||
asr,
|
||||
/// Branch
|
||||
b,
|
||||
/// Breakpoint
|
||||
bkpt,
|
||||
/// Branch with Link and Exchange
|
||||
blx,
|
||||
/// Branch and Exchange
|
||||
bx,
|
||||
/// Compare
|
||||
cmp,
|
||||
/// Pseudo-instruction: End of prologue
|
||||
dbg_prologue_end,
|
||||
/// Pseudo-instruction: Beginning of epilogue
|
||||
dbg_epilogue_begin,
|
||||
/// Pseudo-instruction: Update debug line
|
||||
dbg_line,
|
||||
/// Bitwise Exclusive OR
|
||||
eor,
|
||||
/// Load Register
|
||||
ldr,
|
||||
/// Load Register Byte
|
||||
ldrb,
|
||||
/// Load Register Halfword
|
||||
ldrh,
|
||||
/// Logical Shift Left
|
||||
lsl,
|
||||
/// Logical Shift Right
|
||||
lsr,
|
||||
/// Move
|
||||
mov,
|
||||
/// Move
|
||||
movw,
|
||||
/// Move Top
|
||||
movt,
|
||||
/// Multiply
|
||||
mul,
|
||||
/// Bitwise NOT
|
||||
mvn,
|
||||
/// No Operation
|
||||
nop,
|
||||
/// Bitwise OR
|
||||
orr,
|
||||
/// Pop multiple registers from Stack
|
||||
pop,
|
||||
/// Push multiple registers to Stack
|
||||
push,
|
||||
/// Reverse Subtract
|
||||
rsb,
|
||||
/// Store Register
|
||||
str,
|
||||
/// Store Register Byte
|
||||
strb,
|
||||
/// Store Register Halfword
|
||||
strh,
|
||||
/// Subtract
|
||||
sub,
|
||||
/// Supervisor Call
|
||||
svc,
|
||||
};
|
||||
|
||||
/// The position of an MIR instruction within the `Mir` instructions array.
|
||||
pub const Index = u32;
|
||||
|
||||
/// All instructions have a 8-byte payload, which is contained within
|
||||
/// this union. `Tag` determines which union field is active, as well as
|
||||
/// how to interpret the data within.
|
||||
// TODO flatten down Data (remove use of tagged unions) to make it
|
||||
// 8 bytes only
|
||||
pub const Data = union {
|
||||
/// No additional data
|
||||
///
|
||||
/// Used by e.g. nop
|
||||
nop: void,
|
||||
/// Another instruction
|
||||
///
|
||||
/// Used by e.g. b
|
||||
inst: Index,
|
||||
/// A 16-bit immediate value.
|
||||
///
|
||||
/// Used by e.g. bkpt
|
||||
imm16: u16,
|
||||
/// A 24-bit immediate value.
|
||||
///
|
||||
/// Used by e.g. svc
|
||||
imm24: u24,
|
||||
/// 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. blx
|
||||
reg: Register,
|
||||
/// A register and a 16-bit unsigned immediate
|
||||
///
|
||||
/// Used by e.g. movw
|
||||
r_imm16: struct {
|
||||
rd: Register,
|
||||
imm16: u16,
|
||||
},
|
||||
/// Two registers and a shift amount
|
||||
///
|
||||
/// Used by e.g. lsl
|
||||
rr_shift: struct {
|
||||
rd: Register,
|
||||
rm: Register,
|
||||
shift_amount: bits.Instruction.ShiftAmount,
|
||||
},
|
||||
/// Two registers and an operand
|
||||
///
|
||||
/// Used by e.g. sub
|
||||
rr_op: struct {
|
||||
rd: Register,
|
||||
rn: Register,
|
||||
op: bits.Instruction.Operand,
|
||||
},
|
||||
/// Two registers and an offset
|
||||
///
|
||||
/// Used by e.g. ldr
|
||||
rr_offset: struct {
|
||||
rt: Register,
|
||||
rn: Register,
|
||||
offset: bits.Instruction.OffsetArgs,
|
||||
},
|
||||
/// Two registers and an extra load/store offset
|
||||
///
|
||||
/// Used by e.g. ldrh
|
||||
rr_extra_offset: struct {
|
||||
rt: Register,
|
||||
rn: Register,
|
||||
offset: bits.Instruction.ExtraLoadStoreOffsetArgs,
|
||||
},
|
||||
/// Three registers
|
||||
///
|
||||
/// Used by e.g. mul
|
||||
rrr: struct {
|
||||
rd: Register,
|
||||
rn: Register,
|
||||
rm: Register,
|
||||
},
|
||||
/// An unordered list of registers
|
||||
///
|
||||
/// Used by e.g. push
|
||||
register_list: bits.Instruction.RegisterList,
|
||||
/// 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(Data) == 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,
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user