1//! This file contains the functionality for lowering AArch64 MIR into
2//! machine code
3
4const Emit = @This();
5const std = @import("std");
6const math = std.math;
7const Mir = @import("Mir.zig");
8const bits = @import("bits.zig");
9const link = @import("../../link.zig");
10const Module = @import("../../Module.zig");
11const ErrorMsg = Module.ErrorMsg;
12const assert = std.debug.assert;
13const DW = std.dwarf;
14const leb128 = std.leb;
15const Instruction = bits.Instruction;
16const Register = bits.Register;
17const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
18
19mir: Mir,
20bin_file: *link.File,
21debug_output: DebugInfoOutput,
22target: *const std.Target,
23err_msg: ?*ErrorMsg = null,
24src_loc: Module.SrcLoc,
25code: *std.ArrayList(u8),
26
27prev_di_line: u32,
28prev_di_column: u32,
29/// Relative to the beginning of `code`.
30prev_di_pc: usize,
31
32const InnerError = error{
33    OutOfMemory,
34    EmitFail,
35};
36
37pub fn emitMir(
38    emit: *Emit,
39) InnerError!void {
40    const mir_tags = emit.mir.instructions.items(.tag);
41
42    // Emit machine code
43    for (mir_tags) |tag, index| {
44        const inst = @intCast(u32, index);
45        switch (tag) {
46            .addi => try emit.mirIType(inst),
47            .jalr => try emit.mirIType(inst),
48            .ld => try emit.mirIType(inst),
49            .sd => try emit.mirIType(inst),
50
51            .ebreak => try emit.mirSystem(inst),
52            .ecall => try emit.mirSystem(inst),
53
54            .dbg_line => try emit.mirDbgLine(inst),
55
56            .dbg_prologue_end => try emit.mirDebugPrologueEnd(),
57            .dbg_epilogue_begin => try emit.mirDebugEpilogueBegin(),
58
59            .nop => try emit.mirNop(inst),
60            .ret => try emit.mirNop(inst),
61
62            .lui => try emit.mirUType(inst),
63        }
64    }
65}
66
67pub fn deinit(emit: *Emit) void {
68    emit.* = undefined;
69}
70
71fn writeInstruction(emit: *Emit, instruction: Instruction) !void {
72    const endian = emit.target.cpu.arch.endian();
73    std.mem.writeInt(u32, try emit.code.addManyAsArray(4), instruction.toU32(), endian);
74}
75
76fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
77    @setCold(true);
78    assert(emit.err_msg == null);
79    emit.err_msg = try ErrorMsg.create(emit.bin_file.allocator, emit.src_loc, format, args);
80    return error.EmitFail;
81}
82
83fn dbgAdvancePCAndLine(self: *Emit, line: u32, column: u32) !void {
84    const delta_line = @intCast(i32, line) - @intCast(i32, self.prev_di_line);
85    const delta_pc: usize = self.code.items.len - self.prev_di_pc;
86    switch (self.debug_output) {
87        .dwarf => |dbg_out| {
88            // TODO Look into using the DWARF special opcodes to compress this data.
89            // It lets you emit single-byte opcodes that add different numbers to
90            // both the PC and the line number at the same time.
91            try dbg_out.dbg_line.ensureUnusedCapacity(11);
92            dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_pc);
93            leb128.writeULEB128(dbg_out.dbg_line.writer(), delta_pc) catch unreachable;
94            if (delta_line != 0) {
95                dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_line);
96                leb128.writeILEB128(dbg_out.dbg_line.writer(), delta_line) catch unreachable;
97            }
98            dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.copy);
99            self.prev_di_pc = self.code.items.len;
100            self.prev_di_line = line;
101            self.prev_di_column = column;
102            self.prev_di_pc = self.code.items.len;
103        },
104        .plan9 => |dbg_out| {
105            if (delta_pc <= 0) return; // only do this when the pc changes
106            // we have already checked the target in the linker to make sure it is compatable
107            const quant = @import("../../link/Plan9/aout.zig").getPCQuant(self.target.cpu.arch) catch unreachable;
108
109            // increasing the line number
110            try @import("../../link/Plan9.zig").changeLine(dbg_out.dbg_line, delta_line);
111            // increasing the pc
112            const d_pc_p9 = @intCast(i64, delta_pc) - quant;
113            if (d_pc_p9 > 0) {
114                // minus one because if its the last one, we want to leave space to change the line which is one quanta
115                try dbg_out.dbg_line.append(@intCast(u8, @divExact(d_pc_p9, quant) + 128) - quant);
116                if (dbg_out.pcop_change_index.*) |pci|
117                    dbg_out.dbg_line.items[pci] += 1;
118                dbg_out.pcop_change_index.* = @intCast(u32, dbg_out.dbg_line.items.len - 1);
119            } else if (d_pc_p9 == 0) {
120                // we don't need to do anything, because adding the quant does it for us
121            } else unreachable;
122            if (dbg_out.start_line.* == null)
123                dbg_out.start_line.* = self.prev_di_line;
124            dbg_out.end_line.* = line;
125            // only do this if the pc changed
126            self.prev_di_line = line;
127            self.prev_di_column = column;
128            self.prev_di_pc = self.code.items.len;
129        },
130        .none => {},
131    }
132}
133
134fn mirIType(emit: *Emit, inst: Mir.Inst.Index) !void {
135    const tag = emit.mir.instructions.items(.tag)[inst];
136    const i_type = emit.mir.instructions.items(.data)[inst].i_type;
137
138    switch (tag) {
139        .addi => try emit.writeInstruction(Instruction.addi(i_type.rd, i_type.rs1, i_type.imm12)),
140        .jalr => try emit.writeInstruction(Instruction.jalr(i_type.rd, i_type.imm12, i_type.rs1)),
141        .ld => try emit.writeInstruction(Instruction.ld(i_type.rd, i_type.imm12, i_type.rs1)),
142        .sd => try emit.writeInstruction(Instruction.sd(i_type.rd, i_type.imm12, i_type.rs1)),
143        else => unreachable,
144    }
145}
146
147fn mirSystem(emit: *Emit, inst: Mir.Inst.Index) !void {
148    const tag = emit.mir.instructions.items(.tag)[inst];
149
150    switch (tag) {
151        .ebreak => try emit.writeInstruction(Instruction.ebreak),
152        .ecall => try emit.writeInstruction(Instruction.ecall),
153        else => unreachable,
154    }
155}
156
157fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) !void {
158    const tag = emit.mir.instructions.items(.tag)[inst];
159    const dbg_line_column = emit.mir.instructions.items(.data)[inst].dbg_line_column;
160
161    switch (tag) {
162        .dbg_line => try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column),
163        else => unreachable,
164    }
165}
166
167fn mirDebugPrologueEnd(self: *Emit) !void {
168    switch (self.debug_output) {
169        .dwarf => |dbg_out| {
170            try dbg_out.dbg_line.append(DW.LNS.set_prologue_end);
171            try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column);
172        },
173        .plan9 => {},
174        .none => {},
175    }
176}
177
178fn mirDebugEpilogueBegin(self: *Emit) !void {
179    switch (self.debug_output) {
180        .dwarf => |dbg_out| {
181            try dbg_out.dbg_line.append(DW.LNS.set_epilogue_begin);
182            try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column);
183        },
184        .plan9 => {},
185        .none => {},
186    }
187}
188
189fn mirUType(emit: *Emit, inst: Mir.Inst.Index) !void {
190    const tag = emit.mir.instructions.items(.tag)[inst];
191    const u_type = emit.mir.instructions.items(.data)[inst].u_type;
192
193    switch (tag) {
194        .lui => try emit.writeInstruction(Instruction.lui(u_type.rd, u_type.imm20)),
195        else => unreachable,
196    }
197}
198
199fn mirNop(emit: *Emit, inst: Mir.Inst.Index) !void {
200    const tag = emit.mir.instructions.items(.tag)[inst];
201
202    switch (tag) {
203        .nop => try emit.writeInstruction(Instruction.addi(.zero, .zero, 0)),
204        .ret => try emit.writeInstruction(Instruction.jalr(.zero, 0, .ra)),
205        else => unreachable,
206    }
207}
208