nativity/src/backend/x86_64.zig
David Gonzalez Martin 65d248588d exit process
2023-09-24 21:45:52 -06:00

415 lines
14 KiB
Zig

const std = @import("std");
const assert = std.debug.assert;
const print = std.debug.print;
const emit = @import("emit.zig");
const ir = @import("./intermediate_representation.zig");
const InstructionSelector = emit.InstructionSelector(Instruction);
const Size = enum(u2) {
one = 0,
two = 1,
four = 2,
eight = 3,
};
pub fn selectInstruction(instruction_selector: *InstructionSelector, function: *InstructionSelector.Function, intermediate: *ir.Result, instruction: ir.Instruction) !void {
switch (instruction) {
.@"unreachable" => try function.instructions.append(instruction_selector.allocator, .{ .ud2 = {} }),
.load => |load_index| {
const load = intermediate.loads.get(load_index).*;
const load_value = intermediate.values.get(load.value).*;
switch (load_value) {
.integer => |integer| {
_ = integer;
unreachable;
},
else => |t| @panic(@tagName(t)),
}
unreachable;
},
.syscall => |syscall_index| {
const syscall = intermediate.syscalls.get(syscall_index);
for (syscall.arguments.items, syscall_registers[0..syscall.arguments.items.len]) |argument_index, syscall_register| {
const argument = intermediate.values.get(argument_index).*;
switch (argument) {
.integer => |integer| {
if (integer.value == 0) {
try function.instructions.append(instruction_selector.allocator, .{
.xor_rm_r = .{
.destination = @enumFromInt(@intFromEnum(syscall_register)),
.source = @enumFromInt(@intFromEnum(syscall_register)),
.size = .four,
.direct = true,
},
});
} else if (integer.value <= std.math.maxInt(u32)) {
try function.instructions.append(instruction_selector.allocator, .{
.mov_r_imm = .{
.register_size = .four,
.register = @enumFromInt(@intFromEnum(syscall_register)),
.immediate = argument_index,
.immediate_size = .four,
},
});
// TODO
} else unreachable;
// if (integer.value == 0) {
// try function.instructions.append(instruction_selector.allocator, .{
// .xor_reg32_reg32 = .{
// .destination = syscall_register,
// .source = syscall_register,
// },
// });
// } else if (integer.value < std.math.maxInt(u32)) {
// try function.instructions.append(instruction_selector.allocator, .{
// .mov_reg_imm32 = .{
// .destination = syscall_register,
// .source = @intCast(integer.value),
// },
// });
// } else {
// unreachable;
// }
},
else => |t| @panic(@tagName(t)),
}
}
try function.instructions.append(instruction_selector.allocator, .{
.syscall = {},
});
},
.phi => unreachable,
.ret => unreachable,
.jump => |jump_index| {
_ = jump_index;
// const jump = intermediate.jumps.get(jump_index);
// const relocation = LocalRelative{
// .instruction = .jmp_rel_8,
// .source = @intCast(function.block_map.get(jump.source) orelse unreachable),
// .destination = @intCast(function.block_map.get(jump.destination) orelse unreachable),
// .offset_in_block = function.block_byte_count,
// };
// const index = function.instructions.items.len;
// try function.relocations.append(instruction_selector.allocator, @intCast(index));
// try function.instructions.append(instruction_selector.allocator, .{
// .jmp_rel_8 = relocation,
// });
unreachable;
},
}
}
const RegisterImmediate = struct {
immediate: ir.Value.Index,
register: GPRegister,
register_size: Size,
immediate_size: Size,
};
const RegisterMemoryRegister = struct {
destination: GPRegister,
source: GPRegister,
size: Size,
direct: bool,
};
const RmResult = struct {
rex: Rex,
mod_rm: ModRm,
};
const RmAndRexArguments = packed struct {
rm: GPRegister,
reg: GPRegister,
direct: bool,
bit64: bool,
sib: bool,
};
// fn computeRmAndRex(args: RmAndRexArguments) RmResult {
// _ = register_memory_register;
// const rex_byte = Rex{
// .b = @intFromEnum(args.rm) > std.math.maxInt(u3),
// .x = args.sib,
// .r = @intFromEnum(args.reg) > std.math.maxInt(u3),
// .w = args.bit64,
// };
// var rex_byte = std.mem.zeroes(Rex);
// if (@intFromEnum(rm) > std.math.maxInt(u3))
// }
fn emitImmediate(result: *emit.Result, intermediate: *ir.Result, value_index: ir.Value.Index, size: Size) void {
const value = intermediate.values.get(value_index);
const integer = value.integer.value;
const integer_bytes = switch (size) {
.one => std.mem.asBytes(&@as(u8, @intCast(integer))),
.two => std.mem.asBytes(&@as(u16, @intCast(integer))),
.four => std.mem.asBytes(&@as(u32, @intCast(integer))),
.eight => std.mem.asBytes(&@as(u64, @intCast(integer))),
};
result.appendCode(integer_bytes);
}
const ModRm = packed struct(u8) {
rm: u3,
reg: u3,
mod: u2,
};
pub fn emitInstruction(result: *emit.Result, instruction: Instruction, intermediate: *ir.Result) void {
switch (instruction) {
inline .xor_rm_r => |register_memory_register, tag| {
const rm = register_memory_register.destination;
const reg = register_memory_register.source;
const rex_byte = Rex{
.b = @intFromEnum(rm) > std.math.maxInt(u3),
.x = false, //args.sib,
.r = @intFromEnum(reg) > std.math.maxInt(u3),
.w = register_memory_register.size == .eight,
};
if (@as(u4, @truncate(@as(u8, @bitCast(rex_byte)))) != 0) {
result.appendCodeByte(@bitCast(rex_byte));
}
const modrm = ModRm{
.rm = @truncate(@intFromEnum(rm)),
.reg = @truncate(@intFromEnum(reg)),
.mod = @as(u2, @intFromBool(register_memory_register.direct)) << 1 | @intFromBool(register_memory_register.direct),
};
// _ = modrm;
const opcode = tag.getOpcode(&.{
.{
.register_memory = .{
.value = register_memory_register.destination,
.size = register_memory_register.size,
.direct = register_memory_register.direct,
},
},
.{
.register = .{
.value = register_memory_register.source,
.size = register_memory_register.size,
},
},
});
result.appendCode(opcode);
result.appendCodeByte(@bitCast(modrm));
},
inline .mov_r_imm => |register_immediate, tag| {
const opcode = tag.getOpcode(&.{
.{
.register = .{
.value = register_immediate.register,
.size = register_immediate.register_size,
},
},
.{
.immediate = register_immediate.immediate_size,
},
});
assert(opcode.len == 1);
const opcode_byte = opcode[0] | @intFromEnum(register_immediate.register);
result.appendCodeByte(opcode_byte);
emitImmediate(result, intermediate, register_immediate.immediate, register_immediate.immediate_size);
},
// .jmp_rel_8 => unreachable, //result.appendOnlyOpcodeSkipInstructionBytes(instruction),
// inline .mov_reg_imm32 => |content, tag| {
// _ = tag;
// _ = content;
// // const descriptor = instruction_descriptors.get(tag);
// // result.writeOpcode(descriptor.opcode);
// // result.appendCodeByte(descriptor.getOpcode()[0] | @intFromEnum(content.destination));
// // result.appendCode(std.mem.asBytes(&content.source));
// unreachable;
// },
// inline .xor_reg32_reg32 => |content, tag| {
// _ = tag;
// _ = content;
// // const descriptor = instruction_descriptors.get(tag);
// // result.appendCodeByte(descriptor.getOpcode()[0]);
// // result.appendCodeByte(0xc0 | @as(u8, @intFromEnum(content.source)) << 4 | @intFromEnum(content.destination));
// unreachable;
// },
inline .syscall, .ud2 => |_, tag| {
const opcode = tag.getOpcode(&.{});
result.appendCode(opcode);
},
// else => unreachable,
}
}
pub const Instruction = union(Id) {
xor_rm_r: RegisterMemoryRegister,
mov_r_imm: RegisterImmediate,
// jmp_rel_8: LocalRelative,
// mov_reg_imm32: struct {
// destination: GPRegister,
// source: u32,
// },
// xor_reg32_reg32: struct {
// destination: GPRegister,
// source: GPRegister,
// },
syscall,
ud2,
const Id = enum {
xor_rm_r,
mov_r_imm,
// jmp_rel_8,
// mov_reg_imm32,
// xor_reg32_reg32,
syscall,
ud2,
fn getOpcode(comptime instruction: Instruction.Id, operands: []const Operand) []const u8 {
return switch (instruction) {
.mov_r_imm => switch (operands[0].register.size) {
.one => &.{0xb0},
.two, .four, .eight => &.{0xb8},
},
.syscall => &.{ 0x0f, 0x05 },
.ud2 => &.{ 0x0f, 0x0b },
.xor_rm_r => switch (operands[0].register_memory.size) {
.one => &.{0x30},
.two, .four, .eight => &.{0x31},
},
};
}
};
const Operand = union(enum) {
displacement,
register: struct {
value: GPRegister,
size: Size,
},
// TODO
register_memory: struct {
value: GPRegister,
size: Size,
direct: bool,
},
immediate: Size,
const Id = enum {
displacement,
register,
register_memory,
immediate,
};
};
pub const descriptors = blk: {
var result = std.EnumArray(Instruction.Id, Instruction.Descriptor).initUndefined();
result.getPtr(.jmp_rel_8).* = Instruction.Descriptor.new(&.{0xeb}, &[_]Instruction.Operand{rel8});
result.getPtr(.mov_reg_imm32).* = Instruction.Descriptor.new(&.{0xb8}, &[_]Instruction.Operand{ reg32, imm32 });
result.getPtr(.xor_reg_reg).* = Instruction.Descriptor.new(&.{0x31}, &[_]Instruction.Operand{ reg32, reg32 });
result.getPtr(.syscall).* = Instruction.Descriptor.new(&.{ 0x0f, 0x05 }, &.{});
result.getPtr(.ud2).* = Instruction.Descriptor.new(&.{ 0x0f, 0x0b }, &.{});
break :blk result;
};
const Descriptor = struct {
operands: [4]Operand,
operand_count: u3,
operand_offset: u5,
size: u8,
opcode: [3]u8,
opcode_byte_count: u8,
fn getOperands(descriptor: Descriptor) []const Operand {
return descriptor.operands[0..descriptor.operand_count];
}
fn new(opcode_bytes: []const u8, operands: []const Operand) Descriptor {
// TODO: prefixes
var result = Descriptor{
.operands = undefined,
.operand_count = @intCast(operands.len),
.operand_offset = opcode_bytes.len,
.size = opcode_bytes.len,
.opcode = .{ 0, 0 },
.opcode_byte_count = opcode_bytes.len,
};
if (opcode_bytes.len == 1) {
result.opcode[1] = opcode_bytes[0];
} else for (opcode_bytes, result.opcode[0..opcode_bytes.len]) |opcode_byte, *out_opcode| {
out_opcode.* = opcode_byte;
}
for (operands, result.operands[0..operands.len]) |operand, *out_operand| {
out_operand.* = operand;
result.size += operand.size;
}
return result;
}
};
};
const LocalRelative = struct {
instruction: Instruction.Id,
source: u16,
destination: u16,
offset_in_block: u16,
};
const rel8 = Instruction.Operand{
.type = .relative,
.size = @sizeOf(u8),
};
const reg32 = Instruction.Operand{
.type = .register,
.size = @sizeOf(u32),
};
const imm32 = Instruction.Operand{
.type = .immediate,
.size = @sizeOf(u32),
};
const Rex = packed struct(u8) {
b: bool,
x: bool,
r: bool,
w: bool,
fixed: u4 = 0b0100,
};
const GPRegister = enum(u4) {
a = 0,
c = 1,
d = 2,
b = 3,
sp = 4,
bp = 5,
si = 6,
di = 7,
r8 = 8,
r9 = 9,
r10 = 10,
r11 = 11,
r12 = 12,
r13 = 13,
r14 = 14,
r15 = 15,
};
// pub const BasicGPRegister = enum(u3) {
// a = 0,
// c = 1,
// d = 2,
// b = 3,
// sp = 4,
// bp = 5,
// si = 6,
// di = 7,
// };
const syscall_registers = [7]GPRegister{ .a, .di, .si, .d, .r10, .r8, .r9 };