From 817fb263b533f0a24476cabe43a6ee5826113d8d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 5 Mar 2023 17:40:53 +0100 Subject: [PATCH 01/31] x86_64: downstream table-driven instruction encoder --- src/arch/x86_64/CodeGen.zig | 175 +-- src/arch/x86_64/Emit.zig | 2639 +++++---------------------------- src/arch/x86_64/Encoding.zig | 521 +++++++ src/arch/x86_64/Mir.zig | 48 +- src/arch/x86_64/bits.zig | 1165 ++++----------- src/arch/x86_64/encoder.zig | 794 ++++++++++ src/arch/x86_64/encodings.zig | 542 +++++++ 7 files changed, 2622 insertions(+), 3262 deletions(-) create mode 100644 src/arch/x86_64/Encoding.zig create mode 100644 src/arch/x86_64/encoder.zig create mode 100644 src/arch/x86_64/encodings.zig diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index f8f6a773fa..c108ad6f32 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -303,7 +303,12 @@ pub fn generate( var call_info = function.resolveCallingConventionValues(fn_type) catch |err| switch (err) { error.CodegenFail => return Result{ .fail = function.err_msg.? }, error.OutOfRegisters => return Result{ - .fail = try ErrorMsg.create(bin_file.allocator, src_loc, "CodeGen ran out of registers. This is a bug in the Zig compiler.", .{}), + .fail = try ErrorMsg.create( + bin_file.allocator, + src_loc, + "CodeGen ran out of registers. This is a bug in the Zig compiler.", + .{}, + ), }, else => |e| return e, }; @@ -342,6 +347,20 @@ pub fn generate( defer emit.deinit(); emit.lowerMir() catch |err| switch (err) { error.EmitFail => return Result{ .fail = emit.err_msg.? }, + error.InvalidInstruction, error.CannotEncode => |e| { + const msg = switch (e) { + error.InvalidInstruction => "CodeGen failed to find a viable instruction.", + error.CannotEncode => "CodeGen failed to encode the instruction.", + }; + return Result{ + .fail = try ErrorMsg.create( + bin_file.allocator, + src_loc, + "{s} This is a bug in the Zig compiler.", + .{msg}, + ), + }; + }, else => |e| return e, }; @@ -1687,7 +1706,7 @@ fn genIntMulDivOpMir( else => unreachable, }, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, else => unreachable, @@ -2191,7 +2210,7 @@ fn genSliceElemPtr(self: *Self, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref) !MCValue { .reg2 = .rbp, .flags = 0b01, }), - .data = .{ .imm = @bitCast(u32, -@intCast(i32, off)) }, + .data = .{ .disp = -@intCast(i32, off) }, }); }, else => return self.fail("TODO implement slice_elem_ptr when slice is {}", .{slice_mcv}), @@ -2275,7 +2294,7 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void { .reg1 = addr_reg.to64(), .reg2 = .rbp, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .stack_offset => |off| { @@ -2286,7 +2305,7 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void { .reg1 = addr_reg.to64(), .reg2 = .rbp, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .memory, .linker_load => { @@ -2352,7 +2371,7 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { .reg2 = dst_mcv.register, .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); break :result .{ .register = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)) }; } @@ -2615,7 +2634,7 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo .reg2 = reg, .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); }, .stack_offset => |off| { @@ -2842,7 +2861,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type .reg2 = addr_reg.to64(), .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); const new_ptr = MCValue{ .register = addr_reg.to64() }; @@ -2903,7 +2922,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type .reg2 = tmp_reg, .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); return self.store(new_ptr, .{ .register = tmp_reg }, ptr_ty, value_ty); } @@ -3542,25 +3561,13 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu if (intrinsicsAllowed(self.target.*, dst_ty)) { const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) { .f32 => switch (mir_tag) { - .add => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.add_f32_avx - else - Mir.Inst.Tag.add_f32_sse, - .cmp => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.cmp_f32_avx - else - Mir.Inst.Tag.cmp_f32_sse, + .add => Mir.Inst.Tag.add_f32, + .cmp => Mir.Inst.Tag.cmp_f32, else => return self.fail("TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}), }, .f64 => switch (mir_tag) { - .add => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.add_f64_avx - else - Mir.Inst.Tag.add_f64_sse, - .cmp => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.cmp_f64_avx - else - Mir.Inst.Tag.cmp_f64_sse, + .add => Mir.Inst.Tag.add_f64, + .cmp => Mir.Inst.Tag.cmp_f64, else => return self.fail("TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}), }, else => return self.fail("TODO genBinOpMir for float register-register and type {}", .{dst_ty.fmtDebug()}), @@ -3618,7 +3625,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .reg2 = .rbp, .flags = 0b01, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, } @@ -3644,7 +3651,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .reg2 = registerAlias(src_reg, abi_size), .flags = 0b10, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .immediate => |imm| { @@ -3665,7 +3672,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu else => unreachable, }; const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -off), + .dest_off = -off, .operand = @truncate(u32, imm), }); _ = try self.addInst(.{ @@ -3756,7 +3763,7 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M .reg2 = .rbp, .flags = 0b01, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .memory => { @@ -5360,7 +5367,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE // offset from rbp, which is at the top of the stack frame. // mov [rbp+offset], immediate const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -stack_offset), + .dest_off = -stack_offset, .operand = @truncate(u32, imm), }); _ = try self.addInst(.{ @@ -5400,14 +5407,8 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE .Float => { if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f32_avx - else - Mir.Inst.Tag.mov_f32_sse, - .f64 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f64_avx - else - Mir.Inst.Tag.mov_f64_sse, + .f32 => Mir.Inst.Tag.mov_f32, + .f64 => Mir.Inst.Tag.mov_f64, else => return self.fail("TODO genSetStackArg for register for type {}", .{ty.fmtDebug()}), }; _ = try self.addInst(.{ @@ -5421,7 +5422,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE .reg2 = reg.to128(), .flags = 0b01, }), - .data = .{ .imm = @bitCast(u32, -stack_offset) }, + .data = .{ .disp = -stack_offset }, }); return; } @@ -5436,7 +5437,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE .reg2 = registerAlias(reg, @intCast(u32, abi_size)), .flags = 0b10, }), - .data = .{ .imm = @bitCast(u32, -stack_offset) }, + .data = .{ .disp = -stack_offset }, }); }, } @@ -5516,7 +5517,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl 0 => { assert(ty.isError()); const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -stack_offset), + .dest_off = -stack_offset, .operand = @truncate(u32, x_big), }); _ = try self.addInst(.{ @@ -5530,7 +5531,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl }, 1, 2, 4 => { const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -stack_offset), + .dest_off = -stack_offset, .operand = @truncate(u32, x_big), }); _ = try self.addInst(.{ @@ -5552,7 +5553,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl // insted just use two 32 bit writes to avoid register allocation { const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -stack_offset + 4), + .dest_off = -stack_offset + 4, .operand = @truncate(u32, x_big >> 32), }); _ = try self.addInst(.{ @@ -5566,7 +5567,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl } { const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = @bitCast(u32, -stack_offset), + .dest_off = -stack_offset, .operand = @truncate(u32, x_big), }); _ = try self.addInst(.{ @@ -5595,14 +5596,8 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl .Float => { if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f32_avx - else - Mir.Inst.Tag.mov_f32_sse, - .f64 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f64_avx - else - Mir.Inst.Tag.mov_f64_sse, + .f32 => Mir.Inst.Tag.mov_f32, + .f64 => Mir.Inst.Tag.mov_f64, else => return self.fail("TODO genSetStack for register for type {}", .{ty.fmtDebug()}), }; _ = try self.addInst(.{ @@ -5616,7 +5611,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl .reg2 = reg.to128(), .flags = 0b01, }), - .data = .{ .imm = @bitCast(u32, -stack_offset) }, + .data = .{ .disp = -stack_offset }, }); return; } @@ -5691,7 +5686,7 @@ fn genInlineMemcpyRegisterRegister( .reg2 = registerAlias(tmp_reg, nearest_power_of_two), .flags = 0b10, }), - .data = .{ .imm = @bitCast(u32, -next_offset) }, + .data = .{ .disp = -next_offset }, }); if (nearest_power_of_two > 1) { @@ -5711,7 +5706,7 @@ fn genInlineMemcpyRegisterRegister( .reg2 = registerAlias(src_reg, @intCast(u32, abi_size)), .flags = 0b10, }), - .data = .{ .imm = @bitCast(u32, -offset) }, + .data = .{ .disp = -offset }, }); } } @@ -5758,7 +5753,7 @@ fn genInlineMemcpy( .reg1 = dst_addr_reg.to64(), .reg2 = opts.dest_stack_base orelse .rbp, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .register => |reg| { @@ -5787,7 +5782,7 @@ fn genInlineMemcpy( .reg1 = src_addr_reg.to64(), .reg2 = opts.source_stack_base orelse .rbp, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .register => |reg| { @@ -5911,7 +5906,7 @@ fn genInlineMemset( .reg1 = addr_reg.to64(), .reg2 = opts.dest_stack_base orelse .rbp, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .register => |reg| { @@ -5998,7 +5993,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg1 = registerAlias(reg, abi_size), .reg2 = .rbp, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, .unreach, .none => return, // Nothing to do. @@ -6097,14 +6092,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .Float => { if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f32_avx - else - Mir.Inst.Tag.mov_f32_sse, - .f64 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f64_avx - else - Mir.Inst.Tag.mov_f64_sse, + .f32 => Mir.Inst.Tag.mov_f32, + .f64 => Mir.Inst.Tag.mov_f64, else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}), }; _ = try self.addInst(.{ @@ -6141,14 +6130,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f32_avx - else - Mir.Inst.Tag.mov_f32_sse, - .f64 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f64_avx - else - Mir.Inst.Tag.mov_f64_sse, + .f32 => Mir.Inst.Tag.mov_f32, + .f64 => Mir.Inst.Tag.mov_f64, else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), }; @@ -6162,7 +6145,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => unreachable, }, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); return; } @@ -6178,7 +6161,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg2 = reg.to64(), .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); }, } @@ -6190,14 +6173,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f32_avx - else - Mir.Inst.Tag.mov_f32_sse, - .f64 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f64_avx - else - Mir.Inst.Tag.mov_f64_sse, + .f32 => Mir.Inst.Tag.mov_f32, + .f64 => Mir.Inst.Tag.mov_f64, else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), }; @@ -6211,7 +6188,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => unreachable, }, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); return; } @@ -6255,7 +6232,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg2 = reg.to64(), .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); } } @@ -6283,7 +6260,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg2 = .rbp, .flags = flags, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); return; } @@ -6302,7 +6279,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg2 = .rbp, .flags = flags, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); return; } @@ -6311,14 +6288,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .Float => { if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f32_avx - else - Mir.Inst.Tag.mov_f32_sse, - .f64 => if (hasAvxSupport(self.target.*)) - Mir.Inst.Tag.mov_f64_avx - else - Mir.Inst.Tag.mov_f64_sse, + .f32 => Mir.Inst.Tag.mov_f32, + .f64 => Mir.Inst.Tag.mov_f64, else => return self.fail("TODO genSetReg from stack offset for {}", .{ty.fmtDebug()}), }; _ = try self.addInst(.{ @@ -6331,7 +6302,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => unreachable, }, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); return; } @@ -6347,7 +6318,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg2 = .rbp, .flags = 0b01, }), - .data = .{ .imm = @bitCast(u32, -off) }, + .data = .{ .disp = -off }, }); }, } @@ -6436,7 +6407,7 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { else => |size| return self.fail("TODO load ST(0) with abiSize={}", .{size}), }, }), - .data = .{ .imm = @bitCast(u32, -stack_offset) }, + .data = .{ .disp = -stack_offset }, }); // convert @@ -6452,7 +6423,7 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { else => |size| return self.fail("TODO convert float with abiSize={}", .{size}), }, }), - .data = .{ .imm = @bitCast(u32, -stack_dst.stack_offset) }, + .data = .{ .disp = -stack_dst.stack_offset }, }); return self.finishAir(inst, stack_dst, .{ ty_op.operand, .none, .none }); @@ -6551,7 +6522,7 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void { .reg2 = reg, .flags = 0b01, }), - .data = .{ .imm = 0 }, + .data = .{ .disp = 0 }, }); break :blk MCValue{ .register = reg }; }, diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index e521de4bd4..1c540adc9d 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -1,3 +1,4 @@ +//! //! This file contains the functionality for lowering x86_64 MIR into //! machine code @@ -7,6 +8,7 @@ const std = @import("std"); const assert = std.debug.assert; const bits = @import("bits.zig"); const abi = @import("abi.zig"); +const encoder = @import("encoder.zig"); const link = @import("../../link.zig"); const log = std.log.scoped(.codegen); const math = std.math; @@ -19,12 +21,13 @@ const CodeGen = @import("CodeGen.zig"); const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; const Encoder = bits.Encoder; const ErrorMsg = Module.ErrorMsg; +const Instruction = encoder.Instruction; const MCValue = @import("CodeGen.zig").MCValue; +const Memory = bits.Memory; const Mir = @import("Mir.zig"); const Module = @import("../../Module.zig"); -const Instruction = bits.Instruction; -const Type = @import("../../type.zig").Type; const Register = bits.Register; +const Type = @import("../../type.zig").Type; mir: Mir, bin_file: *link.File, @@ -45,6 +48,8 @@ relocs: std.ArrayListUnmanaged(Reloc) = .{}, const InnerError = error{ OutOfMemory, EmitFail, + InvalidInstruction, + CannotEncode, }; const Reloc = struct { @@ -153,8 +158,8 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .push => try emit.mirPushPop(.push, inst), .pop => try emit.mirPushPop(.pop, inst), - .jmp => try emit.mirJmpCall(.jmp_near, inst), - .call => try emit.mirJmpCall(.call_near, inst), + .jmp => try emit.mirJmpCall(.jmp, inst), + .call => try emit.mirJmpCall(.call, inst), .cond_jmp => try emit.mirCondJmp(inst), .cond_set_byte => try emit.mirCondSetByte(inst), @@ -170,25 +175,15 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .interrupt => try emit.mirInterrupt(inst), .nop => {}, // just skip it - // SSE instructions - .mov_f64_sse => try emit.mirMovFloatSse(.movsd, inst), - .mov_f32_sse => try emit.mirMovFloatSse(.movss, inst), + // SSE/AVX instructions + .mov_f64 => try emit.mirMovFloat(.movsd, inst), + .mov_f32 => try emit.mirMovFloat(.movss, inst), - .add_f64_sse => try emit.mirAddFloatSse(.addsd, inst), - .add_f32_sse => try emit.mirAddFloatSse(.addss, inst), + .add_f64 => try emit.mirAddFloat(.addsd, inst), + .add_f32 => try emit.mirAddFloat(.addss, inst), - .cmp_f64_sse => try emit.mirCmpFloatSse(.ucomisd, inst), - .cmp_f32_sse => try emit.mirCmpFloatSse(.ucomiss, inst), - - // AVX instructions - .mov_f64_avx => try emit.mirMovFloatAvx(.vmovsd, inst), - .mov_f32_avx => try emit.mirMovFloatAvx(.vmovss, inst), - - .add_f64_avx => try emit.mirAddFloatAvx(.vaddsd, inst), - .add_f32_avx => try emit.mirAddFloatAvx(.vaddss, inst), - - .cmp_f64_avx => try emit.mirCmpFloatAvx(.vucomisd, inst), - .cmp_f32_avx => try emit.mirCmpFloatAvx(.vucomiss, inst), + .cmp_f64 => try emit.mirCmpFloat(.ucomisd, inst), + .cmp_f32 => try emit.mirCmpFloat(.ucomiss, inst), // Pseudo-instructions .call_extern => try emit.mirCallExtern(inst), @@ -235,8 +230,23 @@ fn fixupRelocs(emit: *Emit) InnerError!void { } } +fn encode(emit: *Emit, mnemonic: Instruction.Mnemonic, ops: struct { + op1: Instruction.Operand = .none, + op2: Instruction.Operand = .none, + op3: Instruction.Operand = .none, + op4: Instruction.Operand = .none, +}) InnerError!void { + const inst = try Instruction.new(mnemonic, .{ + .op1 = ops.op1, + .op2 = ops.op2, + .op3 = ops.op3, + .op4 = ops.op4, + }); + return inst.encode(emit.code.writer()); +} + fn mirUndefinedInstruction(emit: *Emit) InnerError!void { - return lowerToZoEnc(.ud2, emit.code); + return emit.encode(.ud2, .{}); } fn mirInterrupt(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -244,45 +254,43 @@ fn mirInterrupt(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { assert(tag == .interrupt); const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { - 0b00 => return lowerToZoEnc(.int3, emit.code), + 0b00 => return emit.encode(.int3, .{}), else => return emit.fail("TODO handle variant 0b{b} of interrupt instruction", .{ops.flags}), } } fn mirSyscall(emit: *Emit) InnerError!void { - return lowerToZoEnc(.syscall, emit.code); + return emit.encode(.syscall, .{}); } -fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirPushPop(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - // PUSH/POP reg - return lowerToOEnc(tag, ops.reg1, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + }); }, 0b01 => { - // PUSH/POP r/m64 - const imm = emit.mir.instructions.items(.data)[inst].imm; - const ptr_size: Memory.PtrSize = switch (immOpSize(imm)) { - 16 => .word_ptr, - else => .qword_ptr, - }; - return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{ - .disp = imm, - .base = ops.reg1, - }), emit.code); + const disp = emit.mir.instructions.items(.data)[inst].disp; + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = ops.reg1, + .disp = disp, + }) }, + }); }, 0b10 => { - // PUSH imm32 - assert(tag == .push); const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToIEnc(.push, imm, emit.code); + return emit.encode(.push, .{ + .op1 = .{ .imm = imm }, + }); }, 0b11 => unreachable, } } -fn mirPushPopRegisterList(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirPushPopRegisterList(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); const payload = emit.mir.instructions.items(.data)[inst].payload; const save_reg_list = emit.mir.extraData(Mir.SaveRegisterList, payload).data; @@ -291,15 +299,20 @@ fn mirPushPopRegisterList(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerErro const callee_preserved_regs = abi.getCalleePreservedRegs(emit.target.*); for (callee_preserved_regs) |reg| { if (reg_list.isSet(callee_preserved_regs, reg)) { - switch (tag) { - .push => try lowerToMrEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, disp), - .base = ops.reg1, - }), reg, emit.code), - .pop => try lowerToRmEnc(.mov, reg, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, disp), - .base = ops.reg1, - }), emit.code), + const op1: Instruction.Operand = .{ .mem = Memory.sib(.qword, .{ + .base = ops.reg1, + .disp = disp, + }) }; + const op2: Instruction.Operand = .{ .reg = reg }; + switch (mnemonic) { + .push => try emit.encode(.mov, .{ + .op1 = op1, + .op2 = op2, + }), + .pop => try emit.encode(.mov, .{ + .op1 = op2, + .op2 = op1, + }), else => unreachable, } disp += 8; @@ -307,13 +320,17 @@ fn mirPushPopRegisterList(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerErro } } -fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { const target = emit.mir.instructions.items(.data)[inst].inst; const source = emit.code.items.len; - try lowerToDEnc(tag, 0, emit.code); + try emit.encode(mnemonic, .{ + .op1 = .{ + .imm = 0, + }, + }); try emit.relocs.append(emit.bin_file.allocator, .{ .source = source, .target = target, @@ -323,34 +340,33 @@ fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { }, 0b01 => { if (ops.reg1 == .none) { - // JMP/CALL [imm] const imm = emit.mir.instructions.items(.data)[inst].imm; - const ptr_size: Memory.PtrSize = switch (immOpSize(imm)) { - 16 => .word_ptr, - else => .qword_ptr, - }; - return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{ .disp = imm }), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .imm = imm }, + }); } - // JMP/CALL reg - return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + }); }, 0b10 => { - // JMP/CALL r/m64 - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = imm, - .base = ops.reg1, - }), emit.code); + const disp = emit.mir.instructions.items(.data)[inst].disp; + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = ops.reg1, + .disp = disp, + }) }, + }); }, 0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}), } } fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const mir_tag = emit.mir.instructions.items(.tag)[inst]; - assert(mir_tag == .cond_jmp); + const tag = emit.mir.instructions.items(.tag)[inst]; + assert(tag == .cond_jmp); const inst_cc = emit.mir.instructions.items(.data)[inst].inst_cc; - const tag: Tag = switch (inst_cc.cc) { + const mnemonic: Instruction.Mnemonic = switch (inst_cc.cc) { .a => .ja, .ae => .jae, .b => .jb, @@ -383,7 +399,9 @@ fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .z => .jz, }; const source = emit.code.items.len; - try lowerToDEnc(tag, 0, emit.code); + try emit.encode(mnemonic, .{ + .op1 = .{ .imm = 0 }, + }); try emit.relocs.append(emit.bin_file.allocator, .{ .source = source, .target = inst_cc.inst, @@ -393,11 +411,11 @@ fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } fn mirCondSetByte(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const mir_tag = emit.mir.instructions.items(.tag)[inst]; - assert(mir_tag == .cond_set_byte); + const tag = emit.mir.instructions.items(.tag)[inst]; + assert(tag == .cond_set_byte); const ops = emit.mir.instructions.items(.ops)[inst].decode(); const cc = emit.mir.instructions.items(.data)[inst].cc; - const tag: Tag = switch (cc) { + const mnemonic: Instruction.Mnemonic = switch (cc) { .a => .seta, .ae => .setae, .b => .setb, @@ -429,15 +447,15 @@ fn mirCondSetByte(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .s => .sets, .z => .setz, }; - return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1.to8()), emit.code); + return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 } }); } fn mirCondMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const mir_tag = emit.mir.instructions.items(.tag)[inst]; - assert(mir_tag == .cond_mov); + const tag = emit.mir.instructions.items(.tag)[inst]; + assert(tag == .cond_mov); const ops = emit.mir.instructions.items(.ops)[inst].decode(); const cc = emit.mir.instructions.items(.data)[inst].cc; - const tag: Tag = switch (cc) { + const mnemonic: Instruction.Mnemonic = switch (cc) { .a => .cmova, .ae => .cmovae, .b => .cmovb, @@ -469,21 +487,28 @@ fn mirCondMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .s => .cmovs, .z => .cmovz, }; + const op1: Instruction.Operand = .{ .reg = ops.reg1 }; if (ops.flags == 0b00) { - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(mnemonic, .{ + .op1 = op1, + .op2 = .{ .reg = ops.reg2 }, + }); } - const imm = emit.mir.instructions.items(.data)[inst].imm; + const disp = emit.mir.instructions.items(.data)[inst].disp; const ptr_size: Memory.PtrSize = switch (ops.flags) { 0b00 => unreachable, - 0b01 => .word_ptr, - 0b10 => .dword_ptr, - 0b11 => .qword_ptr, + 0b01 => .word, + 0b10 => .dword, + 0b11 => .qword, }; - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(ptr_size, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); + return emit.encode(mnemonic, .{ + .op1 = op1, + .op2 = .{ .mem = Memory.sib(ptr_size, .{ + .base = ops.reg2, + .disp = disp, + }) }, + }); } fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -493,18 +518,16 @@ fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { switch (ops.flags) { 0b00 => { if (ops.reg2 == .none) { - // TEST r/m64, imm32 - // MI const imm = emit.mir.instructions.items(.data)[inst].imm; - if (ops.reg1.to64() == .rax) { - // TEST rax, imm32 - // I - return lowerToIEnc(.@"test", imm, emit.code); - } - return lowerToMiEnc(.@"test", RegisterOrMemory.reg(ops.reg1), imm, emit.code); + return emit.encode(.@"test", .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .imm = imm }, + }); } - // TEST r/m64, r64 - return lowerToMrEnc(.@"test", RegisterOrMemory.reg(ops.reg1), ops.reg2, emit.code); + return emit.encode(.@"test", .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, else => return emit.fail("TODO more TEST alternatives", .{}), } @@ -515,62 +538,59 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { assert(tag == .ret); const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { - 0b00 => { - // RETF imm16 - // I - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToIEnc(.ret_far, imm, emit.code); - }, - 0b01 => { - return lowerToZoEnc(.ret_far, emit.code); - }, + 0b00 => unreachable, + 0b01 => unreachable, 0b10 => { - // RET imm16 - // I const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToIEnc(.ret_near, imm, emit.code); + return emit.encode(.ret, .{ + .op1 = .{ .imm = imm }, + }); }, 0b11 => { - return lowerToZoEnc(.ret_near, emit.code); + return emit.encode(.ret, .{}); }, } } -fn mirArith(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { if (ops.reg2 == .none) { - // mov reg1, imm32 - // MI const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMiEnc(tag, RegisterOrMemory.reg(ops.reg1), imm, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .imm = imm }, + }); } - // mov reg1, reg2 - // RM - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, 0b01 => { - // mov reg1, [reg2 + imm32] - // RM - const imm = emit.mir.instructions.items(.data)[inst].imm; - const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = imm, - .base = src_reg, - }), emit.code); + const disp = emit.mir.instructions.items(.data)[inst].disp; + const base: ?Register = if (ops.reg2 != .none) ops.reg2 else null; + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .base = base, + .disp = disp, + }) }, + }); }, 0b10 => { if (ops.reg2 == .none) { return emit.fail("TODO unused variant: mov reg1, none, 0b10", .{}); } - // mov [reg1 + imm32], reg2 - // MR - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMrEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg2.size()), .{ - .disp = imm, - .base = ops.reg1, - }), ops.reg2, emit.code); + const disp = emit.mir.instructions.items(.data)[inst].disp; + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{ + .base = ops.reg1, + .disp = disp, + }) }, + .op2 = .{ .reg = ops.reg2 }, + }); }, 0b11 => { return emit.fail("TODO unused variant: mov reg1, reg2, 0b11", .{}); @@ -578,169 +598,165 @@ fn mirArith(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { } } -fn mirArithMemImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithMemImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); assert(ops.reg2 == .none); const payload = emit.mir.instructions.items(.data)[inst].payload; const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte_ptr, - 0b01 => .word_ptr, - 0b10 => .dword_ptr, - 0b11 => .qword_ptr, + 0b00 => .byte, + 0b01 => .word, + 0b10 => .dword, + 0b11 => .qword, }; - return lowerToMiEnc(tag, RegisterOrMemory.mem(ptr_size, .{ - .disp = imm_pair.dest_off, - .base = ops.reg1, - }), imm_pair.operand, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(ptr_size, .{ + .disp = imm_pair.dest_off, + .base = ops.reg1, + }) }, + .op2 = .{ .imm = imm_pair.operand }, + }); } -inline fn setRexWRegister(reg: Register) bool { - if (reg.size() > 64) return false; - if (reg.size() == 64) return true; - return switch (reg) { - .ah, .ch, .dh, .bh => true, - else => false, - }; -} - -inline fn immOpSize(u_imm: u32) u6 { - const imm = @bitCast(i32, u_imm); - if (math.minInt(i8) <= imm and imm <= math.maxInt(i8)) { - return 8; - } - if (math.minInt(i16) <= imm and imm <= math.maxInt(i16)) { - return 16; - } - return 32; -} - -fn mirArithScaleSrc(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithScaleSrc(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); const scale = ops.flags; const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); - // OP reg1, [reg2 + scale*index + imm32] - const scale_index = ScaleIndex{ + const scale_index = Memory.ScaleIndex{ .scale = scale, .index = index_reg_disp.index, }; - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = index_reg_disp.disp, - .base = ops.reg2, - .scale_index = scale_index, - }), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .base = ops.reg2, + .scale_index = scale_index, + .disp = index_reg_disp.disp, + }) }, + }); } -fn mirArithScaleDst(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithScaleDst(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); const scale = ops.flags; const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); - const scale_index = ScaleIndex{ + const scale_index = Memory.ScaleIndex{ .scale = scale, .index = index_reg_disp.index, }; assert(ops.reg2 != .none); - // OP [reg1 + scale*index + imm32], reg2 - return lowerToMrEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg2.size()), .{ - .disp = index_reg_disp.disp, - .base = ops.reg1, - .scale_index = scale_index, - }), ops.reg2, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{ + .base = ops.reg1, + .scale_index = scale_index, + .disp = index_reg_disp.disp, + }) }, + .op2 = .{ .reg = ops.reg2 }, + }); } -fn mirArithScaleImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithScaleImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); const scale = ops.flags; const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp_imm = emit.mir.extraData(Mir.IndexRegisterDispImm, payload).data.decode(); - const scale_index = ScaleIndex{ + const scale_index = Memory.ScaleIndex{ .scale = scale, .index = index_reg_disp_imm.index, }; - // OP qword ptr [reg1 + scale*index + imm32], imm32 - return lowerToMiEnc(tag, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = index_reg_disp_imm.disp, - .base = ops.reg1, - .scale_index = scale_index, - }), index_reg_disp_imm.imm, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = ops.reg1, + .disp = index_reg_disp_imm.disp, + .scale_index = scale_index, + }) }, + .op2 = .{ .imm = index_reg_disp_imm.imm }, + }); } -fn mirArithMemIndexImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithMemIndexImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); assert(ops.reg2 == .none); const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp_imm = emit.mir.extraData(Mir.IndexRegisterDispImm, payload).data.decode(); const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte_ptr, - 0b01 => .word_ptr, - 0b10 => .dword_ptr, - 0b11 => .qword_ptr, + 0b00 => .byte, + 0b01 => .word, + 0b10 => .dword, + 0b11 => .qword, }; - const scale_index = ScaleIndex{ + const scale_index = Memory.ScaleIndex{ .scale = 0, .index = index_reg_disp_imm.index, }; - // OP ptr [reg1 + index + imm32], imm32 - return lowerToMiEnc(tag, RegisterOrMemory.mem(ptr_size, .{ - .disp = index_reg_disp_imm.disp, - .base = ops.reg1, - .scale_index = scale_index, - }), index_reg_disp_imm.imm, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(ptr_size, .{ + .disp = index_reg_disp_imm.disp, + .base = ops.reg1, + .scale_index = scale_index, + }) }, + .op2 = .{ .imm = index_reg_disp_imm.imm }, + }); } fn mirMovSignExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const mir_tag = emit.mir.instructions.items(.tag)[inst]; - assert(mir_tag == .mov_sign_extend); + const tag = emit.mir.instructions.items(.tag)[inst]; + assert(tag == .mov_sign_extend); const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const imm = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].imm else undefined; + const disp = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].disp else undefined; switch (ops.flags) { 0b00 => { - const tag: Tag = if (ops.reg2.size() == 32) .movsxd else .movsx; - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + const mnemonic: Instruction.Mnemonic = if (ops.reg2.size() == 32) .movsxd else .movsx; + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, - 0b01 => { - return lowerToRmEnc(.movsx, ops.reg1, RegisterOrMemory.mem(.byte_ptr, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); - }, - 0b10 => { - return lowerToRmEnc(.movsx, ops.reg1, RegisterOrMemory.mem(.word_ptr, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); - }, - 0b11 => { - return lowerToRmEnc(.movsxd, ops.reg1, RegisterOrMemory.mem(.dword_ptr, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); + else => { + const ptr_size: Memory.PtrSize = switch (ops.flags) { + 0b01 => .byte, + 0b10 => .word, + 0b11 => .qword, + else => unreachable, + }; + return emit.encode(.movsx, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(ptr_size, .{ + .disp = disp, + .base = ops.reg2, + }) }, + }); }, } } fn mirMovZeroExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const mir_tag = emit.mir.instructions.items(.tag)[inst]; - assert(mir_tag == .mov_zero_extend); + const tag = emit.mir.instructions.items(.tag)[inst]; + assert(tag == .mov_zero_extend); const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const imm = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].imm else undefined; + const disp = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].disp else undefined; switch (ops.flags) { 0b00 => { - return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(.movzx, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, - 0b01 => { - return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.mem(.byte_ptr, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); - }, - 0b10 => { - return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.mem(.word_ptr, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); + 0b01, 0b10 => { + const ptr_size: Memory.PtrSize = switch (ops.flags) { + 0b01 => .byte, + 0b10 => .word, + else => unreachable, + }; + return emit.encode(.movzx, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(ptr_size, .{ + .disp = disp, + .base = ops.reg2, + }) }, + }); }, 0b11 => { return emit.fail("TODO unused variant: movzx 0b11", .{}); @@ -759,9 +775,10 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const imm = emit.mir.extraData(Mir.Imm64, payload).data; break :blk imm.decode(); } else emit.mir.instructions.items(.data)[inst].imm; - // movabs reg, imm64 - // OI - return lowerToOiEnc(.mov, ops.reg1, imm, emit.code); + return emit.encode(.mov, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .imm = @bitCast(i64, imm) }, + }); }, 0b01 => { if (ops.reg1 == .none) { @@ -770,18 +787,20 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const imm = emit.mir.extraData(Mir.Imm64, payload).data; break :blk imm.decode(); } else emit.mir.instructions.items(.data)[inst].imm; - // movabs moffs64, rax - // TD - return lowerToTdEnc(.mov, imm, ops.reg2, emit.code); + return emit.encode(.mov, .{ + .op1 = .{ .mem = Memory.moffs(ops.reg2, imm) }, + .op2 = .{ .reg = .rax }, + }); } const imm: u64 = if (ops.reg1.size() == 64) blk: { const payload = emit.mir.instructions.items(.data)[inst].payload; const imm = emit.mir.extraData(Mir.Imm64, payload).data; break :blk imm.decode(); } else emit.mir.instructions.items(.data)[inst].imm; - // movabs rax, moffs64 - // FD - return lowerToFdEnc(.mov, ops.reg1, imm, emit.code); + return emit.encode(.mov, .{ + .op1 = .{ .reg = .rax }, + .op2 = .{ .mem = Memory.moffs(ops.reg1, imm) }, + }); }, else => return emit.fail("TODO unused movabs variant", .{}), } @@ -791,63 +810,58 @@ fn mirFisttp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .fisttp); const ops = emit.mir.instructions.items(.ops)[inst].decode(); - - // the selecting between operand sizes for this particular `fisttp` instruction - // is done via opcode instead of the usual prefixes. - - const opcode: Tag = switch (ops.flags) { - 0b00 => .fisttp16, - 0b01 => .fisttp32, - 0b10 => .fisttp64, + const ptr_size: Memory.PtrSize = switch (ops.flags) { + 0b00 => .word, + 0b01 => .dword, + 0b10 => .qword, else => unreachable, }; - const mem_or_reg = Memory{ - .base = ops.reg1, - .disp = emit.mir.instructions.items(.data)[inst].imm, - .ptr_size = Memory.PtrSize.dword_ptr, // to prevent any prefix from being used - }; - return lowerToMEnc(opcode, .{ .memory = mem_or_reg }, emit.code); + return emit.encode(.fisttp, .{ + .op1 = .{ .mem = Memory.sib(ptr_size, .{ + .base = ops.reg1, + .disp = emit.mir.instructions.items(.data)[inst].disp, + }) }, + }); } fn mirFld(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .fld); const ops = emit.mir.instructions.items(.ops)[inst].decode(); - - // the selecting between operand sizes for this particular `fisttp` instruction - // is done via opcode instead of the usual prefixes. - - const opcode: Tag = switch (ops.flags) { - 0b01 => .fld32, - 0b10 => .fld64, + const ptr_size: Memory.PtrSize = switch (ops.flags) { + 0b01 => .dword, + 0b10 => .qword, else => unreachable, }; - const mem_or_reg = Memory{ - .base = ops.reg1, - .disp = emit.mir.instructions.items(.data)[inst].imm, - .ptr_size = Memory.PtrSize.dword_ptr, // to prevent any prefix from being used - }; - return lowerToMEnc(opcode, .{ .memory = mem_or_reg }, emit.code); + return emit.encode(.fld, .{ + .op1 = .{ .mem = Memory.sib(ptr_size, .{ + .base = ops.reg1, + .disp = emit.mir.instructions.items(.data)[inst].disp, + }) }, + }); } -fn mirShift(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirShift(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - // sal reg1, 1 - // M1 - return lowerToM1Enc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .imm = 1 }, + }); }, 0b01 => { - // sal reg1, .cl - // MC - return lowerToMcEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = .cl }, + }); }, 0b10 => { - // sal reg1, imm8 - // MI const imm = @truncate(u8, emit.mir.instructions.items(.data)[inst].imm); - return lowerToMiImm8Enc(tag, RegisterOrMemory.reg(ops.reg1), imm, emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .imm = imm }, + }); }, 0b11 => { return emit.fail("TODO unused variant: SHIFT reg1, 0b11", .{}); @@ -855,24 +869,28 @@ fn mirShift(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { } } -fn mirMulDiv(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirMulDiv(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); if (ops.reg1 != .none) { assert(ops.reg2 == .none); - return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + }); } assert(ops.reg2 != .none); - const imm = emit.mir.instructions.items(.data)[inst].imm; + const disp = emit.mir.instructions.items(.data)[inst].disp; const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte_ptr, - 0b01 => .word_ptr, - 0b10 => .dword_ptr, - 0b11 => .qword_ptr, + 0b00 => .byte, + 0b01 => .word, + 0b10 => .dword, + 0b11 => .qword, }; - return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(ptr_size, .{ + .base = ops.reg2, + .disp = disp, + }) }, + }); } fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -881,40 +899,54 @@ fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - return lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(.imul, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, 0b01 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; + const disp = emit.mir.instructions.items(.data)[inst].disp; const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - return lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = imm, - .base = src_reg, - }), emit.code); + return emit.encode(.imul, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = src_reg, + .disp = disp, + }) }, + }); }, 0b10 => { const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToRmiEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), imm, emit.code); + return emit.encode(.imul, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + .op3 = .{ .imm = imm }, + }); }, 0b11 => { const payload = emit.mir.instructions.items(.data)[inst].payload; const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; - return lowerToRmiEnc(.imul, ops.reg1, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = imm_pair.dest_off, - .base = ops.reg2, - }), imm_pair.operand, emit.code); + return emit.encode(.imul, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = ops.reg2, + .disp = imm_pair.dest_off, + }) }, + .op3 = .{ .imm = imm_pair.operand }, + }); }, } } fn mirCwd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const tag: Tag = switch (ops.flags) { + const mnemonic: Instruction.Mnemonic = switch (ops.flags) { 0b00 => .cbw, 0b01 => .cwd, 0b10 => .cdq, 0b11 => .cqo, }; - return lowerToZoEnc(tag, emit.code); + return emit.encode(mnemonic, .{}); } fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -923,30 +955,22 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - // lea reg1, [reg2 + imm32] - // RM - const imm = emit.mir.instructions.items(.data)[inst].imm; + const disp = emit.mir.instructions.items(.data)[inst].disp; const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - return lowerToRmEnc( - .lea, - ops.reg1, - RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = imm, + return emit.encode(.lea, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ .base = src_reg, - }), - emit.code, - ); + .disp = disp, + }) }, + }); }, 0b01 => { - // lea reg1, [rip + imm32] - // RM const start_offset = emit.code.items.len; - try lowerToRmEnc( - .lea, - ops.reg1, - RegisterOrMemory.rip(Memory.PtrSize.new(ops.reg1.size()), 0), - emit.code, - ); + try emit.encode(.lea, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromSize(ops.reg1.size()), 0) }, + }); const end_offset = emit.code.items.len; // Backpatch the displacement const payload = emit.mir.instructions.items(.data)[inst].payload; @@ -955,24 +979,21 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { mem.writeIntLittle(i32, emit.code.items[end_offset - 4 ..][0..4], disp); }, 0b10 => { - // lea reg, [rbp + index + imm32] const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - const scale_index = ScaleIndex{ + const scale_index = Memory.ScaleIndex{ .scale = 0, .index = index_reg_disp.index, }; - return lowerToRmEnc( - .lea, - ops.reg1, - RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = index_reg_disp.disp, + return emit.encode(.lea, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ .base = src_reg, .scale_index = scale_index, - }), - emit.code, - ); + .disp = index_reg_disp.disp, + }) }, + }); }, 0b11 => return emit.fail("TODO unused LEA variant 0b11", .{}), } @@ -989,14 +1010,10 @@ fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { else => return emit.fail("TODO unused LEA PIC variant 0b11", .{}), } - // lea reg1, [rip + reloc] - // RM - try lowerToRmEnc( - .lea, - ops.reg1, - RegisterOrMemory.rip(Memory.PtrSize.new(ops.reg1.size()), 0), - emit.code, - ); + try emit.encode(.lea, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromSize(ops.reg1.size()), 0) }, + }); const end_offset = emit.code.items.len; @@ -1039,94 +1056,64 @@ fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } -// SSE instructions +// SSE/AVX instructions -fn mirMovFloatSse(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirMovFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg2.size()), .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); + const disp = emit.mir.instructions.items(.data)[inst].disp; + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{ + .base = ops.reg2, + .disp = disp, + }) }, + }); }, 0b01 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMrEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = imm, - .base = ops.reg1, - }), ops.reg2, emit.code); + const disp = emit.mir.instructions.items(.data)[inst].disp; + return emit.encode(mnemonic, .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .base = ops.reg1, + .disp = disp, + }) }, + .op2 = .{ .reg = ops.reg2 }, + }); }, 0b10 => { - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }), + else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }), } } -fn mirAddFloatSse(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirAddFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }), + else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }), } } -fn mirCmpFloatSse(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirCmpFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .reg = ops.reg2 }, + }); }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }), - } -} -// AVX instructions - -fn mirMovFloatAvx(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToVmEnc(tag, ops.reg1, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg2.size()), .{ - .disp = imm, - .base = ops.reg2, - }), emit.code); - }, - 0b01 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMvEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{ - .disp = imm, - .base = ops.reg1, - }), ops.reg2, emit.code); - }, - 0b10 => { - return lowerToRvmEnc(tag, ops.reg1, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); - }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }), - } -} - -fn mirAddFloatAvx(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return lowerToRvmEnc(tag, ops.reg1, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); - }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }), - } -} - -fn mirCmpFloatAvx(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return lowerToVmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); - }, - else => return emit.fail("TODO unused variant 0b{b} for mov_f64", .{ops.flags}), + else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }), } } @@ -1139,7 +1126,9 @@ fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const offset = blk: { // callq - try lowerToDEnc(.call_near, 0, emit.code); + try emit.encode(.call, .{ + .op1 = .{ .imm = 0 }, + }); break :blk @intCast(u32, emit.code.items.len) - 4; }; @@ -1264,1841 +1253,3 @@ fn mirDbgEpilogueBegin(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .none => {}, } } - -const Tag = enum { - adc, - add, - sub, - xor, - @"and", - @"or", - sbb, - cmp, - mov, - movsx, - movsxd, - movzx, - lea, - jmp_near, - call_near, - push, - pop, - @"test", - ud2, - int3, - nop, - imul, - mul, - idiv, - div, - syscall, - ret_near, - ret_far, - fisttp16, - fisttp32, - fisttp64, - fld32, - fld64, - jo, - jno, - jb, - jbe, - jc, - jnae, - jnc, - jae, - je, - jz, - jne, - jnz, - jna, - jnb, - jnbe, - ja, - js, - jns, - jpe, - jp, - jpo, - jnp, - jnge, - jl, - jge, - jnl, - jle, - jng, - jg, - jnle, - seto, - setno, - setb, - setc, - setnae, - setnb, - setnc, - setae, - sete, - setz, - setne, - setnz, - setbe, - setna, - seta, - setnbe, - sets, - setns, - setp, - setpe, - setnp, - setpo, - setl, - setnge, - setnl, - setge, - setle, - setng, - setnle, - setg, - cmovo, - cmovno, - cmovb, - cmovc, - cmovnae, - cmovnb, - cmovnc, - cmovae, - cmove, - cmovz, - cmovne, - cmovnz, - cmovbe, - cmovna, - cmova, - cmovnbe, - cmovs, - cmovns, - cmovp, - cmovpe, - cmovnp, - cmovpo, - cmovl, - cmovnge, - cmovnl, - cmovge, - cmovle, - cmovng, - cmovnle, - cmovg, - shl, - sal, - shr, - sar, - cbw, - cwd, - cdq, - cqo, - movsd, - movss, - addsd, - addss, - cmpsd, - cmpss, - ucomisd, - ucomiss, - vmovsd, - vmovss, - vaddsd, - vaddss, - vcmpsd, - vcmpss, - vucomisd, - vucomiss, - - fn isSse(tag: Tag) bool { - return switch (tag) { - .movsd, - .movss, - .addsd, - .addss, - .cmpsd, - .cmpss, - .ucomisd, - .ucomiss, - => true, - - else => false, - }; - } - - fn isAvx(tag: Tag) bool { - return switch (tag) { - .vmovsd, - .vmovss, - .vaddsd, - .vaddss, - .vcmpsd, - .vcmpss, - .vucomisd, - .vucomiss, - => true, - - else => false, - }; - } - - fn isSetCC(tag: Tag) bool { - return switch (tag) { - .seto, - .setno, - .setb, - .setc, - .setnae, - .setnb, - .setnc, - .setae, - .sete, - .setz, - .setne, - .setnz, - .setbe, - .setna, - .seta, - .setnbe, - .sets, - .setns, - .setp, - .setpe, - .setnp, - .setpo, - .setl, - .setnge, - .setnl, - .setge, - .setle, - .setng, - .setnle, - .setg, - => true, - else => false, - }; - } -}; - -const Encoding = enum { - /// OP - zo, - - /// OP rel32 - d, - - /// OP r/m64 - m, - - /// OP r64 - o, - - /// OP imm32 - i, - - /// OP r/m64, 1 - m1, - - /// OP r/m64, .cl - mc, - - /// OP r/m64, imm32 - mi, - - /// OP r/m64, imm8 - mi8, - - /// OP r/m64, r64 - mr, - - /// OP r64, r/m64 - rm, - - /// OP r64, imm64 - oi, - - /// OP al/ax/eax/rax, moffs - fd, - - /// OP moffs, al/ax/eax/rax - td, - - /// OP r64, r/m64, imm32 - rmi, - - /// OP xmm1, xmm2/m64 - vm, - - /// OP m64, xmm1 - mv, - - /// OP xmm1, xmm2, xmm3/m64 - rvm, - - /// OP xmm1, xmm2, xmm3/m64, imm8 - rvmi, -}; - -const OpCode = struct { - bytes: [3]u8, - count: usize, - - fn init(comptime in_bytes: []const u8) OpCode { - comptime assert(in_bytes.len <= 3); - comptime var bytes: [3]u8 = undefined; - inline for (in_bytes, 0..) |x, i| { - bytes[i] = x; - } - return .{ .bytes = bytes, .count = in_bytes.len }; - } - - fn encode(opc: OpCode, encoder: Encoder) void { - switch (opc.count) { - 1 => encoder.opcode_1byte(opc.bytes[0]), - 2 => encoder.opcode_2byte(opc.bytes[0], opc.bytes[1]), - 3 => encoder.opcode_3byte(opc.bytes[0], opc.bytes[1], opc.bytes[2]), - else => unreachable, - } - } - - fn encodeWithReg(opc: OpCode, encoder: Encoder, reg: Register) void { - assert(opc.count == 1); - encoder.opcode_withReg(opc.bytes[0], reg.lowEnc()); - } -}; - -inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) OpCode { - // zig fmt: off - switch (enc) { - .zo => return switch (tag) { - .ret_near => OpCode.init(&.{0xc3}), - .ret_far => OpCode.init(&.{0xcb}), - .ud2 => OpCode.init(&.{ 0x0F, 0x0B }), - .int3 => OpCode.init(&.{0xcc}), - .nop => OpCode.init(&.{0x90}), - .syscall => OpCode.init(&.{ 0x0f, 0x05 }), - .cbw => OpCode.init(&.{0x98}), - .cwd, - .cdq, - .cqo => OpCode.init(&.{0x99}), - else => unreachable, - }, - .d => return switch (tag) { - .jmp_near => OpCode.init(&.{0xe9}), - .call_near => OpCode.init(&.{0xe8}), - - .jo => if (is_one_byte) OpCode.init(&.{0x70}) else OpCode.init(&.{0x0f,0x80}), - - .jno => if (is_one_byte) OpCode.init(&.{0x71}) else OpCode.init(&.{0x0f,0x81}), - - .jb, - .jc, - .jnae => if (is_one_byte) OpCode.init(&.{0x72}) else OpCode.init(&.{0x0f,0x82}), - - .jnb, - .jnc, - .jae => if (is_one_byte) OpCode.init(&.{0x73}) else OpCode.init(&.{0x0f,0x83}), - - .je, - .jz => if (is_one_byte) OpCode.init(&.{0x74}) else OpCode.init(&.{0x0f,0x84}), - - .jne, - .jnz => if (is_one_byte) OpCode.init(&.{0x75}) else OpCode.init(&.{0x0f,0x85}), - - .jna, - .jbe => if (is_one_byte) OpCode.init(&.{0x76}) else OpCode.init(&.{0x0f,0x86}), - - .jnbe, - .ja => if (is_one_byte) OpCode.init(&.{0x77}) else OpCode.init(&.{0x0f,0x87}), - - .js => if (is_one_byte) OpCode.init(&.{0x78}) else OpCode.init(&.{0x0f,0x88}), - - .jns => if (is_one_byte) OpCode.init(&.{0x79}) else OpCode.init(&.{0x0f,0x89}), - - .jpe, - .jp => if (is_one_byte) OpCode.init(&.{0x7a}) else OpCode.init(&.{0x0f,0x8a}), - - .jpo, - .jnp => if (is_one_byte) OpCode.init(&.{0x7b}) else OpCode.init(&.{0x0f,0x8b}), - - .jnge, - .jl => if (is_one_byte) OpCode.init(&.{0x7c}) else OpCode.init(&.{0x0f,0x8c}), - - .jge, - .jnl => if (is_one_byte) OpCode.init(&.{0x7d}) else OpCode.init(&.{0x0f,0x8d}), - - .jle, - .jng => if (is_one_byte) OpCode.init(&.{0x7e}) else OpCode.init(&.{0x0f,0x8e}), - - .jg, - .jnle => if (is_one_byte) OpCode.init(&.{0x7f}) else OpCode.init(&.{0x0f,0x8f}), - - else => unreachable, - }, - .m => return switch (tag) { - .jmp_near, - .call_near, - .push => OpCode.init(&.{0xff}), - - .pop => OpCode.init(&.{0x8f}), - .seto => OpCode.init(&.{0x0f,0x90}), - .setno => OpCode.init(&.{0x0f,0x91}), - - .setb, - .setc, - .setnae => OpCode.init(&.{0x0f,0x92}), - - .setnb, - .setnc, - .setae => OpCode.init(&.{0x0f,0x93}), - - .sete, - .setz => OpCode.init(&.{0x0f,0x94}), - - .setne, - .setnz => OpCode.init(&.{0x0f,0x95}), - - .setbe, - .setna => OpCode.init(&.{0x0f,0x96}), - - .seta, - .setnbe => OpCode.init(&.{0x0f,0x97}), - - .sets => OpCode.init(&.{0x0f,0x98}), - .setns => OpCode.init(&.{0x0f,0x99}), - - .setp, - .setpe => OpCode.init(&.{0x0f,0x9a}), - - .setnp, - .setpo => OpCode.init(&.{0x0f,0x9b}), - - .setl, - .setnge => OpCode.init(&.{0x0f,0x9c}), - - .setnl, - .setge => OpCode.init(&.{0x0f,0x9d}), - - .setle, - .setng => OpCode.init(&.{0x0f,0x9e}), - - .setnle, - .setg => OpCode.init(&.{0x0f,0x9f}), - - .idiv, - .div, - .imul, - .mul => if (is_one_byte) OpCode.init(&.{0xf6}) else OpCode.init(&.{0xf7}), - - .fisttp16 => OpCode.init(&.{0xdf}), - .fisttp32 => OpCode.init(&.{0xdb}), - .fisttp64 => OpCode.init(&.{0xdd}), - .fld32 => OpCode.init(&.{0xd9}), - .fld64 => OpCode.init(&.{0xdd}), - else => unreachable, - }, - .o => return switch (tag) { - .push => OpCode.init(&.{0x50}), - .pop => OpCode.init(&.{0x58}), - else => unreachable, - }, - .i => return switch (tag) { - .push => if (is_one_byte) OpCode.init(&.{0x6a}) else OpCode.init(&.{0x68}), - .@"test" => if (is_one_byte) OpCode.init(&.{0xa8}) else OpCode.init(&.{0xa9}), - .ret_near => OpCode.init(&.{0xc2}), - .ret_far => OpCode.init(&.{0xca}), - else => unreachable, - }, - .m1 => return switch (tag) { - .shl, .sal, - .shr, .sar => if (is_one_byte) OpCode.init(&.{0xd0}) else OpCode.init(&.{0xd1}), - else => unreachable, - }, - .mc => return switch (tag) { - .shl, .sal, - .shr, .sar => if (is_one_byte) OpCode.init(&.{0xd2}) else OpCode.init(&.{0xd3}), - else => unreachable, - }, - .mi => return switch (tag) { - .adc, .add, - .sub, .xor, - .@"and", .@"or", - .sbb, .cmp => if (is_one_byte) OpCode.init(&.{0x80}) else OpCode.init(&.{0x81}), - .mov => if (is_one_byte) OpCode.init(&.{0xc6}) else OpCode.init(&.{0xc7}), - .@"test" => if (is_one_byte) OpCode.init(&.{0xf6}) else OpCode.init(&.{0xf7}), - else => unreachable, - }, - .mi8 => return switch (tag) { - .adc, .add, - .sub, .xor, - .@"and", .@"or", - .sbb, .cmp => OpCode.init(&.{0x83}), - .shl, .sal, - .shr, .sar => if (is_one_byte) OpCode.init(&.{0xc0}) else OpCode.init(&.{0xc1}), - else => unreachable, - }, - .mr => return switch (tag) { - .adc => if (is_one_byte) OpCode.init(&.{0x10}) else OpCode.init(&.{0x11}), - .add => if (is_one_byte) OpCode.init(&.{0x00}) else OpCode.init(&.{0x01}), - .sub => if (is_one_byte) OpCode.init(&.{0x28}) else OpCode.init(&.{0x29}), - .xor => if (is_one_byte) OpCode.init(&.{0x30}) else OpCode.init(&.{0x31}), - .@"and" => if (is_one_byte) OpCode.init(&.{0x20}) else OpCode.init(&.{0x21}), - .@"or" => if (is_one_byte) OpCode.init(&.{0x08}) else OpCode.init(&.{0x09}), - .sbb => if (is_one_byte) OpCode.init(&.{0x18}) else OpCode.init(&.{0x19}), - .cmp => if (is_one_byte) OpCode.init(&.{0x38}) else OpCode.init(&.{0x39}), - .mov => if (is_one_byte) OpCode.init(&.{0x88}) else OpCode.init(&.{0x89}), - .@"test" => if (is_one_byte) OpCode.init(&.{0x84}) else OpCode.init(&.{0x85}), - .movsd => OpCode.init(&.{0xf2,0x0f,0x11}), - .movss => OpCode.init(&.{0xf3,0x0f,0x11}), - else => unreachable, - }, - .rm => return switch (tag) { - .adc => if (is_one_byte) OpCode.init(&.{0x12}) else OpCode.init(&.{0x13}), - .add => if (is_one_byte) OpCode.init(&.{0x02}) else OpCode.init(&.{0x03}), - .sub => if (is_one_byte) OpCode.init(&.{0x2a}) else OpCode.init(&.{0x2b}), - .xor => if (is_one_byte) OpCode.init(&.{0x32}) else OpCode.init(&.{0x33}), - .@"and" => if (is_one_byte) OpCode.init(&.{0x22}) else OpCode.init(&.{0x23}), - .@"or" => if (is_one_byte) OpCode.init(&.{0x0a}) else OpCode.init(&.{0x0b}), - .sbb => if (is_one_byte) OpCode.init(&.{0x1a}) else OpCode.init(&.{0x1b}), - .cmp => if (is_one_byte) OpCode.init(&.{0x3a}) else OpCode.init(&.{0x3b}), - .mov => if (is_one_byte) OpCode.init(&.{0x8a}) else OpCode.init(&.{0x8b}), - .movsx => if (is_one_byte) OpCode.init(&.{0x0f,0xbe}) else OpCode.init(&.{0x0f,0xbf}), - .movsxd => OpCode.init(&.{0x63}), - .movzx => if (is_one_byte) OpCode.init(&.{0x0f,0xb6}) else OpCode.init(&.{0x0f,0xb7}), - .lea => if (is_one_byte) OpCode.init(&.{0x8c}) else OpCode.init(&.{0x8d}), - .imul => OpCode.init(&.{0x0f,0xaf}), - - .cmova, - .cmovnbe, => OpCode.init(&.{0x0f,0x47}), - - .cmovae, - .cmovnb, => OpCode.init(&.{0x0f,0x43}), - - .cmovb, - .cmovc, - .cmovnae => OpCode.init(&.{0x0f,0x42}), - - .cmovbe, - .cmovna, => OpCode.init(&.{0x0f,0x46}), - - .cmove, - .cmovz, => OpCode.init(&.{0x0f,0x44}), - - .cmovg, - .cmovnle, => OpCode.init(&.{0x0f,0x4f}), - - .cmovge, - .cmovnl, => OpCode.init(&.{0x0f,0x4d}), - - .cmovl, - .cmovnge, => OpCode.init(&.{0x0f,0x4c}), - - .cmovle, - .cmovng, => OpCode.init(&.{0x0f,0x4e}), - - .cmovne, - .cmovnz, => OpCode.init(&.{0x0f,0x45}), - - .cmovno => OpCode.init(&.{0x0f,0x41}), - - .cmovnp, - .cmovpo, => OpCode.init(&.{0x0f,0x4b}), - - .cmovns => OpCode.init(&.{0x0f,0x49}), - - .cmovo => OpCode.init(&.{0x0f,0x40}), - - .cmovp, - .cmovpe, => OpCode.init(&.{0x0f,0x4a}), - - .cmovs => OpCode.init(&.{0x0f,0x48}), - - .movsd => OpCode.init(&.{0xf2,0x0f,0x10}), - .movss => OpCode.init(&.{0xf3,0x0f,0x10}), - .addsd => OpCode.init(&.{0xf2,0x0f,0x58}), - .addss => OpCode.init(&.{0xf3,0x0f,0x58}), - .ucomisd => OpCode.init(&.{0x66,0x0f,0x2e}), - .ucomiss => OpCode.init(&.{0x0f,0x2e}), - else => unreachable, - }, - .oi => return switch (tag) { - .mov => if (is_one_byte) OpCode.init(&.{0xb0}) else OpCode.init(&.{0xb8}), - else => unreachable, - }, - .fd => return switch (tag) { - .mov => if (is_one_byte) OpCode.init(&.{0xa0}) else OpCode.init(&.{0xa1}), - else => unreachable, - }, - .td => return switch (tag) { - .mov => if (is_one_byte) OpCode.init(&.{0xa2}) else OpCode.init(&.{0xa3}), - else => unreachable, - }, - .rmi => return switch (tag) { - .imul => if (is_one_byte) OpCode.init(&.{0x6b}) else OpCode.init(&.{0x69}), - else => unreachable, - }, - .mv => return switch (tag) { - .vmovsd, - .vmovss => OpCode.init(&.{0x11}), - else => unreachable, - }, - .vm => return switch (tag) { - .vmovsd, - .vmovss => OpCode.init(&.{0x10}), - .vucomisd, - .vucomiss => OpCode.init(&.{0x2e}), - else => unreachable, - }, - .rvm => return switch (tag) { - .vaddsd, - .vaddss => OpCode.init(&.{0x58}), - .vmovsd, - .vmovss => OpCode.init(&.{0x10}), - else => unreachable, - }, - .rvmi => return switch (tag) { - .vcmpsd, - .vcmpss => OpCode.init(&.{0xc2}), - else => unreachable, - }, - } - // zig fmt: on -} - -inline fn getModRmExt(tag: Tag) u3 { - return switch (tag) { - .adc => 0x2, - .add => 0x0, - .sub => 0x5, - .xor => 0x6, - .@"and" => 0x4, - .@"or" => 0x1, - .sbb => 0x3, - .cmp => 0x7, - .mov => 0x0, - .jmp_near => 0x4, - .call_near => 0x2, - .push => 0x6, - .pop => 0x0, - .@"test" => 0x0, - .seto, - .setno, - .setb, - .setc, - .setnae, - .setnb, - .setnc, - .setae, - .sete, - .setz, - .setne, - .setnz, - .setbe, - .setna, - .seta, - .setnbe, - .sets, - .setns, - .setp, - .setpe, - .setnp, - .setpo, - .setl, - .setnge, - .setnl, - .setge, - .setle, - .setng, - .setnle, - .setg, - => 0x0, - .shl, - .sal, - => 0x4, - .shr => 0x5, - .sar => 0x7, - .mul => 0x4, - .imul => 0x5, - .div => 0x6, - .idiv => 0x7, - .fisttp16 => 0x1, - .fisttp32 => 0x1, - .fisttp64 => 0x1, - .fld32 => 0x0, - .fld64 => 0x0, - else => unreachable, - }; -} - -const VexEncoding = struct { - prefix: Encoder.Vex, - reg: ?enum { - ndd, - nds, - dds, - }, -}; - -inline fn getVexEncoding(tag: Tag, enc: Encoding) VexEncoding { - const desc: struct { - reg: enum { - none, - ndd, - nds, - dds, - } = .none, - len_256: bool = false, - wig: bool = false, - lig: bool = false, - lz: bool = false, - lead_opc: enum { - l_0f, - l_0f_3a, - l_0f_38, - } = .l_0f, - simd_prefix: enum { - none, - p_66, - p_f2, - p_f3, - } = .none, - } = blk: { - switch (enc) { - .mv => switch (tag) { - .vmovsd => break :blk .{ .lig = true, .simd_prefix = .p_f2, .wig = true }, - .vmovss => break :blk .{ .lig = true, .simd_prefix = .p_f3, .wig = true }, - else => unreachable, - }, - .vm => switch (tag) { - .vmovsd => break :blk .{ .lig = true, .simd_prefix = .p_f2, .wig = true }, - .vmovss => break :blk .{ .lig = true, .simd_prefix = .p_f3, .wig = true }, - .vucomisd => break :blk .{ .lig = true, .simd_prefix = .p_66, .wig = true }, - .vucomiss => break :blk .{ .lig = true, .wig = true }, - else => unreachable, - }, - .rvm => switch (tag) { - .vaddsd => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f2, .wig = true }, - .vaddss => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f3, .wig = true }, - .vmovsd => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f2, .wig = true }, - .vmovss => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f3, .wig = true }, - else => unreachable, - }, - .rvmi => switch (tag) { - .vcmpsd => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f2, .wig = true }, - .vcmpss => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f3, .wig = true }, - else => unreachable, - }, - else => unreachable, - } - }; - - var vex: Encoder.Vex = .{}; - - if (desc.len_256) vex.len_256(); - if (desc.wig) vex.wig(); - if (desc.lig) vex.lig(); - if (desc.lz) vex.lz(); - - switch (desc.lead_opc) { - .l_0f => {}, - .l_0f_3a => vex.lead_opc_0f_3a(), - .l_0f_38 => vex.lead_opc_0f_38(), - } - - switch (desc.simd_prefix) { - .none => {}, - .p_66 => vex.simd_prefix_66(), - .p_f2 => vex.simd_prefix_f2(), - .p_f3 => vex.simd_prefix_f3(), - } - - return VexEncoding{ .prefix = vex, .reg = switch (desc.reg) { - .none => null, - .nds => .nds, - .dds => .dds, - .ndd => .ndd, - } }; -} - -const ScaleIndex = packed struct { - scale: u2, - index: Register, -}; - -const Memory = struct { - base: ?Register, - rip: bool = false, - disp: u32, - ptr_size: PtrSize, - scale_index: ?ScaleIndex = null, - - const PtrSize = enum(u2) { - byte_ptr = 0b00, - word_ptr = 0b01, - dword_ptr = 0b10, - qword_ptr = 0b11, - - fn new(bit_size: u64) PtrSize { - return @intToEnum(PtrSize, math.log2_int(u4, @intCast(u4, @divExact(bit_size, 8)))); - } - - /// Returns size in bits. - fn size(ptr_size: PtrSize) u64 { - return 8 * (math.powi(u8, 2, @enumToInt(ptr_size)) catch unreachable); - } - }; - - fn encode(mem_op: Memory, encoder: Encoder, operand: u3) void { - if (mem_op.base) |base| { - const dst = base.lowEnc(); - const src = operand; - if (dst == 4 or mem_op.scale_index != null) { - if (mem_op.disp == 0 and dst != 5) { - encoder.modRm_SIBDisp0(src); - if (mem_op.scale_index) |si| { - encoder.sib_scaleIndexBase(si.scale, si.index.lowEnc(), dst); - } else { - encoder.sib_base(dst); - } - } else if (immOpSize(mem_op.disp) == 8) { - encoder.modRm_SIBDisp8(src); - if (mem_op.scale_index) |si| { - encoder.sib_scaleIndexBaseDisp8(si.scale, si.index.lowEnc(), dst); - } else { - encoder.sib_baseDisp8(dst); - } - encoder.disp8(@bitCast(i8, @truncate(u8, mem_op.disp))); - } else { - encoder.modRm_SIBDisp32(src); - if (mem_op.scale_index) |si| { - encoder.sib_scaleIndexBaseDisp32(si.scale, si.index.lowEnc(), dst); - } else { - encoder.sib_baseDisp32(dst); - } - encoder.disp32(@bitCast(i32, mem_op.disp)); - } - } else { - if (mem_op.disp == 0 and dst != 5) { - encoder.modRm_indirectDisp0(src, dst); - } else if (immOpSize(mem_op.disp) == 8) { - encoder.modRm_indirectDisp8(src, dst); - encoder.disp8(@bitCast(i8, @truncate(u8, mem_op.disp))); - } else { - encoder.modRm_indirectDisp32(src, dst); - encoder.disp32(@bitCast(i32, mem_op.disp)); - } - } - } else { - if (mem_op.rip) { - encoder.modRm_RIPDisp32(operand); - } else { - encoder.modRm_SIBDisp0(operand); - if (mem_op.scale_index) |si| { - encoder.sib_scaleIndexDisp32(si.scale, si.index.lowEnc()); - } else { - encoder.sib_disp32(); - } - } - encoder.disp32(@bitCast(i32, mem_op.disp)); - } - } - - /// Returns size in bits. - fn size(memory: Memory) u64 { - return memory.ptr_size.size(); - } -}; - -fn encodeImm(encoder: Encoder, imm: u32, size: u64) void { - switch (size) { - 8 => encoder.imm8(@bitCast(i8, @truncate(u8, imm))), - 16 => encoder.imm16(@bitCast(i16, @truncate(u16, imm))), - 32, 64 => encoder.imm32(@bitCast(i32, imm)), - else => unreachable, - } -} - -const RegisterOrMemory = union(enum) { - register: Register, - memory: Memory, - - fn reg(register: Register) RegisterOrMemory { - return .{ .register = register }; - } - - fn mem(ptr_size: Memory.PtrSize, args: struct { - disp: u32, - base: ?Register = null, - scale_index: ?ScaleIndex = null, - }) RegisterOrMemory { - return .{ - .memory = .{ - .base = args.base, - .disp = args.disp, - .ptr_size = ptr_size, - .scale_index = args.scale_index, - }, - }; - } - - fn rip(ptr_size: Memory.PtrSize, disp: u32) RegisterOrMemory { - return .{ - .memory = .{ - .base = null, - .rip = true, - .disp = disp, - .ptr_size = ptr_size, - }, - }; - } - - /// Returns size in bits. - fn size(reg_or_mem: RegisterOrMemory) u64 { - return switch (reg_or_mem) { - .register => |register| register.size(), - .memory => |memory| memory.size(), - }; - } -}; - -fn lowerToZoEnc(tag: Tag, code: *std.ArrayList(u8)) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .zo, false); - const encoder = try Encoder.init(code, 2); - switch (tag) { - .cqo => { - encoder.rex(.{ - .w = true, - }); - }, - else => {}, - } - opc.encode(encoder); -} - -fn lowerToIEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) InnerError!void { - assert(!tag.isAvx()); - if (tag == .ret_far or tag == .ret_near) { - const encoder = try Encoder.init(code, 3); - const opc = getOpCode(tag, .i, false); - opc.encode(encoder); - encoder.imm16(@bitCast(i16, @truncate(u16, imm))); - return; - } - const opc = getOpCode(tag, .i, immOpSize(imm) == 8); - const encoder = try Encoder.init(code, 5); - if (immOpSize(imm) == 16) { - encoder.prefix16BitMode(); - } - opc.encode(encoder); - encodeImm(encoder, imm, immOpSize(imm)); -} - -fn lowerToOEnc(tag: Tag, reg: Register, code: *std.ArrayList(u8)) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .o, false); - const encoder = try Encoder.init(code, 3); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - encoder.rex(.{ - .w = false, - .b = reg.isExtended(), - }); - opc.encodeWithReg(encoder, reg); -} - -fn lowerToDEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .d, false); - const encoder = try Encoder.init(code, 6); - opc.encode(encoder); - encoder.imm32(@bitCast(i32, imm)); -} - -fn lowerToMxEnc(tag: Tag, reg_or_mem: RegisterOrMemory, enc: Encoding, code: *std.ArrayList(u8)) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, enc, reg_or_mem.size() == 8); - const modrm_ext = getModRmExt(tag); - switch (reg_or_mem) { - .register => |reg| { - const encoder = try Encoder.init(code, 4); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - const wide = if (tag == .jmp_near) false else setRexWRegister(reg); - encoder.rex(.{ - .w = wide, - .b = reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(modrm_ext, reg.lowEnc()); - }, - .memory => |mem_op| { - const encoder = try Encoder.init(code, 8); - if (mem_op.ptr_size == .word_ptr) { - encoder.prefix16BitMode(); - } - if (mem_op.base) |base| { - const wide = if (tag == .jmp_near) false else mem_op.ptr_size == .qword_ptr; - encoder.rex(.{ - .w = wide, - .b = base.isExtended(), - .x = if (mem_op.scale_index) |si| si.index.isExtended() else false, - }); - } - opc.encode(encoder); - mem_op.encode(encoder, modrm_ext); - }, - } -} - -fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { - return lowerToMxEnc(tag, reg_or_mem, .m, code); -} - -fn lowerToM1Enc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { - return lowerToMxEnc(tag, reg_or_mem, .m1, code); -} - -fn lowerToMcEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { - return lowerToMxEnc(tag, reg_or_mem, .mc, code); -} - -fn lowerToTdEnc(tag: Tag, moffs: u64, reg: Register, code: *std.ArrayList(u8)) InnerError!void { - return lowerToTdFdEnc(tag, reg, moffs, code, true); -} - -fn lowerToFdEnc(tag: Tag, reg: Register, moffs: u64, code: *std.ArrayList(u8)) InnerError!void { - return lowerToTdFdEnc(tag, reg, moffs, code, false); -} - -fn lowerToTdFdEnc(tag: Tag, reg: Register, moffs: u64, code: *std.ArrayList(u8), td: bool) InnerError!void { - assert(!tag.isAvx()); - const opc = if (td) getOpCode(tag, .td, reg.size() == 8) else getOpCode(tag, .fd, reg.size() == 8); - const encoder = try Encoder.init(code, 10); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - encoder.rex(.{ - .w = setRexWRegister(reg), - }); - opc.encode(encoder); - switch (reg.size()) { - 8 => encoder.imm8(@bitCast(i8, @truncate(u8, moffs))), - 16 => encoder.imm16(@bitCast(i16, @truncate(u16, moffs))), - 32 => encoder.imm32(@bitCast(i32, @truncate(u32, moffs))), - 64 => encoder.imm64(moffs), - else => unreachable, - } -} - -fn lowerToOiEnc(tag: Tag, reg: Register, imm: u64, code: *std.ArrayList(u8)) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .oi, reg.size() == 8); - const encoder = try Encoder.init(code, 10); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - encoder.rex(.{ - .w = setRexWRegister(reg), - .b = reg.isExtended(), - }); - opc.encodeWithReg(encoder, reg); - switch (reg.size()) { - 8 => encoder.imm8(@bitCast(i8, @truncate(u8, imm))), - 16 => encoder.imm16(@bitCast(i16, @truncate(u16, imm))), - 32 => encoder.imm32(@bitCast(i32, @truncate(u32, imm))), - 64 => encoder.imm64(imm), - else => unreachable, - } -} - -fn lowerToMiXEnc( - tag: Tag, - reg_or_mem: RegisterOrMemory, - imm: u32, - enc: Encoding, - code: *std.ArrayList(u8), -) InnerError!void { - assert(!tag.isAvx()); - const modrm_ext = getModRmExt(tag); - const opc = getOpCode(tag, enc, reg_or_mem.size() == 8); - switch (reg_or_mem) { - .register => |dst_reg| { - const encoder = try Encoder.init(code, 7); - if (dst_reg.size() == 16) { - // 0x66 prefix switches to the non-default size; here we assume a switch from - // the default 32bits to 16bits operand-size. - // More info: https://www.cs.uni-potsdam.de/desn/lehre/ss15/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf#page=32&zoom=auto,-159,773 - encoder.prefix16BitMode(); - } - encoder.rex(.{ - .w = setRexWRegister(dst_reg), - .b = dst_reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(modrm_ext, dst_reg.lowEnc()); - encodeImm(encoder, imm, if (enc == .mi8) 8 else dst_reg.size()); - }, - .memory => |dst_mem| { - const encoder = try Encoder.init(code, 12); - if (dst_mem.ptr_size == .word_ptr) { - encoder.prefix16BitMode(); - } - if (dst_mem.base) |base| { - encoder.rex(.{ - .w = dst_mem.ptr_size == .qword_ptr, - .b = base.isExtended(), - .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false, - }); - } else { - encoder.rex(.{ - .w = dst_mem.ptr_size == .qword_ptr, - .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false, - }); - } - opc.encode(encoder); - dst_mem.encode(encoder, modrm_ext); - encodeImm(encoder, imm, if (enc == .mi8) 8 else dst_mem.ptr_size.size()); - }, - } -} - -fn lowerToMiImm8Enc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u8, code: *std.ArrayList(u8)) InnerError!void { - return lowerToMiXEnc(tag, reg_or_mem, imm, .mi8, code); -} - -fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u32, code: *std.ArrayList(u8)) InnerError!void { - return lowerToMiXEnc(tag, reg_or_mem, imm, .mi, code); -} - -fn lowerToRmEnc( - tag: Tag, - reg: Register, - reg_or_mem: RegisterOrMemory, - code: *std.ArrayList(u8), -) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .rm, reg.size() == 8 or reg_or_mem.size() == 8); - switch (reg_or_mem) { - .register => |src_reg| { - const encoder = try Encoder.init(code, 5); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - encoder.rex(.{ - .w = setRexWRegister(reg) or setRexWRegister(src_reg), - .r = reg.isExtended(), - .b = src_reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(reg.lowEnc(), src_reg.lowEnc()); - }, - .memory => |src_mem| { - const encoder = try Encoder.init(code, 9); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - if (src_mem.base) |base| { - // TODO handle 32-bit base register - requires prefix 0x67 - // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 - encoder.rex(.{ - .w = setRexWRegister(reg), - .r = reg.isExtended(), - .b = base.isExtended(), - .x = if (src_mem.scale_index) |si| si.index.isExtended() else false, - }); - } else { - encoder.rex(.{ - .w = setRexWRegister(reg), - .r = reg.isExtended(), - .x = if (src_mem.scale_index) |si| si.index.isExtended() else false, - }); - } - opc.encode(encoder); - src_mem.encode(encoder, reg.lowEnc()); - }, - } -} - -fn lowerToMrEnc( - tag: Tag, - reg_or_mem: RegisterOrMemory, - reg: Register, - code: *std.ArrayList(u8), -) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .mr, reg.size() == 8 or reg_or_mem.size() == 8); - switch (reg_or_mem) { - .register => |dst_reg| { - const encoder = try Encoder.init(code, 4); - if (dst_reg.size() == 16) { - encoder.prefix16BitMode(); - } - encoder.rex(.{ - .w = setRexWRegister(dst_reg) or setRexWRegister(reg), - .r = reg.isExtended(), - .b = dst_reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(reg.lowEnc(), dst_reg.lowEnc()); - }, - .memory => |dst_mem| { - const encoder = try Encoder.init(code, 9); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - if (dst_mem.base) |base| { - encoder.rex(.{ - .w = dst_mem.ptr_size == .qword_ptr or setRexWRegister(reg), - .r = reg.isExtended(), - .b = base.isExtended(), - .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false, - }); - } else { - encoder.rex(.{ - .w = dst_mem.ptr_size == .qword_ptr or setRexWRegister(reg), - .r = reg.isExtended(), - .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false, - }); - } - opc.encode(encoder); - dst_mem.encode(encoder, reg.lowEnc()); - }, - } -} - -fn lowerToRmiEnc( - tag: Tag, - reg: Register, - reg_or_mem: RegisterOrMemory, - imm: u32, - code: *std.ArrayList(u8), -) InnerError!void { - assert(!tag.isAvx()); - const opc = getOpCode(tag, .rmi, false); - const encoder = try Encoder.init(code, 13); - if (reg.size() == 16) { - encoder.prefix16BitMode(); - } - switch (reg_or_mem) { - .register => |src_reg| { - encoder.rex(.{ - .w = setRexWRegister(reg) or setRexWRegister(src_reg), - .r = reg.isExtended(), - .b = src_reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(reg.lowEnc(), src_reg.lowEnc()); - }, - .memory => |src_mem| { - if (src_mem.base) |base| { - // TODO handle 32-bit base register - requires prefix 0x67 - // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 - encoder.rex(.{ - .w = setRexWRegister(reg), - .r = reg.isExtended(), - .b = base.isExtended(), - .x = if (src_mem.scale_index) |si| si.index.isExtended() else false, - }); - } else { - encoder.rex(.{ - .w = setRexWRegister(reg), - .r = reg.isExtended(), - .x = if (src_mem.scale_index) |si| si.index.isExtended() else false, - }); - } - opc.encode(encoder); - src_mem.encode(encoder, reg.lowEnc()); - }, - } - encodeImm(encoder, imm, reg.size()); -} - -/// Also referred to as XM encoding in Intel manual. -fn lowerToVmEnc( - tag: Tag, - reg: Register, - reg_or_mem: RegisterOrMemory, - code: *std.ArrayList(u8), -) InnerError!void { - const opc = getOpCode(tag, .vm, false); - var enc = getVexEncoding(tag, .vm); - const vex = &enc.prefix; - switch (reg_or_mem) { - .register => |src_reg| { - const encoder = try Encoder.init(code, 5); - vex.rex(.{ - .r = reg.isExtended(), - .b = src_reg.isExtended(), - }); - encoder.vex(enc.prefix); - opc.encode(encoder); - encoder.modRm_direct(reg.lowEnc(), src_reg.lowEnc()); - }, - .memory => |src_mem| { - const encoder = try Encoder.init(code, 10); - if (src_mem.base) |base| { - vex.rex(.{ - .r = reg.isExtended(), - .b = base.isExtended(), - .x = if (src_mem.scale_index) |si| si.index.isExtended() else false, - }); - } else { - vex.rex(.{ - .r = reg.isExtended(), - .x = if (src_mem.scale_index) |si| si.index.isExtended() else false, - }); - } - encoder.vex(enc.prefix); - opc.encode(encoder); - src_mem.encode(encoder, reg.lowEnc()); - }, - } -} - -/// Usually referred to as MR encoding with V/V in Intel manual. -fn lowerToMvEnc( - tag: Tag, - reg_or_mem: RegisterOrMemory, - reg: Register, - code: *std.ArrayList(u8), -) InnerError!void { - const opc = getOpCode(tag, .mv, false); - var enc = getVexEncoding(tag, .mv); - const vex = &enc.prefix; - switch (reg_or_mem) { - .register => |dst_reg| { - const encoder = try Encoder.init(code, 4); - vex.rex(.{ - .r = reg.isExtended(), - .b = dst_reg.isExtended(), - }); - encoder.vex(enc.prefix); - opc.encode(encoder); - encoder.modRm_direct(reg.lowEnc(), dst_reg.lowEnc()); - }, - .memory => |dst_mem| { - const encoder = try Encoder.init(code, 10); - if (dst_mem.base) |base| { - vex.rex(.{ - .r = reg.isExtended(), - .b = base.isExtended(), - .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false, - }); - } else { - vex.rex(.{ - .r = reg.isExtended(), - .x = if (dst_mem.scale_index) |si| si.index.isExtended() else false, - }); - } - encoder.vex(enc.prefix); - opc.encode(encoder); - dst_mem.encode(encoder, reg.lowEnc()); - }, - } -} - -fn lowerToRvmEnc( - tag: Tag, - reg1: Register, - reg2: Register, - reg_or_mem: RegisterOrMemory, - code: *std.ArrayList(u8), -) InnerError!void { - const opc = getOpCode(tag, .rvm, false); - var enc = getVexEncoding(tag, .rvm); - const vex = &enc.prefix; - switch (reg_or_mem) { - .register => |reg3| { - if (enc.reg) |vvvv| { - switch (vvvv) { - .nds => vex.reg(reg2.enc()), - else => unreachable, // TODO - } - } - const encoder = try Encoder.init(code, 5); - vex.rex(.{ - .r = reg1.isExtended(), - .b = reg3.isExtended(), - }); - encoder.vex(enc.prefix); - opc.encode(encoder); - encoder.modRm_direct(reg1.lowEnc(), reg3.lowEnc()); - }, - .memory => |dst_mem| { - _ = dst_mem; - unreachable; // TODO - }, - } -} - -fn lowerToRvmiEnc( - tag: Tag, - reg1: Register, - reg2: Register, - reg_or_mem: RegisterOrMemory, - imm: u32, - code: *std.ArrayList(u8), -) InnerError!void { - const opc = getOpCode(tag, .rvmi, false); - var enc = getVexEncoding(tag, .rvmi); - const vex = &enc.prefix; - const encoder: Encoder = blk: { - switch (reg_or_mem) { - .register => |reg3| { - if (enc.reg) |vvvv| { - switch (vvvv) { - .nds => vex.reg(reg2.enc()), - else => unreachable, // TODO - } - } - const encoder = try Encoder.init(code, 5); - vex.rex(.{ - .r = reg1.isExtended(), - .b = reg3.isExtended(), - }); - encoder.vex(enc.prefix); - opc.encode(encoder); - encoder.modRm_direct(reg1.lowEnc(), reg3.lowEnc()); - break :blk encoder; - }, - .memory => |dst_mem| { - _ = dst_mem; - unreachable; // TODO - }, - } - }; - encodeImm(encoder, imm, 8); // TODO -} - -fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void { - assert(expected.len > 0); - if (mem.eql(u8, expected, given)) return; - const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)}); - defer testing.allocator.free(expected_fmt); - const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)}); - defer testing.allocator.free(given_fmt); - const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?; - var padding = try testing.allocator.alloc(u8, idx + 5); - defer testing.allocator.free(padding); - mem.set(u8, padding, ' '); - std.debug.print("\nASM: {s}\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ - assembly, - expected_fmt, - given_fmt, - padding, - }); - return error.TestFailed; -} - -const TestEmit = struct { - code_buffer: std.ArrayList(u8), - next: usize = 0, - - fn init() TestEmit { - return .{ - .code_buffer = std.ArrayList(u8).init(testing.allocator), - }; - } - - fn deinit(emit: *TestEmit) void { - emit.code_buffer.deinit(); - emit.next = undefined; - } - - fn code(emit: *TestEmit) *std.ArrayList(u8) { - emit.next = emit.code_buffer.items.len; - return &emit.code_buffer; - } - - fn lowered(emit: TestEmit) []const u8 { - return emit.code_buffer.items[emit.next..]; - } -}; - -test "lower MI encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToMiEnc(.mov, RegisterOrMemory.reg(.rax), 0x10, emit.code()); - try expectEqualHexStrings("\x48\xc7\xc0\x10\x00\x00\x00", emit.lowered(), "mov rax, 0x10"); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.dword_ptr, .{ .disp = 0, .base = .r11 }), 0x10, emit.code()); - try expectEqualHexStrings("\x41\xc7\x03\x10\x00\x00\x00", emit.lowered(), "mov dword ptr [r11 + 0], 0x10"); - try lowerToMiEnc(.add, RegisterOrMemory.mem(.dword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -8)), - .base = .rdx, - }), 0x10, emit.code()); - try expectEqualHexStrings("\x81\x42\xF8\x10\x00\x00\x00", emit.lowered(), "add dword ptr [rdx - 8], 0x10"); - try lowerToMiEnc(.sub, RegisterOrMemory.mem(.dword_ptr, .{ - .disp = 0x10000000, - .base = .r11, - }), 0x10, emit.code()); - try expectEqualHexStrings( - "\x41\x81\xab\x00\x00\x00\x10\x10\x00\x00\x00", - emit.lowered(), - "sub dword ptr [r11 + 0x10000000], 0x10", - ); - try lowerToMiEnc(.@"and", RegisterOrMemory.mem(.dword_ptr, .{ .disp = 0x10000000 }), 0x10, emit.code()); - try expectEqualHexStrings( - "\x81\x24\x25\x00\x00\x00\x10\x10\x00\x00\x00", - emit.lowered(), - "and dword ptr [ds:0x10000000], 0x10", - ); - try lowerToMiEnc(.@"and", RegisterOrMemory.mem(.dword_ptr, .{ - .disp = 0x10000000, - .base = .r12, - }), 0x10, emit.code()); - try expectEqualHexStrings( - "\x41\x81\xA4\x24\x00\x00\x00\x10\x10\x00\x00\x00", - emit.lowered(), - "and dword ptr [r12 + 0x10000000], 0x10", - ); - try lowerToMiEnc(.mov, RegisterOrMemory.rip(.qword_ptr, 0x10), 0x10, emit.code()); - try expectEqualHexStrings( - "\x48\xC7\x05\x10\x00\x00\x00\x10\x00\x00\x00", - emit.lowered(), - "mov qword ptr [rip + 0x10], 0x10", - ); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -8)), - .base = .rbp, - }), 0x10, emit.code()); - try expectEqualHexStrings( - "\x48\xc7\x45\xf8\x10\x00\x00\x00", - emit.lowered(), - "mov qword ptr [rbp - 8], 0x10", - ); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.word_ptr, .{ - .disp = @bitCast(u32, @as(i32, -2)), - .base = .rbp, - }), 0x10, emit.code()); - try expectEqualHexStrings("\x66\xC7\x45\xFE\x10\x00", emit.lowered(), "mov word ptr [rbp - 2], 0x10"); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.byte_ptr, .{ - .disp = @bitCast(u32, @as(i32, -1)), - .base = .rbp, - }), 0x10, emit.code()); - try expectEqualHexStrings("\xC6\x45\xFF\x10", emit.lowered(), "mov byte ptr [rbp - 1], 0x10"); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = 0x10000000, - .scale_index = .{ - .scale = 1, - .index = .rcx, - }, - }), 0x10, emit.code()); - try expectEqualHexStrings( - "\x48\xC7\x04\x4D\x00\x00\x00\x10\x10\x00\x00\x00", - emit.lowered(), - "mov qword ptr [rcx*2 + 0x10000000], 0x10", - ); - - try lowerToMiImm8Enc(.add, RegisterOrMemory.reg(.rax), 0x10, emit.code()); - try expectEqualHexStrings("\x48\x83\xC0\x10", emit.lowered(), "add rax, 0x10"); -} - -test "lower RM encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToRmEnc(.mov, .rax, RegisterOrMemory.reg(.rbx), emit.code()); - try expectEqualHexStrings("\x48\x8b\xc3", emit.lowered(), "mov rax, rbx"); - try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0, .base = .r11 }), emit.code()); - try expectEqualHexStrings("\x49\x8b\x03", emit.lowered(), "mov rax, qword ptr [r11 + 0]"); - try lowerToRmEnc(.add, .r11, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0x10000000 }), emit.code()); - try expectEqualHexStrings( - "\x4C\x03\x1C\x25\x00\x00\x00\x10", - emit.lowered(), - "add r11, qword ptr [ds:0x10000000]", - ); - try lowerToRmEnc(.add, .r12b, RegisterOrMemory.mem(.byte_ptr, .{ .disp = 0x10000000 }), emit.code()); - try expectEqualHexStrings( - "\x44\x02\x24\x25\x00\x00\x00\x10", - emit.lowered(), - "add r11b, byte ptr [ds:0x10000000]", - ); - try lowerToRmEnc(.sub, .r11, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = 0x10000000, - .base = .r13, - }), emit.code()); - try expectEqualHexStrings( - "\x4D\x2B\x9D\x00\x00\x00\x10", - emit.lowered(), - "sub r11, qword ptr [r13 + 0x10000000]", - ); - try lowerToRmEnc(.sub, .r11, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = 0x10000000, - .base = .r12, - }), emit.code()); - try expectEqualHexStrings( - "\x4D\x2B\x9C\x24\x00\x00\x00\x10", - emit.lowered(), - "sub r11, qword ptr [r12 + 0x10000000]", - ); - try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -4)), - .base = .rbp, - }), emit.code()); - try expectEqualHexStrings("\x48\x8B\x45\xFC", emit.lowered(), "mov rax, qword ptr [rbp - 4]"); - try lowerToRmEnc(.lea, .rax, RegisterOrMemory.rip(.qword_ptr, 0x10), emit.code()); - try expectEqualHexStrings("\x48\x8D\x05\x10\x00\x00\x00", emit.lowered(), "lea rax, [rip + 0x10]"); - try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -8)), - .base = .rbp, - .scale_index = .{ - .scale = 0, - .index = .rcx, - }, - }), emit.code()); - try expectEqualHexStrings("\x48\x8B\x44\x0D\xF8", emit.lowered(), "mov rax, qword ptr [rbp + rcx*1 - 8]"); - try lowerToRmEnc(.mov, .eax, RegisterOrMemory.mem(.dword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -4)), - .base = .rbp, - .scale_index = .{ - .scale = 2, - .index = .rdx, - }, - }), emit.code()); - try expectEqualHexStrings("\x8B\x44\x95\xFC", emit.lowered(), "mov eax, dword ptr [rbp + rdx*4 - 4]"); - try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -8)), - .base = .rbp, - .scale_index = .{ - .scale = 3, - .index = .rcx, - }, - }), emit.code()); - try expectEqualHexStrings("\x48\x8B\x44\xCD\xF8", emit.lowered(), "mov rax, qword ptr [rbp + rcx*8 - 8]"); - try lowerToRmEnc(.mov, .r8b, RegisterOrMemory.mem(.byte_ptr, .{ - .disp = @bitCast(u32, @as(i32, -24)), - .base = .rsi, - .scale_index = .{ - .scale = 0, - .index = .rcx, - }, - }), emit.code()); - try expectEqualHexStrings("\x44\x8A\x44\x0E\xE8", emit.lowered(), "mov r8b, byte ptr [rsi + rcx*1 - 24]"); - try lowerToRmEnc(.lea, .rsi, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = 0, - .base = .rbp, - .scale_index = .{ - .scale = 0, - .index = .rcx, - }, - }), emit.code()); - try expectEqualHexStrings("\x48\x8D\x74\x0D\x00", emit.lowered(), "lea rsi, qword ptr [rbp + rcx*1 + 0]"); -} - -test "lower MR encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToMrEnc(.mov, RegisterOrMemory.reg(.rax), .rbx, emit.code()); - try expectEqualHexStrings("\x48\x89\xd8", emit.lowered(), "mov rax, rbx"); - try lowerToMrEnc(.mov, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -4)), - .base = .rbp, - }), .r11, emit.code()); - try expectEqualHexStrings("\x4c\x89\x5d\xfc", emit.lowered(), "mov qword ptr [rbp - 4], r11"); - try lowerToMrEnc(.add, RegisterOrMemory.mem(.byte_ptr, .{ .disp = 0x10000000 }), .r12b, emit.code()); - try expectEqualHexStrings( - "\x44\x00\x24\x25\x00\x00\x00\x10", - emit.lowered(), - "add byte ptr [ds:0x10000000], r12b", - ); - try lowerToMrEnc(.add, RegisterOrMemory.mem(.dword_ptr, .{ .disp = 0x10000000 }), .r12d, emit.code()); - try expectEqualHexStrings( - "\x44\x01\x24\x25\x00\x00\x00\x10", - emit.lowered(), - "add dword ptr [ds:0x10000000], r12d", - ); - try lowerToMrEnc(.sub, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = 0x10000000, - .base = .r11, - }), .r12, emit.code()); - try expectEqualHexStrings( - "\x4D\x29\xA3\x00\x00\x00\x10", - emit.lowered(), - "sub qword ptr [r11 + 0x10000000], r12", - ); - try lowerToMrEnc(.mov, RegisterOrMemory.rip(.qword_ptr, 0x10), .r12, emit.code()); - try expectEqualHexStrings("\x4C\x89\x25\x10\x00\x00\x00", emit.lowered(), "mov qword ptr [rip + 0x10], r12"); -} - -test "lower OI encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToOiEnc(.mov, .rax, 0x1000000000000000, emit.code()); - try expectEqualHexStrings( - "\x48\xB8\x00\x00\x00\x00\x00\x00\x00\x10", - emit.lowered(), - "movabs rax, 0x1000000000000000", - ); - try lowerToOiEnc(.mov, .r11, 0x1000000000000000, emit.code()); - try expectEqualHexStrings( - "\x49\xBB\x00\x00\x00\x00\x00\x00\x00\x10", - emit.lowered(), - "movabs r11, 0x1000000000000000", - ); - try lowerToOiEnc(.mov, .r11d, 0x10000000, emit.code()); - try expectEqualHexStrings("\x41\xBB\x00\x00\x00\x10", emit.lowered(), "mov r11d, 0x10000000"); - try lowerToOiEnc(.mov, .r11w, 0x1000, emit.code()); - try expectEqualHexStrings("\x66\x41\xBB\x00\x10", emit.lowered(), "mov r11w, 0x1000"); - try lowerToOiEnc(.mov, .r11b, 0x10, emit.code()); - try expectEqualHexStrings("\x41\xB3\x10", emit.lowered(), "mov r11b, 0x10"); -} - -test "lower FD/TD encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToFdEnc(.mov, .rax, 0x1000000000000000, emit.code()); - try expectEqualHexStrings( - "\x48\xa1\x00\x00\x00\x00\x00\x00\x00\x10", - emit.lowered(), - "mov rax, ds:0x1000000000000000", - ); - try lowerToFdEnc(.mov, .eax, 0x10000000, emit.code()); - try expectEqualHexStrings("\xa1\x00\x00\x00\x10", emit.lowered(), "mov eax, ds:0x10000000"); - try lowerToFdEnc(.mov, .ax, 0x1000, emit.code()); - try expectEqualHexStrings("\x66\xa1\x00\x10", emit.lowered(), "mov ax, ds:0x1000"); - try lowerToFdEnc(.mov, .al, 0x10, emit.code()); - try expectEqualHexStrings("\xa0\x10", emit.lowered(), "mov al, ds:0x10"); -} - -test "lower M encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToMEnc(.jmp_near, RegisterOrMemory.reg(.r12), emit.code()); - try expectEqualHexStrings("\x41\xFF\xE4", emit.lowered(), "jmp r12"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.reg(.r12w), emit.code()); - try expectEqualHexStrings("\x66\x41\xFF\xE4", emit.lowered(), "jmp r12w"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0, .base = .r12 }), emit.code()); - try expectEqualHexStrings("\x41\xFF\x24\x24", emit.lowered(), "jmp qword ptr [r12]"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.word_ptr, .{ .disp = 0, .base = .r12 }), emit.code()); - try expectEqualHexStrings("\x66\x41\xFF\x24\x24", emit.lowered(), "jmp word ptr [r12]"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0x10, .base = .r12 }), emit.code()); - try expectEqualHexStrings("\x41\xFF\x64\x24\x10", emit.lowered(), "jmp qword ptr [r12 + 0x10]"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = 0x1000, - .base = .r12, - }), emit.code()); - try expectEqualHexStrings( - "\x41\xFF\xA4\x24\x00\x10\x00\x00", - emit.lowered(), - "jmp qword ptr [r12 + 0x1000]", - ); - try lowerToMEnc(.jmp_near, RegisterOrMemory.rip(.qword_ptr, 0x10), emit.code()); - try expectEqualHexStrings("\xFF\x25\x10\x00\x00\x00", emit.lowered(), "jmp qword ptr [rip + 0x10]"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.qword_ptr, .{ .disp = 0x10 }), emit.code()); - try expectEqualHexStrings("\xFF\x24\x25\x10\x00\x00\x00", emit.lowered(), "jmp qword ptr [ds:0x10]"); - try lowerToMEnc(.seta, RegisterOrMemory.reg(.r11b), emit.code()); - try expectEqualHexStrings("\x41\x0F\x97\xC3", emit.lowered(), "seta r11b"); - try lowerToMEnc(.idiv, RegisterOrMemory.reg(.rax), emit.code()); - try expectEqualHexStrings("\x48\xF7\xF8", emit.lowered(), "idiv rax"); - try lowerToMEnc(.imul, RegisterOrMemory.reg(.al), emit.code()); - try expectEqualHexStrings("\xF6\xE8", emit.lowered(), "imul al"); -} - -test "lower M1 and MC encodings" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12), emit.code()); - try expectEqualHexStrings("\x49\xD1\xE4", emit.lowered(), "sal r12, 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12d), emit.code()); - try expectEqualHexStrings("\x41\xD1\xE4", emit.lowered(), "sal r12d, 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12w), emit.code()); - try expectEqualHexStrings("\x66\x41\xD1\xE4", emit.lowered(), "sal r12w, 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12b), emit.code()); - try expectEqualHexStrings("\x41\xD0\xE4", emit.lowered(), "sal r12b, 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.reg(.rax), emit.code()); - try expectEqualHexStrings("\x48\xD1\xE0", emit.lowered(), "sal rax, 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.reg(.eax), emit.code()); - try expectEqualHexStrings("\xD1\xE0", emit.lowered(), "sal eax, 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -0x10)), - .base = .rbp, - }), emit.code()); - try expectEqualHexStrings("\x48\xD1\x65\xF0", emit.lowered(), "sal qword ptr [rbp - 0x10], 1"); - try lowerToM1Enc(.sal, RegisterOrMemory.mem(.dword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -0x10)), - .base = .rbp, - }), emit.code()); - try expectEqualHexStrings("\xD1\x65\xF0", emit.lowered(), "sal dword ptr [rbp - 0x10], 1"); - - try lowerToMcEnc(.shr, RegisterOrMemory.reg(.r12), emit.code()); - try expectEqualHexStrings("\x49\xD3\xEC", emit.lowered(), "shr r12, cl"); - try lowerToMcEnc(.shr, RegisterOrMemory.reg(.rax), emit.code()); - try expectEqualHexStrings("\x48\xD3\xE8", emit.lowered(), "shr rax, cl"); - - try lowerToMcEnc(.sar, RegisterOrMemory.reg(.rsi), emit.code()); - try expectEqualHexStrings("\x48\xD3\xFE", emit.lowered(), "sar rsi, cl"); -} - -test "lower O encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToOEnc(.pop, .r12, emit.code()); - try expectEqualHexStrings("\x41\x5c", emit.lowered(), "pop r12"); - try lowerToOEnc(.push, .r12w, emit.code()); - try expectEqualHexStrings("\x66\x41\x54", emit.lowered(), "push r12w"); -} - -test "lower RMI encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToRmiEnc(.imul, .rax, RegisterOrMemory.mem(.qword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -8)), - .base = .rbp, - }), 0x10, emit.code()); - try expectEqualHexStrings( - "\x48\x69\x45\xF8\x10\x00\x00\x00", - emit.lowered(), - "imul rax, qword ptr [rbp - 8], 0x10", - ); - try lowerToRmiEnc(.imul, .eax, RegisterOrMemory.mem(.dword_ptr, .{ - .disp = @bitCast(u32, @as(i32, -4)), - .base = .rbp, - }), 0x10, emit.code()); - try expectEqualHexStrings("\x69\x45\xFC\x10\x00\x00\x00", emit.lowered(), "imul eax, dword ptr [rbp - 4], 0x10"); - try lowerToRmiEnc(.imul, .ax, RegisterOrMemory.mem(.word_ptr, .{ - .disp = @bitCast(u32, @as(i32, -2)), - .base = .rbp, - }), 0x10, emit.code()); - try expectEqualHexStrings("\x66\x69\x45\xFE\x10\x00", emit.lowered(), "imul ax, word ptr [rbp - 2], 0x10"); - try lowerToRmiEnc(.imul, .r12, RegisterOrMemory.reg(.r12), 0x10, emit.code()); - try expectEqualHexStrings("\x4D\x69\xE4\x10\x00\x00\x00", emit.lowered(), "imul r12, r12, 0x10"); - try lowerToRmiEnc(.imul, .r12w, RegisterOrMemory.reg(.r12w), 0x10, emit.code()); - try expectEqualHexStrings("\x66\x45\x69\xE4\x10\x00", emit.lowered(), "imul r12w, r12w, 0x10"); -} - -test "lower MV encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToMvEnc(.vmovsd, RegisterOrMemory.rip(.qword_ptr, 0x10), .xmm1, emit.code()); - try expectEqualHexStrings( - "\xC5\xFB\x11\x0D\x10\x00\x00\x00", - emit.lowered(), - "vmovsd qword ptr [rip + 0x10], xmm1", - ); -} - -test "lower VM encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToVmEnc(.vmovsd, .xmm1, RegisterOrMemory.rip(.qword_ptr, 0x10), emit.code()); - try expectEqualHexStrings( - "\xC5\xFB\x10\x0D\x10\x00\x00\x00", - emit.lowered(), - "vmovsd xmm1, qword ptr [rip + 0x10]", - ); -} - -test "lower to RVM encoding" { - var emit = TestEmit.init(); - defer emit.deinit(); - try lowerToRvmEnc(.vaddsd, .xmm0, .xmm1, RegisterOrMemory.reg(.xmm2), emit.code()); - try expectEqualHexStrings("\xC5\xF3\x58\xC2", emit.lowered(), "vaddsd xmm0, xmm1, xmm2"); - try lowerToRvmEnc(.vaddsd, .xmm0, .xmm0, RegisterOrMemory.reg(.xmm1), emit.code()); - try expectEqualHexStrings("\xC5\xFB\x58\xC1", emit.lowered(), "vaddsd xmm0, xmm0, xmm1"); -} diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig new file mode 100644 index 0000000000..2cccded7ec --- /dev/null +++ b/src/arch/x86_64/Encoding.zig @@ -0,0 +1,521 @@ +const Encoding = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const math = std.math; + +const bits = @import("bits.zig"); +const encoder = @import("encoder.zig"); +const Instruction = encoder.Instruction; +const Register = bits.Register; +const Rex = encoder.Rex; +const LegacyPrefixes = encoder.LegacyPrefixes; + +const table = @import("encodings.zig").table; + +mnemonic: Mnemonic, +op_en: OpEn, +op1: Op, +op2: Op, +op3: Op, +op4: Op, +opc_len: u2, +opc: [3]u8, +modrm_ext: u3, +mode: Mode, + +pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { + op1: Instruction.Operand, + op2: Instruction.Operand, + op3: Instruction.Operand, + op4: Instruction.Operand, +}) ?Encoding { + const input_op1 = Op.fromOperand(args.op1); + const input_op2 = Op.fromOperand(args.op2); + const input_op3 = Op.fromOperand(args.op3); + const input_op4 = Op.fromOperand(args.op4); + + // TODO work out what is the maximum number of variants we can actually find in one swoop. + var candidates: [10]Encoding = undefined; + var count: usize = 0; + inline for (table) |entry| { + const enc = Encoding{ + .mnemonic = entry[0], + .op_en = entry[1], + .op1 = entry[2], + .op2 = entry[3], + .op3 = entry[4], + .op4 = entry[5], + .opc_len = entry[6], + .opc = .{ entry[7], entry[8], entry[9] }, + .modrm_ext = entry[10], + .mode = entry[11], + }; + if (enc.mnemonic == mnemonic and + input_op1.isSubset(enc.op1, enc.mode) and + input_op2.isSubset(enc.op2, enc.mode) and + input_op3.isSubset(enc.op3, enc.mode) and + input_op4.isSubset(enc.op4, enc.mode)) + { + candidates[count] = enc; + count += 1; + } + } + + if (count == 0) return null; + if (count == 1) return candidates[0]; + + const EncodingLength = struct { + fn estimate(encoding: Encoding, params: struct { + op1: Instruction.Operand, + op2: Instruction.Operand, + op3: Instruction.Operand, + op4: Instruction.Operand, + }) usize { + var inst = Instruction{ + .op1 = params.op1, + .op2 = params.op2, + .op3 = params.op3, + .op4 = params.op4, + .encoding = encoding, + }; + var cwriter = std.io.countingWriter(std.io.null_writer); + inst.encode(cwriter.writer()) catch unreachable; + return cwriter.bytes_written; + } + }; + + var shortest_encoding: ?struct { + index: usize, + len: usize, + } = null; + var i: usize = 0; + while (i < count) : (i += 1) { + const len = EncodingLength.estimate(candidates[i], .{ + .op1 = args.op1, + .op2 = args.op2, + .op3 = args.op3, + .op4 = args.op4, + }); + const current = shortest_encoding orelse { + shortest_encoding = .{ .index = i, .len = len }; + continue; + }; + if (len < current.len) { + shortest_encoding = .{ .index = i, .len = len }; + } + } + + return candidates[shortest_encoding.?.index]; +} + +/// Returns first matching encoding by opcode. +pub fn findByOpcode(opc: []const u8, prefixes: struct { + legacy: LegacyPrefixes, + rex: Rex, +}, modrm_ext: ?u3) ?Encoding { + inline for (table) |entry| { + const enc = Encoding{ + .mnemonic = entry[0], + .op_en = entry[1], + .op1 = entry[2], + .op2 = entry[3], + .op3 = entry[4], + .op4 = entry[5], + .opc_len = entry[6], + .opc = .{ entry[7], entry[8], entry[9] }, + .modrm_ext = entry[10], + .mode = entry[11], + }; + const match = match: { + if (modrm_ext) |ext| { + break :match ext == enc.modrm_ext and std.mem.eql(u8, enc.opcode(), opc); + } + break :match std.mem.eql(u8, enc.opcode(), opc); + }; + if (match) { + if (prefixes.rex.w) { + switch (enc.mode) { + .fpu, .sse, .sse2 => {}, + .long => return enc, + .none => { + // TODO this is a hack to allow parsing of instructions which contain + // spurious prefix bytes such as + // rex.W mov dil, 0x1 + // Here, rex.W is not needed. + const rex_w_allowed = blk: { + const bit_size = enc.operandSize(); + break :blk bit_size == 64 or bit_size == 8; + }; + if (rex_w_allowed) return enc; + }, + } + } else if (prefixes.legacy.prefix_66) { + switch (enc.operandSize()) { + 16 => return enc, + else => {}, + } + } else { + if (enc.mode == .none) { + switch (enc.operandSize()) { + 16 => {}, + else => return enc, + } + } + } + } + } + return null; +} + +pub fn opcode(encoding: *const Encoding) []const u8 { + return encoding.opc[0..encoding.opc_len]; +} + +pub fn mandatoryPrefix(encoding: *const Encoding) ?u8 { + const prefix = encoding.opc[0]; + return switch (prefix) { + 0x66, 0xf2, 0xf3 => prefix, + else => null, + }; +} + +pub fn modRmExt(encoding: Encoding) u3 { + return switch (encoding.op_en) { + .m, .mi, .m1, .mc => encoding.modrm_ext, + else => unreachable, + }; +} + +pub fn operandSize(encoding: Encoding) u32 { + if (encoding.mode == .long) return 64; + const bit_size: u32 = switch (encoding.op_en) { + .np => switch (encoding.op1) { + .o16 => 16, + .o32 => 32, + .o64 => 64, + else => 32, + }, + .td => encoding.op2.size(), + else => encoding.op1.size(), + }; + return bit_size; +} + +pub fn format( + encoding: Encoding, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = options; + _ = fmt; + switch (encoding.mode) { + .long => try writer.writeAll("REX.W + "), + else => {}, + } + + for (encoding.opcode()) |byte| { + try writer.print("{x:0>2} ", .{byte}); + } + + switch (encoding.op_en) { + .np, .fd, .td, .i, .zi, .d => {}, + .o, .oi => { + const tag = switch (encoding.op1) { + .r8 => "rb", + .r16 => "rw", + .r32 => "rd", + .r64 => "rd", + else => unreachable, + }; + try writer.print("+{s} ", .{tag}); + }, + .m, .mi, .m1, .mc => try writer.print("/{d} ", .{encoding.modRmExt()}), + .mr, .rm, .rmi => try writer.writeAll("/r "), + } + + switch (encoding.op_en) { + .i, .d, .zi, .oi, .mi, .rmi => { + const op = switch (encoding.op_en) { + .i, .d => encoding.op1, + .zi, .oi, .mi => encoding.op2, + .rmi => encoding.op3, + else => unreachable, + }; + const tag = switch (op) { + .imm8 => "ib", + .imm16 => "iw", + .imm32 => "id", + .imm64 => "io", + .rel8 => "cb", + .rel16 => "cw", + .rel32 => "cd", + else => unreachable, + }; + try writer.print("{s} ", .{tag}); + }, + .np, .fd, .td, .o, .m, .m1, .mc, .mr, .rm => {}, + } + + try writer.print("{s} ", .{@tagName(encoding.mnemonic)}); + + const ops = &[_]Op{ encoding.op1, encoding.op2, encoding.op3, encoding.op4 }; + for (ops) |op| switch (op) { + .none, .o16, .o32, .o64 => break, + else => try writer.print("{s} ", .{@tagName(op)}), + }; + + const op_en = switch (encoding.op_en) { + .zi => .i, + else => |op_en| op_en, + }; + try writer.print("{s}", .{@tagName(op_en)}); +} + +pub const Mnemonic = enum { + // zig fmt: off + // General-purpose + adc, add, @"and", + call, cbw, cwde, cdqe, cwd, cdq, cqo, cmp, + cmova, cmovae, cmovb, cmovbe, cmovc, cmove, cmovg, cmovge, cmovl, cmovle, cmovna, + cmovnae, cmovnb, cmovnbe, cmovnc, cmovne, cmovng, cmovnge, cmovnl, cmovnle, cmovno, + cmovnp, cmovns, cmovnz, cmovo, cmovp, cmovpe, cmovpo, cmovs, cmovz, + div, + fisttp, fld, + idiv, imul, int3, + ja, jae, jb, jbe, jc, jrcxz, je, jg, jge, jl, jle, jna, jnae, jnb, jnbe, + jnc, jne, jng, jnge, jnl, jnle, jno, jnp, jns, jnz, jo, jp, jpe, jpo, js, jz, + jmp, + lea, + mov, movsx, movsxd, movzx, mul, + nop, + @"or", + pop, push, + ret, + sal, sar, sbb, shl, shr, sub, syscall, + seta, setae, setb, setbe, setc, sete, setg, setge, setl, setle, setna, setnae, + setnb, setnbe, setnc, setne, setng, setnge, setnl, setnle, setno, setnp, setns, + setnz, seto, setp, setpe, setpo, sets, setz, + @"test", + ud2, + xor, + // SSE + addss, + cmpss, + movss, + ucomiss, + // SSE2 + addsd, + cmpsd, + movq, movsd, + ucomisd, + // zig fmt: on +}; + +pub const OpEn = enum { + // zig fmt: off + np, + o, oi, + i, zi, + d, m, + fd, td, + m1, mc, mi, mr, rm, rmi, + // zig fmt: on +}; + +pub const Op = enum { + // zig fmt: off + none, + o16, o32, o64, + unity, + imm8, imm16, imm32, imm64, + al, ax, eax, rax, + cl, + r8, r16, r32, r64, + rm8, rm16, rm32, rm64, + m8, m16, m32, m64, m80, + rel8, rel16, rel32, + m, + moffs, + sreg, + xmm, xmm_m32, xmm_m64, + // zig fmt: on + + pub fn fromOperand(operand: Instruction.Operand) Op { + switch (operand) { + .none => return .none, + + .reg => |reg| { + switch (reg.class()) { + .segment => return .sreg, + .floating_point => return switch (reg.size()) { + 128 => .xmm, + else => unreachable, + }, + .general_purpose => { + if (reg.to64() == .rax) return switch (reg) { + .al => .al, + .ax => .ax, + .eax => .eax, + .rax => .rax, + else => unreachable, + }; + if (reg == .cl) return .cl; + return switch (reg.size()) { + 8 => .r8, + 16 => .r16, + 32 => .r32, + 64 => .r64, + else => unreachable, + }; + }, + } + }, + + .mem => |mem| switch (mem) { + .moffs => return .moffs, + .sib, .rip => { + const bit_size = mem.size(); + return switch (bit_size) { + 8 => .m8, + 16 => .m16, + 32 => .m32, + 64 => .m64, + 80 => .m80, + else => unreachable, + }; + }, + }, + + .imm => |imm| { + if (imm == 1) return .unity; + if (math.cast(i8, imm)) |_| return .imm8; + if (math.cast(i16, imm)) |_| return .imm16; + if (math.cast(i32, imm)) |_| return .imm32; + return .imm64; + }, + } + } + + pub fn size(op: Op) u32 { + return switch (op) { + .none, .o16, .o32, .o64, .moffs, .m, .sreg, .unity => unreachable, + .imm8, .al, .cl, .r8, .m8, .rm8, .rel8 => 8, + .imm16, .ax, .r16, .m16, .rm16, .rel16 => 16, + .imm32, .eax, .r32, .m32, .rm32, .rel32, .xmm_m32 => 32, + .imm64, .rax, .r64, .m64, .rm64, .xmm_m64 => 64, + .m80 => 80, + .xmm => 128, + }; + } + + pub fn isRegister(op: Op) bool { + // zig fmt: off + return switch (op) { + .cl, + .al, .ax, .eax, .rax, + .r8, .r16, .r32, .r64, + .rm8, .rm16, .rm32, .rm64, + .xmm, .xmm_m32, .xmm_m64, + => true, + else => false, + }; + // zig fmt: on + } + + pub fn isImmediate(op: Op) bool { + // zig fmt: off + return switch (op) { + .imm8, .imm16, .imm32, .imm64, + .rel8, .rel16, .rel32, + .unity, + => true, + else => false, + }; + // zig fmt: on + } + + pub fn isMemory(op: Op) bool { + // zig fmt: off + return switch (op) { + .rm8, .rm16, .rm32, .rm64, + .m8, .m16, .m32, .m64, .m80, + .m, + .xmm_m32, .xmm_m64, + => true, + else => false, + }; + // zig fmt: on + } + + pub fn isSegmentRegister(op: Op) bool { + return switch (op) { + .moffs, .sreg => true, + else => false, + }; + } + + pub fn isFloatingPointRegister(op: Op) bool { + return switch (op) { + .xmm, .xmm_m32, .xmm_m64 => true, + else => false, + }; + } + + /// Given an operand `op` checks if `target` is a subset for the purposes + /// of the encoding. + pub fn isSubset(op: Op, target: Op, mode: Mode) bool { + switch (op) { + .m, .o16, .o32, .o64 => unreachable, + .moffs, .sreg => return op == target, + .none => switch (target) { + .o16, .o32, .o64, .none => return true, + else => return false, + }, + else => { + if (op.isRegister() and target.isRegister()) { + switch (mode) { + .sse, .sse2 => return op.isFloatingPointRegister() and target.isFloatingPointRegister(), + else => switch (target) { + .cl, .al, .ax, .eax, .rax => return op == target, + else => return op.size() == target.size(), + }, + } + } + if (op.isMemory() and target.isMemory()) { + switch (target) { + .m => return true, + else => return op.size() == target.size(), + } + } + if (op.isImmediate() and target.isImmediate()) { + switch (target) { + .imm32, .rel32 => switch (op) { + .unity, .imm8, .imm16, .imm32 => return true, + else => return op == target, + }, + .imm16, .rel16 => switch (op) { + .unity, .imm8, .imm16 => return true, + else => return op == target, + }, + .imm8, .rel8 => switch (op) { + .unity, .imm8 => return true, + else => return op == target, + }, + else => return op == target, + } + } + return false; + }, + } + } +}; + +pub const Mode = enum { + none, + fpu, + long, + sse, + sse2, +}; diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index ba71f4cddd..b3be08e86b 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -339,41 +339,23 @@ pub const Inst = struct { /// Nop nop, - /// SSE instructions + /// SSE/AVX instructions /// ops flags: form: /// 0b00 reg1, qword ptr [reg2 + imm32] /// 0b01 qword ptr [reg1 + imm32], reg2 /// 0b10 reg1, reg2 - mov_f64_sse, - mov_f32_sse, + mov_f64, + mov_f32, /// ops flags: form: /// 0b00 reg1, reg2 - add_f64_sse, - add_f32_sse, + add_f64, + add_f32, /// ops flags: form: /// 0b00 reg1, reg2 - cmp_f64_sse, - cmp_f32_sse, - - /// AVX instructions - /// ops flags: form: - /// 0b00 reg1, qword ptr [reg2 + imm32] - /// 0b01 qword ptr [reg1 + imm32], reg2 - /// 0b10 reg1, reg1, reg2 - mov_f64_avx, - mov_f32_avx, - - /// ops flags: form: - /// 0b00 reg1, reg1, reg2 - add_f64_avx, - add_f32_avx, - - /// ops flags: form: - /// 0b00 reg1, reg1, reg2 - cmp_f64_avx, - cmp_f32_avx, + cmp_f64, + cmp_f32, /// Pseudo-instructions /// call extern function @@ -439,6 +421,8 @@ pub const Inst = struct { inst: Index, /// A 32-bit immediate value. imm: u32, + /// A 32-bit signed displacement value. + disp: i32, /// A condition code for use with EFLAGS register. cc: bits.Condition, /// Another instruction with condition code. @@ -476,9 +460,9 @@ pub const IndexRegisterDisp = struct { index: u32, /// Displacement value - disp: u32, + disp: i32, - pub fn encode(index: Register, disp: u32) IndexRegisterDisp { + pub fn encode(index: Register, disp: i32) IndexRegisterDisp { return .{ .index = @enumToInt(index), .disp = disp, @@ -487,7 +471,7 @@ pub const IndexRegisterDisp = struct { pub fn decode(this: IndexRegisterDisp) struct { index: Register, - disp: u32, + disp: i32, } { return .{ .index = @intToEnum(Register, this.index), @@ -503,12 +487,12 @@ pub const IndexRegisterDispImm = struct { index: u32, /// Displacement value - disp: u32, + disp: i32, /// Immediate imm: u32, - pub fn encode(index: Register, disp: u32, imm: u32) IndexRegisterDispImm { + pub fn encode(index: Register, disp: i32, imm: u32) IndexRegisterDispImm { return .{ .index = @enumToInt(index), .disp = disp, @@ -518,7 +502,7 @@ pub const IndexRegisterDispImm = struct { pub fn decode(this: IndexRegisterDispImm) struct { index: Register, - disp: u32, + disp: i32, imm: u32, } { return .{ @@ -576,7 +560,7 @@ pub const SaveRegisterList = struct { }; pub const ImmPair = struct { - dest_off: u32, + dest_off: i32, operand: u32, }; diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index cc123b96b6..9166550f16 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -1,9 +1,9 @@ const std = @import("std"); -const testing = std.testing; -const mem = std.mem; const assert = std.debug.assert; -const ArrayList = std.ArrayList; +const expect = std.testing.expect; + const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayList; const DW = std.dwarf; /// EFLAGS condition codes @@ -135,960 +135,357 @@ pub const Condition = enum(u5) { } }; -/// Definitions of all of the general purpose x64 registers. The order is semantically meaningful. -/// The registers are defined such that IDs go in descending order of 64-bit, -/// 32-bit, 16-bit, and then 8-bit, and each set contains exactly sixteen -/// registers. This results in some useful properties: -/// -/// Any 64-bit register can be turned into its 32-bit form by adding 16, and -/// vice versa. This also works between 32-bit and 16-bit forms. With 8-bit, it -/// works for all except for sp, bp, si, and di, which do *not* have an 8-bit -/// form. -/// -/// If (register & 8) is set, the register is extended. -/// -/// The ID can be easily determined by figuring out what range the register is -/// in, and then subtracting the base. pub const Register = enum(u7) { // zig fmt: off - // 0 through 15, 64-bit registers. 8-15 are extended. - // id is just the int value. rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi, r8, r9, r10, r11, r12, r13, r14, r15, - // 16 through 31, 32-bit registers. 24-31 are extended. - // id is int value - 16. eax, ecx, edx, ebx, esp, ebp, esi, edi, r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d, - // 32-47, 16-bit registers. 40-47 are extended. - // id is int value - 32. ax, cx, dx, bx, sp, bp, si, di, r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w, - // 48-63, 8-bit registers. 56-63 are extended. - // id is int value - 48. - al, cl, dl, bl, ah, ch, dh, bh, + al, cl, dl, bl, spl, bpl, sil, dil, r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b, - // 64-79, 256-bit registers. - // id is int value - 64. + ah, ch, dh, bh, + ymm0, ymm1, ymm2, ymm3, ymm4, ymm5, ymm6, ymm7, ymm8, ymm9, ymm10, ymm11, ymm12, ymm13, ymm14, ymm15, - // 80-95, 128-bit registers. - // id is int value - 80. xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15, - // Pseudo-value for MIR instructions. + es, cs, ss, ds, fs, gs, + none, // zig fmt: on - pub fn id(self: Register) u7 { - return switch (@enumToInt(self)) { - 0...63 => @as(u7, @truncate(u4, @enumToInt(self))), - 64...79 => @enumToInt(self), + pub const Class = enum(u2) { + general_purpose, + floating_point, + segment, + }; + + pub fn class(reg: Register) Class { + return switch (@enumToInt(reg)) { + // zig fmt: off + @enumToInt(Register.rax) ... @enumToInt(Register.r15) => .general_purpose, + @enumToInt(Register.eax) ... @enumToInt(Register.r15d) => .general_purpose, + @enumToInt(Register.ax) ... @enumToInt(Register.r15w) => .general_purpose, + @enumToInt(Register.al) ... @enumToInt(Register.r15b) => .general_purpose, + @enumToInt(Register.ah) ... @enumToInt(Register.bh) => .general_purpose, + + @enumToInt(Register.ymm0) ... @enumToInt(Register.ymm15) => .floating_point, + @enumToInt(Register.xmm0) ... @enumToInt(Register.xmm15) => .floating_point, + + @enumToInt(Register.es) ... @enumToInt(Register.gs) => .segment, + + else => unreachable, + // zig fmt: on + }; + } + + pub fn id(reg: Register) u6 { + const base = switch (@enumToInt(reg)) { + // zig fmt: off + @enumToInt(Register.rax) ... @enumToInt(Register.r15) => @enumToInt(Register.rax), + @enumToInt(Register.eax) ... @enumToInt(Register.r15d) => @enumToInt(Register.eax), + @enumToInt(Register.ax) ... @enumToInt(Register.r15w) => @enumToInt(Register.ax), + @enumToInt(Register.al) ... @enumToInt(Register.r15b) => @enumToInt(Register.al), + @enumToInt(Register.ah) ... @enumToInt(Register.bh) => @enumToInt(Register.ah) - 4, + + @enumToInt(Register.ymm0) ... @enumToInt(Register.ymm15) => @enumToInt(Register.ymm0) - 16, + @enumToInt(Register.xmm0) ... @enumToInt(Register.xmm15) => @enumToInt(Register.xmm0) - 16, + + @enumToInt(Register.es) ... @enumToInt(Register.gs) => @enumToInt(Register.es) - 32, + + else => unreachable, + // zig fmt: on + }; + return @intCast(u6, @enumToInt(reg) - base); + } + + pub fn size(reg: Register) u32 { + return switch (@enumToInt(reg)) { + // zig fmt: off + @enumToInt(Register.rax) ... @enumToInt(Register.r15) => 64, + @enumToInt(Register.eax) ... @enumToInt(Register.r15d) => 32, + @enumToInt(Register.ax) ... @enumToInt(Register.r15w) => 16, + @enumToInt(Register.al) ... @enumToInt(Register.r15b) => 8, + @enumToInt(Register.ah) ... @enumToInt(Register.bh) => 8, + + @enumToInt(Register.ymm0) ... @enumToInt(Register.ymm15) => 256, + @enumToInt(Register.xmm0) ... @enumToInt(Register.xmm15) => 128, + + @enumToInt(Register.es) ... @enumToInt(Register.gs) => 16, + + else => unreachable, + // zig fmt: on + }; + } + + pub fn isExtended(reg: Register) bool { + return switch (@enumToInt(reg)) { + // zig fmt: off + @enumToInt(Register.r8) ... @enumToInt(Register.r15) => true, + @enumToInt(Register.r8d) ... @enumToInt(Register.r15d) => true, + @enumToInt(Register.r8w) ... @enumToInt(Register.r15w) => true, + @enumToInt(Register.r8b) ... @enumToInt(Register.r15b) => true, + + @enumToInt(Register.ymm8) ... @enumToInt(Register.ymm15) => true, + @enumToInt(Register.xmm8) ... @enumToInt(Register.xmm15) => true, + + else => false, + // zig fmt: on + }; + } + + pub fn isRexInvalid(reg: Register) bool { + return switch (@enumToInt(reg)) { + @enumToInt(Register.ah)...@enumToInt(Register.bh) => true, + else => false, + }; + } + + pub fn enc(reg: Register) u4 { + const base = switch (@enumToInt(reg)) { + // zig fmt: off + @enumToInt(Register.rax) ... @enumToInt(Register.r15) => @enumToInt(Register.rax), + @enumToInt(Register.eax) ... @enumToInt(Register.r15d) => @enumToInt(Register.eax), + @enumToInt(Register.ax) ... @enumToInt(Register.r15w) => @enumToInt(Register.ax), + @enumToInt(Register.al) ... @enumToInt(Register.r15b) => @enumToInt(Register.al), + @enumToInt(Register.ah) ... @enumToInt(Register.bh) => @enumToInt(Register.ah) - 4, + + @enumToInt(Register.ymm0) ... @enumToInt(Register.ymm15) => @enumToInt(Register.ymm0), + @enumToInt(Register.xmm0) ... @enumToInt(Register.xmm15) => @enumToInt(Register.xmm0), + + @enumToInt(Register.es) ... @enumToInt(Register.gs) => @enumToInt(Register.es), + + else => unreachable, + // zig fmt: on + }; + return @truncate(u4, @enumToInt(reg) - base); + } + + pub fn lowEnc(reg: Register) u3 { + return @truncate(u3, reg.enc()); + } + + pub fn toSize(reg: Register, bit_size: u32) Register { + return switch (bit_size) { + 8 => reg.to8(), + 16 => reg.to16(), + 32 => reg.to32(), + 64 => reg.to64(), + 128 => reg.to128(), + 256 => reg.to256(), else => unreachable, }; } - /// Returns the bit-width of the register. - pub fn size(self: Register) u9 { - return switch (@enumToInt(self)) { - 0...15 => 64, - 16...31 => 32, - 32...47 => 16, - 48...63 => 8, - 64...79 => 256, - 80...95 => 128, + fn gpBase(reg: Register) u7 { + assert(reg.class() == .general_purpose); + return switch (@enumToInt(reg)) { + // zig fmt: off + @enumToInt(Register.rax) ... @enumToInt(Register.r15) => @enumToInt(Register.rax), + @enumToInt(Register.eax) ... @enumToInt(Register.r15d) => @enumToInt(Register.eax), + @enumToInt(Register.ax) ... @enumToInt(Register.r15w) => @enumToInt(Register.ax), + @enumToInt(Register.al) ... @enumToInt(Register.r15b) => @enumToInt(Register.al), + @enumToInt(Register.ah) ... @enumToInt(Register.bh) => @enumToInt(Register.ah) - 4, + else => unreachable, + // zig fmt: on + }; + } + + pub fn to64(reg: Register) Register { + return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.rax)); + } + + pub fn to32(reg: Register) Register { + return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.eax)); + } + + pub fn to16(reg: Register) Register { + return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.ax)); + } + + pub fn to8(reg: Register) Register { + return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.al)); + } + + fn fpBase(reg: Register) u7 { + assert(reg.class() == .floating_point); + return switch (@enumToInt(reg)) { + @enumToInt(Register.ymm0)...@enumToInt(Register.ymm15) => @enumToInt(Register.ymm0), + @enumToInt(Register.xmm0)...@enumToInt(Register.xmm15) => @enumToInt(Register.xmm0), else => unreachable, }; } - /// Returns whether the register is *extended*. Extended registers are the - /// new registers added with amd64, r8 through r15. This also includes any - /// other variant of access to those registers, such as r8b, r15d, and so - /// on. This is needed because access to these registers requires special - /// handling via the REX prefix, via the B or R bits, depending on context. - pub fn isExtended(self: Register) bool { - return @enumToInt(self) & 0x08 != 0; + pub fn to256(reg: Register) Register { + return @intToEnum(Register, @enumToInt(reg) - reg.fpBase() + @enumToInt(Register.ymm0)); } - /// This returns the 4-bit register ID, which is used in practically every - /// opcode. Note that bit 3 (the highest bit) is *never* used directly in - /// an instruction (@see isExtended), and requires special handling. The - /// lower three bits are often embedded directly in instructions (such as - /// the B8 variant of moves), or used in R/M bytes. - pub fn enc(self: Register) u4 { - return @truncate(u4, @enumToInt(self)); + pub fn to128(reg: Register) Register { + return @intToEnum(Register, @enumToInt(reg) - reg.fpBase() + @enumToInt(Register.xmm0)); } - /// Like enc, but only returns the lower 3 bits. - pub fn lowEnc(self: Register) u3 { - return @truncate(u3, @enumToInt(self)); - } - - pub fn to256(self: Register) Register { - return @intToEnum(Register, @as(u8, self.enc()) + 64); - } - - pub fn to128(self: Register) Register { - return @intToEnum(Register, @as(u8, self.enc()) + 80); - } - - /// Convert from any register to its 64 bit alias. - pub fn to64(self: Register) Register { - return @intToEnum(Register, self.enc()); - } - - /// Convert from any register to its 32 bit alias. - pub fn to32(self: Register) Register { - return @intToEnum(Register, @as(u8, self.enc()) + 16); - } - - /// Convert from any register to its 16 bit alias. - pub fn to16(self: Register) Register { - return @intToEnum(Register, @as(u8, self.enc()) + 32); - } - - /// Convert from any register to its 8 bit alias. - pub fn to8(self: Register) Register { - return @intToEnum(Register, @as(u8, self.enc()) + 48); - } - - pub fn dwarfLocOp(self: Register) u8 { - switch (@enumToInt(self)) { - 0...63 => return switch (self.to64()) { - .rax => DW.OP.reg0, - .rdx => DW.OP.reg1, - .rcx => DW.OP.reg2, - .rbx => DW.OP.reg3, - .rsi => DW.OP.reg4, - .rdi => DW.OP.reg5, - .rbp => DW.OP.reg6, - .rsp => DW.OP.reg7, - - .r8 => DW.OP.reg8, - .r9 => DW.OP.reg9, - .r10 => DW.OP.reg10, - .r11 => DW.OP.reg11, - .r12 => DW.OP.reg12, - .r13 => DW.OP.reg13, - .r14 => DW.OP.reg14, - .r15 => DW.OP.reg15, - - else => unreachable, - }, - - 64...79 => return @as(u8, self.enc()) + DW.OP.reg17, - + pub fn dwarfLocOp(reg: Register) u8 { + return switch (reg.class()) { + .general_purpose => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.reg0, + .floating_point => @intCast(u8, @enumToInt(reg) - reg.fpBase()) + DW.OP.reg17, else => unreachable, - } + }; } /// DWARF encodings that push a value onto the DWARF stack that is either /// the contents of a register or the result of adding the contents a given /// register to a given signed offset. - pub fn dwarfLocOpDeref(self: Register) u8 { - switch (@enumToInt(self)) { - 0...63 => return switch (self.to64()) { - .rax => DW.OP.breg0, - .rdx => DW.OP.breg1, - .rcx => DW.OP.breg2, - .rbx => DW.OP.breg3, - .rsi => DW.OP.breg4, - .rdi => DW.OP.breg5, - .rbp => DW.OP.breg6, - .rsp => DW.OP.fbreg, - - .r8 => DW.OP.breg8, - .r9 => DW.OP.breg9, - .r10 => DW.OP.breg10, - .r11 => DW.OP.breg11, - .r12 => DW.OP.breg12, - .r13 => DW.OP.breg13, - .r14 => DW.OP.breg14, - .r15 => DW.OP.breg15, - - else => unreachable, - }, - - 64...79 => return @as(u8, self.enc()) + DW.OP.breg17, - + pub fn dwarfLocOpDeref(reg: Register) u8 { + return switch (reg.class()) { + .general_purpose => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.breg0, + .floating_point => @intCast(u8, @enumToInt(reg) - reg.fpBase()) + DW.OP.breg17, else => unreachable, - } + }; } }; -// zig fmt: on +test "Register id - different classes" { + try expect(Register.al.id() == Register.ax.id()); + try expect(Register.ah.id() == Register.spl.id()); + try expect(Register.ax.id() == Register.eax.id()); + try expect(Register.eax.id() == Register.rax.id()); -/// Encoding helper functions for x86_64 instructions -/// -/// Many of these helpers do very little, but they can help make things -/// slightly more readable with more descriptive field names / function names. -/// -/// Some of them also have asserts to ensure that we aren't doing dumb things. -/// For example, trying to use register 4 (esp) in an indirect modr/m byte is illegal, -/// you need to encode it with an SIB byte. -/// -/// Note that ALL of these helper functions will assume capacity, -/// so ensure that the `code` has sufficient capacity before using them. -/// The `init` method is the recommended way to ensure capacity. -pub const Encoder = struct { - /// Non-owning reference to the code array - code: *ArrayList(u8), + try expect(Register.ymm0.id() == 0b10000); + try expect(Register.ymm0.id() != Register.rax.id()); + try expect(Register.xmm0.id() == Register.ymm0.id()); - const Self = @This(); + try expect(Register.es.id() == 0b100000); +} - /// Wrap `code` in Encoder to make it easier to call these helper functions - /// - /// maximum_inst_size should contain the maximum number of bytes - /// that the encoded instruction will take. - /// This is because the helper functions will assume capacity - /// in order to avoid bounds checking. - pub fn init(code: *ArrayList(u8), maximum_inst_size: u8) !Self { - try code.ensureUnusedCapacity(maximum_inst_size); - return Self{ .code = code }; - } +test "Register enc - different classes" { + try expect(Register.al.enc() == Register.ax.enc()); + try expect(Register.ax.enc() == Register.eax.enc()); + try expect(Register.eax.enc() == Register.rax.enc()); + try expect(Register.ymm0.enc() == Register.rax.enc()); + try expect(Register.xmm0.enc() == Register.ymm0.enc()); + try expect(Register.es.enc() == Register.rax.enc()); +} - /// Directly write a number to the code array with big endianness - pub fn writeIntBig(self: Self, comptime T: type, value: T) void { - mem.writeIntBig( - T, - self.code.addManyAsArrayAssumeCapacity(@divExact(@typeInfo(T).Int.bits, 8)), - value, - ); - } +test "Register classes" { + try expect(Register.r11.class() == .general_purpose); + try expect(Register.ymm11.class() == .floating_point); + try expect(Register.fs.class() == .segment); +} - /// Directly write a number to the code array with little endianness - pub fn writeIntLittle(self: Self, comptime T: type, value: T) void { - mem.writeIntLittle( - T, - self.code.addManyAsArrayAssumeCapacity(@divExact(@typeInfo(T).Int.bits, 8)), - value, - ); - } +pub const Memory = union(enum) { + sib: Sib, + rip: Rip, + moffs: Moffs, - // -------- - // Prefixes - // -------- - - pub const LegacyPrefixes = packed struct { - /// LOCK - prefix_f0: bool = false, - /// REPNZ, REPNE, REP, Scalar Double-precision - prefix_f2: bool = false, - /// REPZ, REPE, REP, Scalar Single-precision - prefix_f3: bool = false, - - /// CS segment override or Branch not taken - prefix_2e: bool = false, - /// DS segment override - prefix_36: bool = false, - /// ES segment override - prefix_26: bool = false, - /// FS segment override - prefix_64: bool = false, - /// GS segment override - prefix_65: bool = false, - - /// Branch taken - prefix_3e: bool = false, - - /// Operand size override (enables 16 bit operation) - prefix_66: bool = false, - - /// Address size override (enables 16 bit address size) - prefix_67: bool = false, - - padding: u5 = 0, + pub const ScaleIndex = packed struct { + scale: u4, + index: Register, }; - /// Encodes legacy prefixes - pub fn legacyPrefixes(self: Self, prefixes: LegacyPrefixes) void { - if (@bitCast(u16, prefixes) != 0) { - // Hopefully this path isn't taken very often, so we'll do it the slow way for now + pub const PtrSize = enum { + byte, + word, + dword, + qword, + tbyte, - // LOCK - if (prefixes.prefix_f0) self.code.appendAssumeCapacity(0xf0); - // REPNZ, REPNE, REP, Scalar Double-precision - if (prefixes.prefix_f2) self.code.appendAssumeCapacity(0xf2); - // REPZ, REPE, REP, Scalar Single-precision - if (prefixes.prefix_f3) self.code.appendAssumeCapacity(0xf3); - - // CS segment override or Branch not taken - if (prefixes.prefix_2e) self.code.appendAssumeCapacity(0x2e); - // DS segment override - if (prefixes.prefix_36) self.code.appendAssumeCapacity(0x36); - // ES segment override - if (prefixes.prefix_26) self.code.appendAssumeCapacity(0x26); - // FS segment override - if (prefixes.prefix_64) self.code.appendAssumeCapacity(0x64); - // GS segment override - if (prefixes.prefix_65) self.code.appendAssumeCapacity(0x65); - - // Branch taken - if (prefixes.prefix_3e) self.code.appendAssumeCapacity(0x3e); - - // Operand size override - if (prefixes.prefix_66) self.code.appendAssumeCapacity(0x66); - - // Address size override - if (prefixes.prefix_67) self.code.appendAssumeCapacity(0x67); - } - } - - /// Use 16 bit operand size - /// - /// Note that this flag is overridden by REX.W, if both are present. - pub fn prefix16BitMode(self: Self) void { - self.code.appendAssumeCapacity(0x66); - } - - pub const Vex = struct { - rex_prefix: Rex = .{}, - lead_opc: u5 = 0b0_0001, - register: u4 = 0b1111, - length: u1 = 0b0, - simd_prefix: u2 = 0b00, - wig_desc: bool = false, - lig_desc: bool = false, - lz_desc: bool = false, - - pub fn rex(self: *Vex, r: Rex) void { - self.rex_prefix = r; - } - - pub fn lead_opc_0f(self: *Vex) void { - self.lead_opc = 0b0_0001; - } - - pub fn lead_opc_0f_38(self: *Vex) void { - self.lead_opc = 0b0_0010; - } - - pub fn lead_opc_0f_3a(self: *Vex) void { - self.lead_opc = 0b0_0011; - } - - pub fn reg(self: *Vex, register: u4) void { - self.register = ~register; - } - - pub fn len_128(self: *Vex) void { - self.length = 0; - } - - pub fn len_256(self: *Vex) void { - assert(!self.lz_desc); - self.length = 1; - } - - pub fn simd_prefix_66(self: *Vex) void { - self.simd_prefix = 0b01; - } - - pub fn simd_prefix_f3(self: *Vex) void { - self.simd_prefix = 0b10; - } - - pub fn simd_prefix_f2(self: *Vex) void { - self.simd_prefix = 0b11; - } - - pub fn wig(self: *Vex) void { - self.wig_desc = true; - } - - pub fn lig(self: *Vex) void { - self.lig_desc = true; - } - - pub fn lz(self: *Vex) void { - self.lz_desc = true; - } - - pub fn write(self: Vex, writer: anytype) usize { - var buf: [3]u8 = .{0} ** 3; - const form_3byte: bool = blk: { - if (self.rex_prefix.w and !self.wig_desc) break :blk true; - if (self.rex_prefix.x or self.rex_prefix.b) break :blk true; - break :blk self.lead_opc != 0b0_0001; + pub fn fromSize(bit_size: u32) PtrSize { + return switch (bit_size) { + 8 => .byte, + 16 => .word, + 32 => .dword, + 64 => .qword, + 80 => .tbyte, + else => unreachable, }; + } - if (self.lz_desc) { - assert(self.length == 0); - } - - if (form_3byte) { - // First byte - buf[0] = 0xc4; - // Second byte - const rxb_mask: u3 = @intCast(u3, @boolToInt(!self.rex_prefix.r)) << 2 | - @intCast(u2, @boolToInt(!self.rex_prefix.x)) << 1 | - @boolToInt(!self.rex_prefix.b); - buf[1] |= @intCast(u8, rxb_mask) << 5; - buf[1] |= self.lead_opc; - // Third byte - buf[2] |= @intCast(u8, @boolToInt(!self.rex_prefix.w)) << 7; - buf[2] |= @intCast(u7, self.register) << 3; - buf[2] |= @intCast(u3, self.length) << 2; - buf[2] |= self.simd_prefix; - } else { - // First byte - buf[0] = 0xc5; - // Second byte - buf[1] |= @intCast(u8, @boolToInt(!self.rex_prefix.r)) << 7; - buf[1] |= @intCast(u7, self.register) << 3; - buf[1] |= @intCast(u3, self.length) << 2; - buf[1] |= self.simd_prefix; - } - - const count: usize = if (form_3byte) 3 else 2; - _ = writer.writeAll(buf[0..count]) catch unreachable; - return count; + pub fn size(s: PtrSize) u32 { + return switch (s) { + .byte => 8, + .word => 16, + .dword => 32, + .qword => 64, + .tbyte => 80, + }; } }; - pub fn vex(self: Self, prefix: Vex) void { - _ = prefix.write(self.code.writer()); - } - - /// From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB - pub const Rex = struct { - /// Wide, enables 64-bit operation - w: bool = false, - /// Extends the reg field in the ModR/M byte - r: bool = false, - /// Extends the index field in the SIB byte - x: bool = false, - /// Extends the r/m field in the ModR/M byte, - /// or the base field in the SIB byte, - /// or the reg field in the Opcode byte - b: bool = false, + pub const Sib = struct { + ptr_size: PtrSize, + base: ?Register, + scale_index: ?ScaleIndex, + disp: i32, }; - /// Encodes a REX prefix byte given all the fields - /// - /// Use this byte whenever you need 64 bit operation, - /// or one of reg, index, r/m, base, or opcode-reg might be extended. - /// - /// See struct `Rex` for a description of each field. - /// - /// Does not add a prefix byte if none of the fields are set! - pub fn rex(self: Self, byte: Rex) void { - var value: u8 = 0b0100_0000; + pub const Rip = struct { + ptr_size: PtrSize, + disp: i32, + }; - if (byte.w) value |= 0b1000; - if (byte.r) value |= 0b0100; - if (byte.x) value |= 0b0010; - if (byte.b) value |= 0b0001; + pub const Moffs = struct { + seg: Register, + offset: u64, + }; - if (value != 0b0100_0000) { - self.code.appendAssumeCapacity(value); - } + pub fn moffs(reg: Register, offset: u64) Memory { + assert(reg.class() == .segment); + return .{ .moffs = .{ .seg = reg, .offset = offset } }; } - // ------ - // Opcode - // ------ - - /// Encodes a 1 byte opcode - pub fn opcode_1byte(self: Self, opcode: u8) void { - self.code.appendAssumeCapacity(opcode); + pub fn sib(ptr_size: PtrSize, args: struct { + disp: i32, + base: ?Register = null, + scale_index: ?ScaleIndex = null, + }) Memory { + return .{ .sib = .{ + .base = args.base, + .disp = args.disp, + .ptr_size = ptr_size, + .scale_index = args.scale_index, + } }; } - /// Encodes a 2 byte opcode - /// - /// e.g. IMUL has the opcode 0x0f 0xaf, so you use - /// - /// encoder.opcode_2byte(0x0f, 0xaf); - pub fn opcode_2byte(self: Self, prefix: u8, opcode: u8) void { - self.code.appendAssumeCapacity(prefix); - self.code.appendAssumeCapacity(opcode); + pub fn rip(ptr_size: PtrSize, disp: i32) Memory { + return .{ .rip = .{ .ptr_size = ptr_size, .disp = disp } }; } - /// Encodes a 3 byte opcode - /// - /// e.g. MOVSD has the opcode 0xf2 0x0f 0x10 - /// - /// encoder.opcode_3byte(0xf2, 0x0f, 0x10); - pub fn opcode_3byte(self: Self, prefix_1: u8, prefix_2: u8, opcode: u8) void { - self.code.appendAssumeCapacity(prefix_1); - self.code.appendAssumeCapacity(prefix_2); - self.code.appendAssumeCapacity(opcode); + pub fn isSegmentRegister(mem: Memory) bool { + return switch (mem) { + .moffs => true, + .rip => false, + .sib => |s| if (s.base) |r| r.class() == .segment else false, + }; } - /// Encodes a 1 byte opcode with a reg field - /// - /// Remember to add a REX prefix byte if reg is extended! - pub fn opcode_withReg(self: Self, opcode: u8, reg: u3) void { - assert(opcode & 0b111 == 0); - self.code.appendAssumeCapacity(opcode | reg); + pub fn base(mem: Memory) ?Register { + return switch (mem) { + .moffs => |m| m.seg, + .sib => |s| s.base, + .rip => null, + }; } - // ------ - // ModR/M - // ------ - - /// Construct a ModR/M byte given all the fields - /// - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm(self: Self, mod: u2, reg_or_opx: u3, rm: u3) void { - self.code.appendAssumeCapacity( - @as(u8, mod) << 6 | @as(u8, reg_or_opx) << 3 | rm, - ); + pub fn scaleIndex(mem: Memory) ?ScaleIndex { + return switch (mem) { + .moffs, .rip => null, + .sib => |s| s.scale_index, + }; } - /// Construct a ModR/M byte using direct r/m addressing - /// r/m effective address: r/m - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_direct(self: Self, reg_or_opx: u3, rm: u3) void { - self.modRm(0b11, reg_or_opx, rm); - } - - /// Construct a ModR/M byte using indirect r/m addressing - /// r/m effective address: [r/m] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_indirectDisp0(self: Self, reg_or_opx: u3, rm: u3) void { - assert(rm != 4 and rm != 5); - self.modRm(0b00, reg_or_opx, rm); - } - - /// Construct a ModR/M byte using indirect SIB addressing - /// r/m effective address: [SIB] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_SIBDisp0(self: Self, reg_or_opx: u3) void { - self.modRm(0b00, reg_or_opx, 0b100); - } - - /// Construct a ModR/M byte using RIP-relative addressing - /// r/m effective address: [RIP + disp32] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_RIPDisp32(self: Self, reg_or_opx: u3) void { - self.modRm(0b00, reg_or_opx, 0b101); - } - - /// Construct a ModR/M byte using indirect r/m with a 8bit displacement - /// r/m effective address: [r/m + disp8] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_indirectDisp8(self: Self, reg_or_opx: u3, rm: u3) void { - assert(rm != 4); - self.modRm(0b01, reg_or_opx, rm); - } - - /// Construct a ModR/M byte using indirect SIB with a 8bit displacement - /// r/m effective address: [SIB + disp8] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_SIBDisp8(self: Self, reg_or_opx: u3) void { - self.modRm(0b01, reg_or_opx, 0b100); - } - - /// Construct a ModR/M byte using indirect r/m with a 32bit displacement - /// r/m effective address: [r/m + disp32] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_indirectDisp32(self: Self, reg_or_opx: u3, rm: u3) void { - assert(rm != 4); - self.modRm(0b10, reg_or_opx, rm); - } - - /// Construct a ModR/M byte using indirect SIB with a 32bit displacement - /// r/m effective address: [SIB + disp32] - /// - /// Note reg's effective address is always just reg for the ModR/M byte. - /// Remember to add a REX prefix byte if reg or rm are extended! - pub fn modRm_SIBDisp32(self: Self, reg_or_opx: u3) void { - self.modRm(0b10, reg_or_opx, 0b100); - } - - // --- - // SIB - // --- - - /// Construct a SIB byte given all the fields - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib(self: Self, scale: u2, index: u3, base: u3) void { - self.code.appendAssumeCapacity( - @as(u8, scale) << 6 | @as(u8, index) << 3 | base, - ); - } - - /// Construct a SIB byte with scale * index + base, no frills. - /// r/m effective address: [base + scale * index] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_scaleIndexBase(self: Self, scale: u2, index: u3, base: u3) void { - assert(base != 5); - - self.sib(scale, index, base); - } - - /// Construct a SIB byte with scale * index + disp32 - /// r/m effective address: [scale * index + disp32] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_scaleIndexDisp32(self: Self, scale: u2, index: u3) void { - assert(index != 4); - - // scale is actually ignored - // index = 4 means no index - // base = 5 means no base, if mod == 0. - self.sib(scale, index, 5); - } - - /// Construct a SIB byte with just base - /// r/m effective address: [base] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_base(self: Self, base: u3) void { - assert(base != 5); - - // scale is actually ignored - // index = 4 means no index - self.sib(0, 4, base); - } - - /// Construct a SIB byte with just disp32 - /// r/m effective address: [disp32] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_disp32(self: Self) void { - // scale is actually ignored - // index = 4 means no index - // base = 5 means no base, if mod == 0. - self.sib(0, 4, 5); - } - - /// Construct a SIB byte with scale * index + base + disp8 - /// r/m effective address: [base + scale * index + disp8] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_scaleIndexBaseDisp8(self: Self, scale: u2, index: u3, base: u3) void { - self.sib(scale, index, base); - } - - /// Construct a SIB byte with base + disp8, no index - /// r/m effective address: [base + disp8] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_baseDisp8(self: Self, base: u3) void { - // scale is ignored - // index = 4 means no index - self.sib(0, 4, base); - } - - /// Construct a SIB byte with scale * index + base + disp32 - /// r/m effective address: [base + scale * index + disp32] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_scaleIndexBaseDisp32(self: Self, scale: u2, index: u3, base: u3) void { - self.sib(scale, index, base); - } - - /// Construct a SIB byte with base + disp32, no index - /// r/m effective address: [base + disp32] - /// - /// Remember to add a REX prefix byte if index or base are extended! - pub fn sib_baseDisp32(self: Self, base: u3) void { - // scale is ignored - // index = 4 means no index - self.sib(0, 4, base); - } - - // ------------------------- - // Trivial (no bit fiddling) - // ------------------------- - - /// Encode an 8 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm8(self: Self, imm: i8) void { - self.code.appendAssumeCapacity(@bitCast(u8, imm)); - } - - /// Encode an 8 bit displacement - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn disp8(self: Self, disp: i8) void { - self.code.appendAssumeCapacity(@bitCast(u8, disp)); - } - - /// Encode an 16 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm16(self: Self, imm: i16) void { - self.writeIntLittle(i16, imm); - } - - /// Encode an 32 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm32(self: Self, imm: i32) void { - self.writeIntLittle(i32, imm); - } - - /// Encode an 32 bit displacement - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn disp32(self: Self, disp: i32) void { - self.writeIntLittle(i32, disp); - } - - /// Encode an 64 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm64(self: Self, imm: u64) void { - self.writeIntLittle(u64, imm); + pub fn size(mem: Memory) u32 { + return switch (mem) { + .rip => |r| r.ptr_size.size(), + .sib => |s| s.ptr_size.size(), + .moffs => unreachable, + }; } }; - -test "Encoder helpers - general purpose registers" { - var code = ArrayList(u8).init(testing.allocator); - defer code.deinit(); - - // simple integer multiplication - - // imul eax,edi - // 0faf c7 - { - try code.resize(0); - const encoder = try Encoder.init(&code, 4); - encoder.rex(.{ - .r = Register.eax.isExtended(), - .b = Register.edi.isExtended(), - }); - encoder.opcode_2byte(0x0f, 0xaf); - encoder.modRm_direct( - Register.eax.lowEnc(), - Register.edi.lowEnc(), - ); - - try testing.expectEqualSlices(u8, &[_]u8{ 0x0f, 0xaf, 0xc7 }, code.items); - } - - // simple mov - - // mov eax,edi - // 89 f8 - { - try code.resize(0); - const encoder = try Encoder.init(&code, 3); - encoder.rex(.{ - .r = Register.edi.isExtended(), - .b = Register.eax.isExtended(), - }); - encoder.opcode_1byte(0x89); - encoder.modRm_direct( - Register.edi.lowEnc(), - Register.eax.lowEnc(), - ); - - try testing.expectEqualSlices(u8, &[_]u8{ 0x89, 0xf8 }, code.items); - } - - // signed integer addition of 32-bit sign extended immediate to 64 bit register - - // add rcx, 2147483647 - // - // Using the following opcode: REX.W + 81 /0 id, we expect the following encoding - // - // 48 : REX.W set for 64 bit operand (*r*cx) - // 81 : opcode for " with immediate" - // c1 : id = rcx, - // : c1 = 11 <-- mod = 11 indicates r/m is register (rcx) - // : 000 <-- opcode_extension = 0 because opcode extension is /0. /0 specifies ADD - // : 001 <-- 001 is rcx - // ffffff7f : 2147483647 - { - try code.resize(0); - const encoder = try Encoder.init(&code, 7); - encoder.rex(.{ .w = true }); // use 64 bit operation - encoder.opcode_1byte(0x81); - encoder.modRm_direct( - 0, - Register.rcx.lowEnc(), - ); - encoder.imm32(2147483647); - - try testing.expectEqualSlices(u8, &[_]u8{ 0x48, 0x81, 0xc1, 0xff, 0xff, 0xff, 0x7f }, code.items); - } -} - -test "Encoder helpers - Vex prefix" { - var buf: [3]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); - - { - var vex_prefix = Encoder.Vex{}; - vex_prefix.rex(.{ - .r = true, - }); - const nwritten = vex_prefix.write(writer); - try testing.expectEqualSlices(u8, &[_]u8{ 0xc5, 0x78 }, buf[0..nwritten]); - } - - { - stream.reset(); - var vex_prefix = Encoder.Vex{}; - vex_prefix.reg(Register.xmm15.enc()); - const nwritten = vex_prefix.write(writer); - try testing.expectEqualSlices(u8, &[_]u8{ 0xc5, 0x80 }, buf[0..nwritten]); - } - - { - stream.reset(); - var vex_prefix = Encoder.Vex{}; - vex_prefix.rex(.{ - .w = true, - .x = true, - }); - const nwritten = vex_prefix.write(writer); - try testing.expectEqualSlices(u8, &[_]u8{ 0xc4, 0b101_0_0001, 0b0_1111_0_00 }, buf[0..nwritten]); - } - - { - stream.reset(); - var vex_prefix = Encoder.Vex{}; - vex_prefix.rex(.{ - .w = true, - .r = true, - }); - vex_prefix.len_256(); - vex_prefix.lead_opc_0f(); - vex_prefix.simd_prefix_66(); - const nwritten = vex_prefix.write(writer); - try testing.expectEqualSlices(u8, &[_]u8{ 0xc4, 0b011_0_0001, 0b0_1111_1_01 }, buf[0..nwritten]); - } - - var code = ArrayList(u8).init(testing.allocator); - defer code.deinit(); - - { - // vmovapd xmm1, xmm2 - const encoder = try Encoder.init(&code, 4); - var vex = Encoder.Vex{}; - vex.simd_prefix_66(); - encoder.vex(vex); // use 64 bit operation - encoder.opcode_1byte(0x28); - encoder.modRm_direct(0, Register.xmm1.lowEnc()); - try testing.expectEqualSlices(u8, &[_]u8{ 0xC5, 0xF9, 0x28, 0xC1 }, code.items); - } - - { - try code.resize(0); - - // vmovhpd xmm13, xmm1, qword ptr [rip] - const encoder = try Encoder.init(&code, 9); - var vex = Encoder.Vex{}; - vex.len_128(); - vex.simd_prefix_66(); - vex.lead_opc_0f(); - vex.rex(.{ .r = true }); - vex.reg(Register.xmm1.enc()); - encoder.vex(vex); - encoder.opcode_1byte(0x16); - encoder.modRm_RIPDisp32(Register.xmm13.lowEnc()); - encoder.disp32(0); - try testing.expectEqualSlices(u8, &[_]u8{ 0xC5, 0x71, 0x16, 0x2D, 0x00, 0x00, 0x00, 0x00 }, code.items); - } -} - -// TODO add these registers to the enum and populate dwarfLocOp -// // Return Address register. This is stored in `0(%rsp, "")` and is not a physical register. -// RA = (16, "RA"), -// -// XMM0 = (17, "xmm0"), -// XMM1 = (18, "xmm1"), -// XMM2 = (19, "xmm2"), -// XMM3 = (20, "xmm3"), -// XMM4 = (21, "xmm4"), -// XMM5 = (22, "xmm5"), -// XMM6 = (23, "xmm6"), -// XMM7 = (24, "xmm7"), -// -// XMM8 = (25, "xmm8"), -// XMM9 = (26, "xmm9"), -// XMM10 = (27, "xmm10"), -// XMM11 = (28, "xmm11"), -// XMM12 = (29, "xmm12"), -// XMM13 = (30, "xmm13"), -// XMM14 = (31, "xmm14"), -// XMM15 = (32, "xmm15"), -// -// ST0 = (33, "st0"), -// ST1 = (34, "st1"), -// ST2 = (35, "st2"), -// ST3 = (36, "st3"), -// ST4 = (37, "st4"), -// ST5 = (38, "st5"), -// ST6 = (39, "st6"), -// ST7 = (40, "st7"), -// -// MM0 = (41, "mm0"), -// MM1 = (42, "mm1"), -// MM2 = (43, "mm2"), -// MM3 = (44, "mm3"), -// MM4 = (45, "mm4"), -// MM5 = (46, "mm5"), -// MM6 = (47, "mm6"), -// MM7 = (48, "mm7"), -// -// RFLAGS = (49, "rFLAGS"), -// ES = (50, "es"), -// CS = (51, "cs"), -// SS = (52, "ss"), -// DS = (53, "ds"), -// FS = (54, "fs"), -// GS = (55, "gs"), -// -// FS_BASE = (58, "fs.base"), -// GS_BASE = (59, "gs.base"), -// -// TR = (62, "tr"), -// LDTR = (63, "ldtr"), -// MXCSR = (64, "mxcsr"), -// FCW = (65, "fcw"), -// FSW = (66, "fsw"), -// -// XMM16 = (67, "xmm16"), -// XMM17 = (68, "xmm17"), -// XMM18 = (69, "xmm18"), -// XMM19 = (70, "xmm19"), -// XMM20 = (71, "xmm20"), -// XMM21 = (72, "xmm21"), -// XMM22 = (73, "xmm22"), -// XMM23 = (74, "xmm23"), -// XMM24 = (75, "xmm24"), -// XMM25 = (76, "xmm25"), -// XMM26 = (77, "xmm26"), -// XMM27 = (78, "xmm27"), -// XMM28 = (79, "xmm28"), -// XMM29 = (80, "xmm29"), -// XMM30 = (81, "xmm30"), -// XMM31 = (82, "xmm31"), -// -// K0 = (118, "k0"), -// K1 = (119, "k1"), -// K2 = (120, "k2"), -// K3 = (121, "k3"), -// K4 = (122, "k4"), -// K5 = (123, "k5"), -// K6 = (124, "k6"), -// K7 = (125, "k7"), diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig new file mode 100644 index 0000000000..3daffc7ad2 --- /dev/null +++ b/src/arch/x86_64/encoder.zig @@ -0,0 +1,794 @@ +const std = @import("std"); +const assert = std.debug.assert; +const math = std.math; + +const bits = @import("bits.zig"); +const Encoding = @import("Encoding.zig"); +const Memory = bits.Memory; +const Moffs = bits.Moffs; +const PtrSize = bits.PtrSize; +const Register = bits.Register; + +pub const Instruction = struct { + op1: Operand = .none, + op2: Operand = .none, + op3: Operand = .none, + op4: Operand = .none, + encoding: Encoding, + + pub const Mnemonic = Encoding.Mnemonic; + + pub const Operand = union(enum) { + none, + reg: Register, + mem: Memory, + imm: i64, + + /// Returns the bitsize of the operand. + /// Asserts the operand is either register or memory. + pub fn size(op: Operand) u64 { + return switch (op) { + .none => unreachable, + .reg => |reg| reg.size(), + .mem => |mem| mem.size(), + .imm => unreachable, + }; + } + + /// Returns true if the operand is a segment register. + /// Asserts the operand is either register or memory. + pub fn isSegmentRegister(op: Operand) bool { + return switch (op) { + .none => unreachable, + .reg => |reg| reg.class() == .segment, + .mem => |mem| mem.isSegmentRegister(), + .imm => unreachable, + }; + } + + pub fn fmtPrint(op: Operand, enc_op: Encoding.Op, writer: anytype) !void { + switch (op) { + .none => {}, + .reg => |reg| try writer.writeAll(@tagName(reg)), + .mem => |mem| switch (mem) { + .rip => |rip| { + try writer.print("{s} ptr [rip", .{@tagName(rip.ptr_size)}); + if (rip.disp != 0) { + const sign_bit = if (sign(rip.disp) < 0) "-" else "+"; + const disp_abs = try std.math.absInt(rip.disp); + try writer.print(" {s} 0x{x}", .{ sign_bit, disp_abs }); + } + try writer.writeByte(']'); + }, + .sib => |sib| { + try writer.print("{s} ptr ", .{@tagName(sib.ptr_size)}); + + if (mem.isSegmentRegister()) { + return writer.print("{s}:0x{x}", .{ @tagName(sib.base.?), sib.disp }); + } + + try writer.writeByte('['); + + if (sib.base) |base| { + try writer.print("{s}", .{@tagName(base)}); + } + if (sib.scale_index) |si| { + if (sib.base != null) { + try writer.writeAll(" + "); + } + try writer.print("{s} * {d}", .{ @tagName(si.index), si.scale }); + } + if (sib.disp != 0) { + if (sib.base != null or sib.scale_index != null) { + try writer.writeByte(' '); + } + try writer.writeByte(if (sign(sib.disp) < 0) '-' else '+'); + const disp_abs = try std.math.absInt(sib.disp); + try writer.print(" 0x{x}", .{disp_abs}); + } + + try writer.writeByte(']'); + }, + .moffs => |moffs| try writer.print("{s}:0x{x}", .{ @tagName(moffs.seg), moffs.offset }), + }, + .imm => |imm| { + if (enc_op == .imm64) { + return writer.print("0x{x}", .{@bitCast(u64, imm)}); + } + const imm_abs = try std.math.absInt(imm); + if (sign(imm) < 0) { + try writer.writeByte('-'); + } + try writer.print("0x{x}", .{imm_abs}); + }, + } + } + }; + + pub fn new(mnemonic: Mnemonic, args: struct { + op1: Operand = .none, + op2: Operand = .none, + op3: Operand = .none, + op4: Operand = .none, + }) !Instruction { + const encoding = Encoding.findByMnemonic(mnemonic, .{ + .op1 = args.op1, + .op2 = args.op2, + .op3 = args.op3, + .op4 = args.op4, + }) orelse return error.InvalidInstruction; + std.log.debug("{}", .{encoding}); + return .{ + .op1 = args.op1, + .op2 = args.op2, + .op3 = args.op3, + .op4 = args.op4, + .encoding = encoding, + }; + } + + pub fn fmtPrint(inst: Instruction, writer: anytype) !void { + try writer.print("{s}", .{@tagName(inst.encoding.mnemonic)}); + const ops = [_]struct { Operand, Encoding.Op }{ + .{ inst.op1, inst.encoding.op1 }, + .{ inst.op2, inst.encoding.op2 }, + .{ inst.op3, inst.encoding.op3 }, + .{ inst.op4, inst.encoding.op4 }, + }; + for (&ops, 0..) |op, i| { + if (op[0] == .none) break; + if (i > 0) { + try writer.writeByte(','); + } + try writer.writeByte(' '); + try op[0].fmtPrint(op[1], writer); + } + } + + pub fn encode(inst: Instruction, writer: anytype) !void { + const encoder = Encoder(@TypeOf(writer)){ .writer = writer }; + const encoding = inst.encoding; + + try inst.encodeLegacyPrefixes(encoder); + try inst.encodeMandatoryPrefix(encoder); + try inst.encodeRexPrefix(encoder); + try inst.encodeOpcode(encoder); + + switch (encoding.op_en) { + .np, .o => {}, + .i, .d => try encodeImm(inst.op1.imm, encoding.op1, encoder), + .zi, .oi => try encodeImm(inst.op2.imm, encoding.op2, encoder), + .fd => try encoder.imm64(inst.op2.mem.moffs.offset), + .td => try encoder.imm64(inst.op1.mem.moffs.offset), + else => { + const mem_op = switch (encoding.op_en) { + .m, .mi, .m1, .mc, .mr => inst.op1, + .rm, .rmi => inst.op2, + else => unreachable, + }; + switch (mem_op) { + .reg => |reg| { + const rm = switch (encoding.op_en) { + .m, .mi, .m1, .mc => encoding.modRmExt(), + .mr => inst.op2.reg.lowEnc(), + .rm, .rmi => inst.op1.reg.lowEnc(), + else => unreachable, + }; + try encoder.modRm_direct(rm, reg.lowEnc()); + }, + .mem => |mem| { + const op = switch (encoding.op_en) { + .m, .mi, .m1, .mc => .none, + .mr => inst.op2, + .rm, .rmi => inst.op1, + else => unreachable, + }; + try encodeMemory(encoding, mem, op, encoder); + }, + else => unreachable, + } + + switch (encoding.op_en) { + .mi => try encodeImm(inst.op2.imm, encoding.op2, encoder), + .rmi => try encodeImm(inst.op3.imm, encoding.op3, encoder), + else => {}, + } + }, + } + } + + fn encodeOpcode(inst: Instruction, encoder: anytype) !void { + const opcode = inst.encoding.opcode(); + switch (inst.encoding.op_en) { + .o, .oi => try encoder.opcode_withReg(opcode[0], inst.op1.reg.lowEnc()), + else => { + const index: usize = if (inst.encoding.mandatoryPrefix()) |_| 1 else 0; + for (opcode[index..]) |byte| { + try encoder.opcode_1byte(byte); + } + }, + } + } + + fn encodeLegacyPrefixes(inst: Instruction, encoder: anytype) !void { + const enc = inst.encoding; + const op_en = enc.op_en; + + var legacy = LegacyPrefixes{}; + if (enc.mode == .none) { + const bit_size = enc.operandSize(); + if (bit_size == 16) { + legacy.set16BitOverride(); + } + } + + const segment_override: ?Register = switch (op_en) { + .i, .zi, .o, .oi, .d, .np => null, + .fd => inst.op2.mem.base().?, + .td => inst.op1.mem.base().?, + .rm, .rmi => if (inst.op2.isSegmentRegister()) blk: { + break :blk switch (inst.op2) { + .reg => |r| r, + .mem => |m| m.base().?, + else => unreachable, + }; + } else null, + .m, .mi, .m1, .mc, .mr => if (inst.op1.isSegmentRegister()) blk: { + break :blk switch (inst.op1) { + .reg => |r| r, + .mem => |m| m.base().?, + else => unreachable, + }; + } else null, + }; + if (segment_override) |seg| { + legacy.setSegmentOverride(seg); + } + + try encoder.legacyPrefixes(legacy); + } + + fn encodeRexPrefix(inst: Instruction, encoder: anytype) !void { + const op_en = inst.encoding.op_en; + + // Check if we need REX and can actually encode it + const is_rex_invalid = for (&[_]Operand{ inst.op1, inst.op2, inst.op3, inst.op4 }) |op| switch (op) { + .reg => |r| if (r.isRexInvalid()) break true, + else => {}, + } else false; + + var rex = Rex{}; + rex.w = inst.encoding.mode == .long; + + switch (op_en) { + .np, .i, .zi, .fd, .td, .d => {}, + .o, .oi => { + rex.b = inst.op1.reg.isExtended(); + }, + .m, .mi, .m1, .mc, .mr, .rm, .rmi => { + const r_op = switch (op_en) { + .rm, .rmi => inst.op1, + .mr => inst.op2, + else => null, + }; + if (r_op) |op| { + rex.r = op.reg.isExtended(); + } + + const b_x_op = switch (op_en) { + .rm, .rmi => inst.op2, + .m, .mi, .m1, .mc, .mr => inst.op1, + else => unreachable, + }; + switch (b_x_op) { + .reg => |r| { + rex.b = r.isExtended(); + }, + .mem => |mem| { + rex.b = if (mem.base()) |base| base.isExtended() else false; + rex.x = if (mem.scaleIndex()) |si| si.index.isExtended() else false; + }, + else => unreachable, + } + }, + } + + if (rex.isSet() and is_rex_invalid) return error.CannotEncode; + + try encoder.rex(rex); + } + + fn encodeMandatoryPrefix(inst: Instruction, encoder: anytype) !void { + const prefix = inst.encoding.mandatoryPrefix() orelse return; + try encoder.opcode_1byte(prefix); + } + + fn encodeMemory(encoding: Encoding, mem: Memory, operand: Operand, encoder: anytype) !void { + const operand_enc = switch (operand) { + .reg => |reg| reg.lowEnc(), + .none => encoding.modRmExt(), + else => unreachable, + }; + + switch (mem) { + .moffs => unreachable, + .sib => |sib| { + if (sib.base) |base| { + if (base.class() == .segment) { + // TODO audit this wrt SIB + try encoder.modRm_SIBDisp0(operand_enc); + if (sib.scale_index) |si| { + const scale = math.log2_int(u4, si.scale); + try encoder.sib_scaleIndexDisp32(scale, si.index.lowEnc()); + } else { + try encoder.sib_disp32(); + } + try encoder.disp32(sib.disp); + } else { + assert(base.class() == .general_purpose); + const dst = base.lowEnc(); + const src = operand_enc; + if (dst == 4 or sib.scale_index != null) { + if (sib.disp == 0 and dst != 5) { + try encoder.modRm_SIBDisp0(src); + if (sib.scale_index) |si| { + const scale = math.log2_int(u4, si.scale); + try encoder.sib_scaleIndexBase(scale, si.index.lowEnc(), dst); + } else { + try encoder.sib_base(dst); + } + } else if (math.cast(i8, sib.disp)) |_| { + try encoder.modRm_SIBDisp8(src); + if (sib.scale_index) |si| { + const scale = math.log2_int(u4, si.scale); + try encoder.sib_scaleIndexBaseDisp8(scale, si.index.lowEnc(), dst); + } else { + try encoder.sib_baseDisp8(dst); + } + try encoder.disp8(@truncate(i8, sib.disp)); + } else { + try encoder.modRm_SIBDisp32(src); + if (sib.scale_index) |si| { + const scale = math.log2_int(u4, si.scale); + try encoder.sib_scaleIndexBaseDisp32(scale, si.index.lowEnc(), dst); + } else { + try encoder.sib_baseDisp32(dst); + } + try encoder.disp32(sib.disp); + } + } else { + if (sib.disp == 0 and dst != 5) { + try encoder.modRm_indirectDisp0(src, dst); + } else if (math.cast(i8, sib.disp)) |_| { + try encoder.modRm_indirectDisp8(src, dst); + try encoder.disp8(@truncate(i8, sib.disp)); + } else { + try encoder.modRm_indirectDisp32(src, dst); + try encoder.disp32(sib.disp); + } + } + } + } else { + try encoder.modRm_SIBDisp0(operand_enc); + if (sib.scale_index) |si| { + const scale = math.log2_int(u4, si.scale); + try encoder.sib_scaleIndexDisp32(scale, si.index.lowEnc()); + } else { + try encoder.sib_disp32(); + } + try encoder.disp32(sib.disp); + } + }, + .rip => |rip| { + try encoder.modRm_RIPDisp32(operand_enc); + try encoder.disp32(rip.disp); + }, + } + } + + fn encodeImm(imm: i64, kind: Encoding.Op, encoder: anytype) !void { + switch (kind) { + .imm8, .rel8 => try encoder.imm8(@truncate(i8, imm)), + .imm16, .rel16 => try encoder.imm16(@truncate(i16, imm)), + .imm32, .rel32 => try encoder.imm32(@truncate(i32, imm)), + .imm64 => try encoder.imm64(@bitCast(u64, imm)), + else => unreachable, + } + } +}; + +inline fn sign(i: anytype) @TypeOf(i) { + return @as(@TypeOf(i), @boolToInt(i > 0)) - @boolToInt(i < 0); +} + +pub const LegacyPrefixes = packed struct { + /// LOCK + prefix_f0: bool = false, + /// REPNZ, REPNE, REP, Scalar Double-precision + prefix_f2: bool = false, + /// REPZ, REPE, REP, Scalar Single-precision + prefix_f3: bool = false, + + /// CS segment override or Branch not taken + prefix_2e: bool = false, + /// SS segment override + prefix_36: bool = false, + /// ES segment override + prefix_26: bool = false, + /// FS segment override + prefix_64: bool = false, + /// GS segment override + prefix_65: bool = false, + + /// Branch taken + prefix_3e: bool = false, + + /// Address size override (enables 16 bit address size) + prefix_67: bool = false, + + /// Operand size override (enables 16 bit operation) + prefix_66: bool = false, + + padding: u5 = 0, + + pub fn setSegmentOverride(self: *LegacyPrefixes, reg: Register) void { + assert(reg.class() == .segment); + switch (reg) { + .cs => self.prefix_2e = true, + .ss => self.prefix_36 = true, + .es => self.prefix_26 = true, + .fs => self.prefix_64 = true, + .gs => self.prefix_65 = true, + .ds => {}, + else => unreachable, + } + } + + pub fn set16BitOverride(self: *LegacyPrefixes) void { + self.prefix_66 = true; + } +}; + +fn Encoder(comptime T: type) type { + return struct { + writer: T, + + const Self = @This(); + + // -------- + // Prefixes + // -------- + + /// Encodes legacy prefixes + pub fn legacyPrefixes(self: Self, prefixes: LegacyPrefixes) !void { + if (@bitCast(u16, prefixes) != 0) { + // Hopefully this path isn't taken very often, so we'll do it the slow way for now + + // LOCK + if (prefixes.prefix_f0) try self.writer.writeByte(0xf0); + // REPNZ, REPNE, REP, Scalar Double-precision + if (prefixes.prefix_f2) try self.writer.writeByte(0xf2); + // REPZ, REPE, REP, Scalar Single-precision + if (prefixes.prefix_f3) try self.writer.writeByte(0xf3); + + // CS segment override or Branch not taken + if (prefixes.prefix_2e) try self.writer.writeByte(0x2e); + // DS segment override + if (prefixes.prefix_36) try self.writer.writeByte(0x36); + // ES segment override + if (prefixes.prefix_26) try self.writer.writeByte(0x26); + // FS segment override + if (prefixes.prefix_64) try self.writer.writeByte(0x64); + // GS segment override + if (prefixes.prefix_65) try self.writer.writeByte(0x65); + + // Branch taken + if (prefixes.prefix_3e) try self.writer.writeByte(0x3e); + + // Operand size override + if (prefixes.prefix_66) try self.writer.writeByte(0x66); + + // Address size override + if (prefixes.prefix_67) try self.writer.writeByte(0x67); + } + } + + /// Use 16 bit operand size + /// + /// Note that this flag is overridden by REX.W, if both are present. + pub fn prefix16BitMode(self: Self) !void { + try self.writer.writeByte(0x66); + } + + /// Encodes a REX prefix byte given all the fields + /// + /// Use this byte whenever you need 64 bit operation, + /// or one of reg, index, r/m, base, or opcode-reg might be extended. + /// + /// See struct `Rex` for a description of each field. + /// + /// Does not add a prefix byte if none of the fields are set! + pub fn rex(self: Self, byte: Rex) !void { + var value: u8 = 0b0100_0000; + + if (byte.w) value |= 0b1000; + if (byte.r) value |= 0b0100; + if (byte.x) value |= 0b0010; + if (byte.b) value |= 0b0001; + + if (value != 0b0100_0000) { + try self.writer.writeByte(value); + } + } + + // ------ + // Opcode + // ------ + + /// Encodes a 1 byte opcode + pub fn opcode_1byte(self: Self, opcode: u8) !void { + try self.writer.writeByte(opcode); + } + + /// Encodes a 2 byte opcode + /// + /// e.g. IMUL has the opcode 0x0f 0xaf, so you use + /// + /// encoder.opcode_2byte(0x0f, 0xaf); + pub fn opcode_2byte(self: Self, prefix: u8, opcode: u8) !void { + try self.writer.writeAll(&.{ prefix, opcode }); + } + + /// Encodes a 3 byte opcode + /// + /// e.g. MOVSD has the opcode 0xf2 0x0f 0x10 + /// + /// encoder.opcode_3byte(0xf2, 0x0f, 0x10); + pub fn opcode_3byte(self: Self, prefix_1: u8, prefix_2: u8, opcode: u8) !void { + try self.writer.writeAll(&.{ prefix_1, prefix_2, opcode }); + } + + /// Encodes a 1 byte opcode with a reg field + /// + /// Remember to add a REX prefix byte if reg is extended! + pub fn opcode_withReg(self: Self, opcode: u8, reg: u3) !void { + assert(opcode & 0b111 == 0); + try self.writer.writeByte(opcode | reg); + } + + // ------ + // ModR/M + // ------ + + /// Construct a ModR/M byte given all the fields + /// + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm(self: Self, mod: u2, reg_or_opx: u3, rm: u3) !void { + try self.writer.writeByte(@as(u8, mod) << 6 | @as(u8, reg_or_opx) << 3 | rm); + } + + /// Construct a ModR/M byte using direct r/m addressing + /// r/m effective address: r/m + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_direct(self: Self, reg_or_opx: u3, rm: u3) !void { + try self.modRm(0b11, reg_or_opx, rm); + } + + /// Construct a ModR/M byte using indirect r/m addressing + /// r/m effective address: [r/m] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_indirectDisp0(self: Self, reg_or_opx: u3, rm: u3) !void { + assert(rm != 4 and rm != 5); + try self.modRm(0b00, reg_or_opx, rm); + } + + /// Construct a ModR/M byte using indirect SIB addressing + /// r/m effective address: [SIB] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_SIBDisp0(self: Self, reg_or_opx: u3) !void { + try self.modRm(0b00, reg_or_opx, 0b100); + } + + /// Construct a ModR/M byte using RIP-relative addressing + /// r/m effective address: [RIP + disp32] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_RIPDisp32(self: Self, reg_or_opx: u3) !void { + try self.modRm(0b00, reg_or_opx, 0b101); + } + + /// Construct a ModR/M byte using indirect r/m with a 8bit displacement + /// r/m effective address: [r/m + disp8] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_indirectDisp8(self: Self, reg_or_opx: u3, rm: u3) !void { + assert(rm != 4); + try self.modRm(0b01, reg_or_opx, rm); + } + + /// Construct a ModR/M byte using indirect SIB with a 8bit displacement + /// r/m effective address: [SIB + disp8] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_SIBDisp8(self: Self, reg_or_opx: u3) !void { + try self.modRm(0b01, reg_or_opx, 0b100); + } + + /// Construct a ModR/M byte using indirect r/m with a 32bit displacement + /// r/m effective address: [r/m + disp32] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_indirectDisp32(self: Self, reg_or_opx: u3, rm: u3) !void { + assert(rm != 4); + try self.modRm(0b10, reg_or_opx, rm); + } + + /// Construct a ModR/M byte using indirect SIB with a 32bit displacement + /// r/m effective address: [SIB + disp32] + /// + /// Note reg's effective address is always just reg for the ModR/M byte. + /// Remember to add a REX prefix byte if reg or rm are extended! + pub fn modRm_SIBDisp32(self: Self, reg_or_opx: u3) !void { + try self.modRm(0b10, reg_or_opx, 0b100); + } + + // --- + // SIB + // --- + + /// Construct a SIB byte given all the fields + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib(self: Self, scale: u2, index: u3, base: u3) !void { + try self.writer.writeByte(@as(u8, scale) << 6 | @as(u8, index) << 3 | base); + } + + /// Construct a SIB byte with scale * index + base, no frills. + /// r/m effective address: [base + scale * index] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_scaleIndexBase(self: Self, scale: u2, index: u3, base: u3) !void { + assert(base != 5); + + try self.sib(scale, index, base); + } + + /// Construct a SIB byte with scale * index + disp32 + /// r/m effective address: [scale * index + disp32] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_scaleIndexDisp32(self: Self, scale: u2, index: u3) !void { + // scale is actually ignored + // index = 4 means no index if and only if we haven't extended the register + // TODO enforce this + // base = 5 means no base, if mod == 0. + try self.sib(scale, index, 5); + } + + /// Construct a SIB byte with just base + /// r/m effective address: [base] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_base(self: Self, base: u3) !void { + assert(base != 5); + + // scale is actually ignored + // index = 4 means no index + try self.sib(0, 4, base); + } + + /// Construct a SIB byte with just disp32 + /// r/m effective address: [disp32] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_disp32(self: Self) !void { + // scale is actually ignored + // index = 4 means no index + // base = 5 means no base, if mod == 0. + try self.sib(0, 4, 5); + } + + /// Construct a SIB byte with scale * index + base + disp8 + /// r/m effective address: [base + scale * index + disp8] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_scaleIndexBaseDisp8(self: Self, scale: u2, index: u3, base: u3) !void { + try self.sib(scale, index, base); + } + + /// Construct a SIB byte with base + disp8, no index + /// r/m effective address: [base + disp8] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_baseDisp8(self: Self, base: u3) !void { + // scale is ignored + // index = 4 means no index + try self.sib(0, 4, base); + } + + /// Construct a SIB byte with scale * index + base + disp32 + /// r/m effective address: [base + scale * index + disp32] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_scaleIndexBaseDisp32(self: Self, scale: u2, index: u3, base: u3) !void { + try self.sib(scale, index, base); + } + + /// Construct a SIB byte with base + disp32, no index + /// r/m effective address: [base + disp32] + /// + /// Remember to add a REX prefix byte if index or base are extended! + pub fn sib_baseDisp32(self: Self, base: u3) !void { + // scale is ignored + // index = 4 means no index + try self.sib(0, 4, base); + } + + // ------------------------- + // Trivial (no bit fiddling) + // ------------------------- + + /// Encode an 8 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm8(self: Self, imm: i8) !void { + try self.writer.writeByte(@bitCast(u8, imm)); + } + + /// Encode an 8 bit displacement + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn disp8(self: Self, disp: i8) !void { + try self.writer.writeByte(@bitCast(u8, disp)); + } + + /// Encode an 16 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm16(self: Self, imm: i16) !void { + try self.writer.writeIntLittle(i16, imm); + } + + /// Encode an 32 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm32(self: Self, imm: i32) !void { + try self.writer.writeIntLittle(i32, imm); + } + + /// Encode an 32 bit displacement + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn disp32(self: Self, disp: i32) !void { + try self.writer.writeIntLittle(i32, disp); + } + + /// Encode an 64 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm64(self: Self, imm: u64) !void { + try self.writer.writeIntLittle(u64, imm); + } + }; +} + +pub const Rex = struct { + w: bool = false, + r: bool = false, + x: bool = false, + b: bool = false, + + pub fn isSet(rex: Rex) bool { + return rex.w or rex.r or rex.x or rex.b; + } +}; diff --git a/src/arch/x86_64/encodings.zig b/src/arch/x86_64/encodings.zig new file mode 100644 index 0000000000..b7099d2ad2 --- /dev/null +++ b/src/arch/x86_64/encodings.zig @@ -0,0 +1,542 @@ +const Encoding = @import("Encoding.zig"); +const Mnemonic = Encoding.Mnemonic; +const OpEn = Encoding.OpEn; +const Op = Encoding.Op; +const Mode = Encoding.Mode; + +const opcode_len = u2; +const modrm_ext = u3; + +const Entry = struct { Mnemonic, OpEn, Op, Op, Op, Op, opcode_len, u8, u8, u8, modrm_ext, Mode }; + +// TODO move this into a .zon file when Zig is capable of importing .zon files +// zig fmt: off +pub const table = &[_]Entry{ + // General-purpose + .{ .adc, .zi, .al, .imm8, .none, .none, 1, 0x14, 0x00, 0x00, 0, .none }, + .{ .adc, .zi, .ax, .imm16, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, + .{ .adc, .zi, .eax, .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, + .{ .adc, .zi, .rax, .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .long }, + .{ .adc, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .long }, + .{ .adc, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 2, .long }, + .{ .adc, .mr, .rm8, .r8, .none, .none, 1, 0x10, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm16, .r16, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm32, .r32, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm64, .r64, .none, .none, 1, 0x11, 0x00, 0x00, 0, .long }, + .{ .adc, .rm, .r8, .rm8, .none, .none, 1, 0x12, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r16, .rm16, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r32, .rm32, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r64, .rm64, .none, .none, 1, 0x13, 0x00, 0x00, 0, .long }, + + .{ .add, .zi, .al, .imm8, .none, .none, 1, 0x04, 0x00, 0x00, 0, .none }, + .{ .add, .zi, .ax, .imm16, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, + .{ .add, .zi, .eax, .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, + .{ .add, .zi, .rax, .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .long }, + .{ .add, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .long }, + .{ .add, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 0, .long }, + .{ .add, .mr, .rm8, .r8, .none, .none, 1, 0x00, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm16, .r16, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm32, .r32, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm64, .r64, .none, .none, 1, 0x01, 0x00, 0x00, 0, .long }, + .{ .add, .rm, .r8, .rm8, .none, .none, 1, 0x02, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r16, .rm16, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r32, .rm32, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r64, .rm64, .none, .none, 1, 0x03, 0x00, 0x00, 0, .long }, + + .{ .@"and", .zi, .al, .imm8, .none, .none, 1, 0x24, 0x00, 0x00, 0, .none }, + .{ .@"and", .zi, .ax, .imm16, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, + .{ .@"and", .zi, .eax, .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, + .{ .@"and", .zi, .rax, .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .long }, + .{ .@"and", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .long }, + .{ .@"and", .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 4, .long }, + .{ .@"and", .mr, .rm8, .r8, .none, .none, 1, 0x20, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm16, .r16, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm32, .r32, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm64, .r64, .none, .none, 1, 0x21, 0x00, 0x00, 0, .long }, + .{ .@"and", .rm, .r8, .rm8, .none, .none, 1, 0x22, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r16, .rm16, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r32, .rm32, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r64, .rm64, .none, .none, 1, 0x23, 0x00, 0x00, 0, .long }, + + // This is M encoding according to Intel, but D makes more sense here. + .{ .call, .d, .rel32, .none, .none, .none, 1, 0xe8, 0x00, 0x00, 0, .none }, + .{ .call, .m, .rm64, .none, .none, .none, 1, 0xff, 0x00, 0x00, 2, .none }, + + .{ .cbw, .np, .o16, .none, .none, .none, 1, 0x98, 0x00, 0x00, 0, .none }, + .{ .cwde, .np, .o32, .none, .none, .none, 1, 0x98, 0x00, 0x00, 0, .none }, + .{ .cdqe, .np, .o64, .none, .none, .none, 1, 0x98, 0x00, 0x00, 0, .long }, + + .{ .cwd, .np, .o16, .none, .none, .none, 1, 0x99, 0x00, 0x00, 0, .none }, + .{ .cdq, .np, .o32, .none, .none, .none, 1, 0x99, 0x00, 0x00, 0, .none }, + .{ .cqo, .np, .o64, .none, .none, .none, 1, 0x99, 0x00, 0x00, 0, .long }, + + .{ .cmova, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .none }, + .{ .cmova, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .none }, + .{ .cmova, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .long }, + .{ .cmovae, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none }, + .{ .cmovae, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none }, + .{ .cmovae, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .long }, + .{ .cmovb, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none }, + .{ .cmovb, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none }, + .{ .cmovb, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .long }, + .{ .cmovbe, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .none }, + .{ .cmovbe, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .none }, + .{ .cmovbe, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .long }, + .{ .cmovc, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none }, + .{ .cmovc, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none }, + .{ .cmovc, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .long }, + .{ .cmove, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none }, + .{ .cmove, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none }, + .{ .cmove, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .long }, + .{ .cmovg, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .none }, + .{ .cmovg, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .none }, + .{ .cmovg, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .long }, + .{ .cmovge, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .none }, + .{ .cmovge, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .none }, + .{ .cmovge, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .long }, + .{ .cmovl, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .none }, + .{ .cmovl, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .none }, + .{ .cmovl, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .long }, + .{ .cmovle, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .none }, + .{ .cmovle, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .none }, + .{ .cmovle, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .long }, + .{ .cmovna, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .none }, + .{ .cmovna, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .none }, + .{ .cmovna, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x46, 0x00, 0, .long }, + .{ .cmovnae, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none }, + .{ .cmovnae, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .none }, + .{ .cmovnae, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x42, 0x00, 0, .long }, + .{ .cmovnb, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none }, + .{ .cmovnb, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none }, + .{ .cmovnb, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .long }, + .{ .cmovnbe, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .none }, + .{ .cmovnbe, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .none }, + .{ .cmovnbe, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x47, 0x00, 0, .long }, + .{ .cmovnc, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none }, + .{ .cmovnc, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .none }, + .{ .cmovnc, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x43, 0x00, 0, .long }, + .{ .cmovne, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .none }, + .{ .cmovne, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .none }, + .{ .cmovne, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .long }, + .{ .cmovng, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .none }, + .{ .cmovng, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .none }, + .{ .cmovng, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4e, 0x00, 0, .long }, + .{ .cmovnge, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .none }, + .{ .cmovnge, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .none }, + .{ .cmovnge, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4c, 0x00, 0, .long }, + .{ .cmovnl, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .none }, + .{ .cmovnl, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .none }, + .{ .cmovnl, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4d, 0x00, 0, .long }, + .{ .cmovnle, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .none }, + .{ .cmovnle, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .none }, + .{ .cmovnle, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4f, 0x00, 0, .long }, + .{ .cmovno, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x41, 0x00, 0, .none }, + .{ .cmovno, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x41, 0x00, 0, .none }, + .{ .cmovno, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x41, 0x00, 0, .long }, + .{ .cmovnp, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .none }, + .{ .cmovnp, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .none }, + .{ .cmovnp, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .long }, + .{ .cmovns, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x49, 0x00, 0, .none }, + .{ .cmovns, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x49, 0x00, 0, .none }, + .{ .cmovns, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x49, 0x00, 0, .long }, + .{ .cmovnz, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .none }, + .{ .cmovnz, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .none }, + .{ .cmovnz, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x45, 0x00, 0, .long }, + .{ .cmovo, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x40, 0x00, 0, .none }, + .{ .cmovo, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x40, 0x00, 0, .none }, + .{ .cmovo, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x40, 0x00, 0, .long }, + .{ .cmovp, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .none }, + .{ .cmovp, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .none }, + .{ .cmovp, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .long }, + .{ .cmovpe, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .none }, + .{ .cmovpe, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .none }, + .{ .cmovpe, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4a, 0x00, 0, .long }, + .{ .cmovpo, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .none }, + .{ .cmovpo, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .none }, + .{ .cmovpo, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x4b, 0x00, 0, .long }, + .{ .cmovs, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x48, 0x00, 0, .none }, + .{ .cmovs, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x48, 0x00, 0, .none }, + .{ .cmovs, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x48, 0x00, 0, .long }, + .{ .cmovz, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none }, + .{ .cmovz, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none }, + .{ .cmovz, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .long }, + + .{ .cmp, .zi, .al, .imm8, .none, .none, 1, 0x3c, 0x00, 0x00, 0, .none }, + .{ .cmp, .zi, .ax, .imm16, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, + .{ .cmp, .zi, .eax, .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, + .{ .cmp, .zi, .rax, .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .long }, + .{ .cmp, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .long }, + .{ .cmp, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 7, .long }, + .{ .cmp, .mr, .rm8, .r8, .none, .none, 1, 0x38, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm16, .r16, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm32, .r32, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm64, .r64, .none, .none, 1, 0x39, 0x00, 0x00, 0, .long }, + .{ .cmp, .rm, .r8, .rm8, .none, .none, 1, 0x3a, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r16, .rm16, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r32, .rm32, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r64, .rm64, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .long }, + + .{ .div, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 6, .none }, + .{ .div, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .none }, + .{ .div, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .none }, + .{ .div, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .long }, + + .{ .fisttp, .m, .m16, .none, .none, .none, 1, 0xdf, 0x00, 0x00, 1, .fpu }, + .{ .fisttp, .m, .m32, .none, .none, .none, 1, 0xdb, 0x00, 0x00, 1, .fpu }, + .{ .fisttp, .m, .m64, .none, .none, .none, 1, 0xdd, 0x00, 0x00, 1, .fpu }, + + .{ .fld, .m, .m32, .none, .none, .none, 1, 0xd9, 0x00, 0x00, 0, .fpu }, + .{ .fld, .m, .m64, .none, .none, .none, 1, 0xdd, 0x00, 0x00, 0, .fpu }, + .{ .fld, .m, .m80, .none, .none, .none, 1, 0xdb, 0x00, 0x00, 5, .fpu }, + + .{ .idiv, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 7, .none }, + .{ .idiv, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .none }, + .{ .idiv, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .none }, + .{ .idiv, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .long }, + + .{ .imul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .long }, + .{ .imul, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .none }, + .{ .imul, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .none }, + .{ .imul, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .long }, + .{ .imul, .rmi, .r16, .rm16, .imm8, .none, 1, 0x6b, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r32, .rm32, .imm8, .none, 1, 0x6b, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r64, .rm64, .imm8, .none, 1, 0x6b, 0x00, 0x00, 0, .long }, + .{ .imul, .rmi, .r16, .rm16, .imm16, .none, 1, 0x69, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r32, .rm32, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r64, .rm64, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .long }, + + .{ .int3, .np, .none, .none, .none, .none, 1, 0xcc, 0x00, 0x00, 0, .none }, + + .{ .ja, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x87, 0x00, 0, .none }, + .{ .jae, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x83, 0x00, 0, .none }, + .{ .jb, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x82, 0x00, 0, .none }, + .{ .jbe, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x86, 0x00, 0, .none }, + .{ .jc, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x82, 0x00, 0, .none }, + .{ .jrcxz, .d, .rel32, .none, .none, .none, 1, 0xe3, 0x00, 0x00, 0, .none }, + .{ .je, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x84, 0x00, 0, .none }, + .{ .jg, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8f, 0x00, 0, .none }, + .{ .jge, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8d, 0x00, 0, .none }, + .{ .jl, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8c, 0x00, 0, .none }, + .{ .jle, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8e, 0x00, 0, .none }, + .{ .jna, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x86, 0x00, 0, .none }, + .{ .jnae, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x82, 0x00, 0, .none }, + .{ .jnb, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x83, 0x00, 0, .none }, + .{ .jnbe, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x87, 0x00, 0, .none }, + .{ .jnc, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x83, 0x00, 0, .none }, + .{ .jne, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x85, 0x00, 0, .none }, + .{ .jng, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8e, 0x00, 0, .none }, + .{ .jnge, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8c, 0x00, 0, .none }, + .{ .jnl, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8d, 0x00, 0, .none }, + .{ .jnle, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8f, 0x00, 0, .none }, + .{ .jno, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x81, 0x00, 0, .none }, + .{ .jnp, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8b, 0x00, 0, .none }, + .{ .jns, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x89, 0x00, 0, .none }, + .{ .jnz, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x85, 0x00, 0, .none }, + .{ .jo, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x80, 0x00, 0, .none }, + .{ .jp, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8a, 0x00, 0, .none }, + .{ .jpe, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8a, 0x00, 0, .none }, + .{ .jpo, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x8b, 0x00, 0, .none }, + .{ .js, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x88, 0x00, 0, .none }, + .{ .jz, .d, .rel32, .none, .none, .none, 2, 0x0f, 0x84, 0x00, 0, .none }, + + .{ .jmp, .d, .rel32, .none, .none, .none, 1, 0xe9, 0x00, 0x00, 0, .none }, + .{ .jmp, .m, .rm64, .none, .none, .none, 1, 0xff, 0x00, 0x00, 4, .none }, + + .{ .lea, .rm, .r16, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .none }, + .{ .lea, .rm, .r32, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .none }, + .{ .lea, .rm, .r64, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .long }, + + .{ .mov, .mr, .rm8, .r8, .none, .none, 1, 0x88, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm16, .r16, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm32, .r32, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm64, .r64, .none, .none, 1, 0x89, 0x00, 0x00, 0, .long }, + .{ .mov, .rm, .r8, .rm8, .none, .none, 1, 0x8a, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r16, .rm16, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r32, .rm32, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r64, .rm64, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .long }, + .{ .mov, .mr, .rm16, .sreg, .none, .none, 1, 0x8c, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm64, .sreg, .none, .none, 1, 0x8c, 0x00, 0x00, 0, .long }, + .{ .mov, .rm, .sreg, .rm16, .none, .none, 1, 0x8e, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .sreg, .rm64, .none, .none, 1, 0x8e, 0x00, 0x00, 0, .long }, + .{ .mov, .fd, .al, .moffs, .none, .none, 1, 0xa0, 0x00, 0x00, 0, .none }, + .{ .mov, .fd, .ax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none }, + .{ .mov, .fd, .eax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none }, + .{ .mov, .fd, .rax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .long }, + .{ .mov, .td, .moffs, .al, .none, .none, 1, 0xa2, 0x00, 0x00, 0, .none }, + .{ .mov, .td, .moffs, .ax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, + .{ .mov, .td, .moffs, .eax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, + .{ .mov, .td, .moffs, .rax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .long }, + .{ .mov, .oi, .r8, .imm8, .none, .none, 1, 0xb0, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r16, .imm16, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r32, .imm32, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r64, .imm64, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .long }, + .{ .mov, .mi, .rm8, .imm8, .none, .none, 1, 0xc6, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm16, .imm16, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm32, .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm64, .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .long }, + + .{ .movsx, .rm, .r16, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none }, + .{ .movsx, .rm, .r32, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none }, + .{ .movsx, .rm, .r64, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .long }, + .{ .movsx, .rm, .r32, .rm16, .none, .none, 2, 0x0f, 0xbf, 0x00, 0, .none }, + .{ .movsx, .rm, .r64, .rm16, .none, .none, 2, 0x0f, 0xbf, 0x00, 0, .long }, + + // This instruction is discouraged. + .{ .movsxd, .rm, .r32, .rm32, .none, .none, 1, 0x63, 0x00, 0x00, 0, .none }, + .{ .movsxd, .rm, .r64, .rm32, .none, .none, 1, 0x63, 0x00, 0x00, 0, .long }, + + .{ .movzx, .rm, .r16, .rm8, .none, .none, 2, 0x0f, 0xb6, 0x00, 0, .none }, + .{ .movzx, .rm, .r32, .rm8, .none, .none, 2, 0x0f, 0xb6, 0x00, 0, .none }, + .{ .movzx, .rm, .r64, .rm8, .none, .none, 2, 0x0f, 0xb6, 0x00, 0, .long }, + .{ .movzx, .rm, .r32, .rm16, .none, .none, 2, 0x0f, 0xb7, 0x00, 0, .none }, + .{ .movzx, .rm, .r64, .rm16, .none, .none, 2, 0x0f, 0xb7, 0x00, 0, .long }, + + .{ .mul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 4, .none }, + .{ .mul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .none }, + .{ .mul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .none }, + .{ .mul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .long }, + + .{ .nop, .np, .none, .none, .none, .none, 1, 0x90, 0x00, 0x00, 0, .none }, + + .{ .@"or", .zi, .al, .imm8, .none, .none, 1, 0x0c, 0x00, 0x00, 0, .none }, + .{ .@"or", .zi, .ax, .imm16, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, + .{ .@"or", .zi, .eax, .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, + .{ .@"or", .zi, .rax, .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .long }, + .{ .@"or", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .long }, + .{ .@"or", .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 1, .long }, + .{ .@"or", .mr, .rm8, .r8, .none, .none, 1, 0x08, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm16, .r16, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm32, .r32, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm64, .r64, .none, .none, 1, 0x09, 0x00, 0x00, 0, .long }, + .{ .@"or", .rm, .r8, .rm8, .none, .none, 1, 0x0a, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r16, .rm16, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r32, .rm32, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r64, .rm64, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .long }, + + .{ .pop, .o, .r16, .none, .none, .none, 1, 0x58, 0x00, 0x00, 0, .none }, + .{ .pop, .o, .r64, .none, .none, .none, 1, 0x58, 0x00, 0x00, 0, .none }, + .{ .pop, .m, .rm16, .none, .none, .none, 1, 0x8f, 0x00, 0x00, 0, .none }, + .{ .pop, .m, .rm64, .none, .none, .none, 1, 0x8f, 0x00, 0x00, 0, .none }, + + .{ .push, .o, .r16, .none, .none, .none, 1, 0x50, 0x00, 0x00, 0, .none }, + .{ .push, .o, .r64, .none, .none, .none, 1, 0x50, 0x00, 0x00, 0, .none }, + .{ .push, .m, .rm16, .none, .none, .none, 1, 0xff, 0x00, 0x00, 6, .none }, + .{ .push, .m, .rm64, .none, .none, .none, 1, 0xff, 0x00, 0x00, 6, .none }, + .{ .push, .i, .imm8, .none, .none, .none, 1, 0x6a, 0x00, 0x00, 0, .none }, + .{ .push, .i, .imm16, .none, .none, .none, 1, 0x68, 0x00, 0x00, 0, .none }, + .{ .push, .i, .imm32, .none, .none, .none, 1, 0x68, 0x00, 0x00, 0, .none }, + + .{ .ret, .np, .none, .none, .none, .none, 1, 0xc3, 0x00, 0x00, 0, .none }, + + .{ .sal, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .none }, + .{ .sal, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, + .{ .sal, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, + .{ .sal, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .long }, + .{ .sal, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 4, .none }, + .{ .sal, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, + .{ .sal, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, + .{ .sal, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .long }, + .{ .sal, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 4, .none }, + .{ .sal, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, + .{ .sal, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, + .{ .sal, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .long }, + + .{ .sar, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 7, .none }, + .{ .sar, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .none }, + .{ .sar, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .none }, + .{ .sar, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .long }, + .{ .sar, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 7, .none }, + .{ .sar, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 7, .none }, + .{ .sar, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 7, .none }, + .{ .sar, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 7, .long }, + .{ .sar, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 7, .none }, + .{ .sar, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .none }, + .{ .sar, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .none }, + .{ .sar, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .long }, + + .{ .sbb, .zi, .al, .imm8, .none, .none, 1, 0x1c, 0x00, 0x00, 0, .none }, + .{ .sbb, .zi, .ax, .imm16, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, + .{ .sbb, .zi, .eax, .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, + .{ .sbb, .zi, .rax, .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .long }, + .{ .sbb, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .long }, + .{ .sbb, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 3, .long }, + .{ .sbb, .mr, .rm8, .r8, .none, .none, 1, 0x18, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm16, .r16, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm32, .r32, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm64, .r64, .none, .none, 1, 0x19, 0x00, 0x00, 0, .long }, + .{ .sbb, .rm, .r8, .rm8, .none, .none, 1, 0x1a, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r16, .rm16, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r32, .rm32, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r64, .rm64, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .long }, + + .{ .seta, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .none }, + .{ .setae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, + .{ .setb, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none }, + .{ .setbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .none }, + .{ .setc, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none }, + .{ .sete, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .none }, + .{ .setg, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .none }, + .{ .setge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .none }, + .{ .setl, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .none }, + .{ .setle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .none }, + .{ .setna, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .none }, + .{ .setnae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none }, + .{ .setnb, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, + .{ .setnbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .none }, + .{ .setnc, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, + .{ .setne, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .none }, + .{ .setng, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .none }, + .{ .setnge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .none }, + .{ .setnl, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .none }, + .{ .setnle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .none }, + .{ .setno, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x91, 0x00, 0, .none }, + .{ .setnp, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .none }, + .{ .setns, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x99, 0x00, 0, .none }, + .{ .setnz, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .none }, + .{ .seto, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x90, 0x00, 0, .none }, + .{ .setp, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .none }, + .{ .setpe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .none }, + .{ .setpo, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .none }, + .{ .sets, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x98, 0x00, 0, .none }, + .{ .setz, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .none }, + + .{ .shl, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .none }, + .{ .shl, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, + .{ .shl, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, + .{ .shl, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .long }, + .{ .shl, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 4, .none }, + .{ .shl, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, + .{ .shl, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, + .{ .shl, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .long }, + .{ .shl, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 4, .none }, + .{ .shl, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, + .{ .shl, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, + .{ .shl, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .long }, + + .{ .shr, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 5, .none }, + .{ .shr, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .none }, + .{ .shr, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .none }, + .{ .shr, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .long }, + .{ .shr, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 5, .none }, + .{ .shr, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 5, .none }, + .{ .shr, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 5, .none }, + .{ .shr, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 5, .long }, + .{ .shr, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 5, .none }, + .{ .shr, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .none }, + .{ .shr, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .none }, + .{ .shr, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .long }, + + .{ .sub, .zi, .al, .imm8, .none, .none, 1, 0x2c, 0x00, 0x00, 0, .none }, + .{ .sub, .zi, .ax, .imm16, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, + .{ .sub, .zi, .eax, .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, + .{ .sub, .zi, .rax, .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .long }, + .{ .sub, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .long }, + .{ .sub, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 5, .long }, + .{ .sub, .mr, .rm8, .r8, .none, .none, 1, 0x28, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm16, .r16, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm32, .r32, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm64, .r64, .none, .none, 1, 0x29, 0x00, 0x00, 0, .long }, + .{ .sub, .rm, .r8, .rm8, .none, .none, 1, 0x2a, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r16, .rm16, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r32, .rm32, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r64, .rm64, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .long }, + + .{ .syscall, .np, .none, .none, .none, .none, 2, 0x0f, 0x05, 0x00, 0, .none }, + + .{ .@"test", .zi, .al, .imm8, .none, .none, 1, 0xa8, 0x00, 0x00, 0, .none }, + .{ .@"test", .zi, .ax, .imm16, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, + .{ .@"test", .zi, .eax, .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, + .{ .@"test", .zi, .rax, .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .long }, + .{ .@"test", .mi, .rm8, .imm8, .none, .none, 1, 0xf6, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm16, .imm16, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm32, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm64, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .long }, + .{ .@"test", .mr, .rm8, .r8, .none, .none, 1, 0x84, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm16, .r16, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm32, .r32, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm64, .r64, .none, .none, 1, 0x85, 0x00, 0x00, 0, .long }, + + .{ .ud2, .np, .none, .none, .none, .none, 2, 0x0f, 0x0b, 0x00, 0, .none }, + + .{ .xor, .zi, .al, .imm8, .none, .none, 1, 0x34, 0x00, 0x00, 0, .none }, + .{ .xor, .zi, .ax, .imm16, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, + .{ .xor, .zi, .eax, .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, + .{ .xor, .zi, .rax, .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .long }, + .{ .xor, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .long }, + .{ .xor, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 6, .long }, + .{ .xor, .mr, .rm8, .r8, .none, .none, 1, 0x30, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm16, .r16, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm32, .r32, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm64, .r64, .none, .none, 1, 0x31, 0x00, 0x00, 0, .long }, + .{ .xor, .rm, .r8, .rm8, .none, .none, 1, 0x32, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r16, .rm16, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r32, .rm32, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r64, .rm64, .none, .none, 1, 0x33, 0x00, 0x00, 0, .long }, + + // SSE + .{ .addss, .rm, .xmm, .xmm_m32, .none, .none, 3, 0xf3, 0x0f, 0x58, 0, .sse }, + + .{ .cmpss, .rmi, .xmm, .xmm_m32, .imm8, .none, 3, 0xf3, 0x0f, 0xc2, 0, .sse }, + + .{ .movss, .rm, .xmm, .xmm_m32, .none, .none, 3, 0xf3, 0x0f, 0x10, 0, .sse }, + .{ .movss, .mr, .xmm_m32, .xmm, .none, .none, 3, 0xf3, 0x0f, 0x11, 0, .sse }, + + .{ .ucomiss, .rm, .xmm, .xmm_m32, .none, .none, 2, 0x0f, 0x2e, 0x00, 0, .sse }, + + // SSE2 + .{ .addsd, .rm, .xmm, .xmm_m64, .none, .none, 3, 0xf2, 0x0f, 0x58, 0, .sse2 }, + + .{ .cmpsd, .rmi, .xmm, .xmm_m64, .imm8, .none, 3, 0xf2, 0x0f, 0xc2, 0, .sse2 }, + + .{ .movq, .rm, .xmm, .xmm_m64, .none, .none, 3, 0xf3, 0x0f, 0x7e, 0, .sse2 }, + .{ .movq, .mr, .xmm_m64, .xmm, .none, .none, 3, 0x66, 0x0f, 0xd6, 0, .sse2 }, + + .{ .movsd, .rm, .xmm, .xmm_m64, .none, .none, 3, 0xf2, 0x0f, 0x10, 0, .sse2 }, + .{ .movsd, .mr, .xmm_m64, .xmm, .none, .none, 3, 0xf2, 0x0f, 0x11, 0, .sse2 }, + + .{ .ucomisd, .rm, .xmm, .xmm_m64, .none, .none, 3, 0x66, 0x0f, 0x2e, 0, .sse2 }, +}; +// zig fmt: on From f14831ec73d7dd87a770f902fb53d1ede486e524 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 5 Mar 2023 18:47:00 +0100 Subject: [PATCH 02/31] x86_64: truncate immediates --- src/arch/x86_64/CodeGen.zig | 109 +++++++++++++++++++++++++++++------ src/arch/x86_64/Emit.zig | 16 ++--- src/arch/x86_64/Encoding.zig | 6 +- src/arch/x86_64/Mir.zig | 4 +- src/arch/x86_64/encoder.zig | 24 +++----- 5 files changed, 114 insertions(+), 45 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index c108ad6f32..ef599092de 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -385,6 +385,24 @@ pub fn addExtra(self: *Self, extra: anytype) Allocator.Error!u32 { return self.addExtraAssumeCapacity(extra); } +fn extraData(self: *Self, comptime T: type, index: u32) struct { data: T, end: u32 } { + const fields = std.meta.fields(T); + var i: u32 = index; + var result: T = undefined; + inline for (fields) |field| { + @field(result, field.name) = switch (field.type) { + u32 => self.mir_extra.items[i], + i32 => @bitCast(i32, self.mir_extra.items[i]), + else => @compileError("bad field type"), + }; + i += 1; + } + return .{ + .data = result, + .end = i, + }; +} + pub fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { const fields = std.meta.fields(@TypeOf(extra)); const result = @intCast(u32, self.mir_extra.items.len); @@ -2759,9 +2777,15 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type 1, 2, 4 => { // TODO this is wasteful! // introduce new MIR tag specifically for mov [reg + 0], imm + const operand = switch (abi_size) { + 1 => @truncate(u8, imm), + 2 => @truncate(u16, imm), + 4 => @truncate(u32, imm), + else => unreachable, + }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = 0, - .operand = @truncate(u32, imm), + .operand = operand, }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -2872,10 +2896,17 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type return self.fail("TODO saving imm to memory for abi_size {}", .{abi_size}); } + const operand = switch (abi_size) { + 1 => @truncate(u8, imm), + 2 => @truncate(u16, imm), + 4 => @truncate(u32, imm), + 8 => @truncate(u32, imm), + else => unreachable, + }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = 0, // TODO check if this logic is correct - .operand = @truncate(u32, imm), + .operand = operand, }); const flags: u2 = switch (abi_size) { 1 => 0b00, @@ -3600,7 +3631,13 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu _ = try self.addInst(.{ .tag = mir_tag, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(dst_reg, abi_size) }), - .data = .{ .imm = @truncate(u32, imm) }, + .data = .{ .imm = switch (abi_size) { + 1 => @truncate(u8, imm), + 2 => @truncate(u16, imm), + 4 => @truncate(u32, imm), + 8 => @truncate(u32, imm), + else => unreachable, + } }, }); }, .memory, @@ -3671,9 +3708,16 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu 8 => 0b11, else => unreachable, }; + const operand = switch (abi_size) { + 1 => @truncate(u8, imm), + 2 => @truncate(u16, imm), + 4 => @truncate(u32, imm), + 8 => @truncate(u32, imm), + else => unreachable, + }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -off, - .operand = @truncate(u32, imm), + .operand = operand, }); _ = try self.addInst(.{ .tag = tag, @@ -4855,7 +4899,13 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u _ = try self.addInst(.{ .tag = .xor, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(cond_reg, abi_size) }), - .data = .{ .imm = @intCast(u32, imm) }, + .data = .{ .imm = switch (abi_size) { + 1 => @truncate(u8, imm), + 2 => @truncate(u16, imm), + 4 => @truncate(u32, imm), + 8 => @truncate(u32, imm), + else => unreachable, + } }, }); }, .register => |reg| { @@ -5366,20 +5416,27 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE // We have a positive stack offset value but we want a twos complement negative // offset from rbp, which is at the top of the stack frame. // mov [rbp+offset], immediate + const operand = switch (abi_size) { + 1 => @truncate(u8, imm), + 2 => @truncate(u16, imm), + 4 => @truncate(u32, imm), + else => unreachable, + }; + const flags: u2 = switch (abi_size) { + 1 => 0b00, + 2 => 0b01, + 4 => 0b10, + else => unreachable, + }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = @truncate(u32, imm), + .operand = operand, }); _ = try self.addInst(.{ .tag = .mov_mem_imm, .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp, - .flags = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - else => unreachable, - }, + .flags = flags, }), .data = .{ .payload = payload }, }); @@ -5518,7 +5575,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl assert(ty.isError()); const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = @truncate(u32, x_big), + .operand = @truncate(u8, x_big), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5530,9 +5587,15 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl }); }, 1, 2, 4 => { + const operand = switch (abi_size) { + 1 => @truncate(u8, x_big), + 2 => @truncate(u16, x_big), + 4 => @truncate(u32, x_big), + else => unreachable, + }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = @truncate(u32, x_big), + .operand = operand, }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5932,7 +5995,7 @@ fn genInlineMemset( const loop_start = try self.addInst(.{ .tag = .cmp, .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - .data = .{ .imm = @bitCast(u32, @as(i32, -1)) }, + .data = .{ .imm = @bitCast(u8, @as(i8, -1)) }, }); // je end @@ -6037,7 +6100,13 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void _ = try self.addInst(.{ .tag = .mov, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }), - .data = .{ .imm = @truncate(u32, x) }, + .data = .{ .imm = switch (abi_size) { + 1 => @truncate(u8, x), + 2 => @truncate(u16, x), + 4 => @truncate(u32, x), + 8 => @truncate(u32, x), + else => unreachable, + } }, }); return; } @@ -6204,7 +6273,13 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg1 = registerAlias(reg, abi_size), .flags = 0b01, }), - .data = .{ .imm = @truncate(u32, x) }, + .data = .{ .imm = switch (abi_size) { + 1 => @truncate(u8, x), + 2 => @truncate(u16, x), + 4 => @truncate(u32, x), + 8 => @truncate(u32, x), + else => unreachable, + } }, }); } else { // If this is RAX, we can use a direct load. diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 1c540adc9d..5d52b87d87 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -236,12 +236,12 @@ fn encode(emit: *Emit, mnemonic: Instruction.Mnemonic, ops: struct { op3: Instruction.Operand = .none, op4: Instruction.Operand = .none, }) InnerError!void { - const inst = try Instruction.new(mnemonic, .{ + const inst = Instruction.new(mnemonic, .{ .op1 = ops.op1, .op2 = ops.op2, .op3 = ops.op3, .op4 = ops.op4, - }); + }) catch unreachable; return inst.encode(emit.code.writer()); } @@ -624,7 +624,7 @@ fn mirArithScaleSrc(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst. const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); const scale_index = Memory.ScaleIndex{ - .scale = scale, + .scale = @as(u4, 1) << scale, .index = index_reg_disp.index, }; return emit.encode(mnemonic, .{ @@ -643,7 +643,7 @@ fn mirArithScaleDst(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst. const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); const scale_index = Memory.ScaleIndex{ - .scale = scale, + .scale = @as(u4, 1) << scale, .index = index_reg_disp.index, }; assert(ops.reg2 != .none); @@ -663,7 +663,7 @@ fn mirArithScaleImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst. const payload = emit.mir.instructions.items(.data)[inst].payload; const index_reg_disp_imm = emit.mir.extraData(Mir.IndexRegisterDispImm, payload).data.decode(); const scale_index = Memory.ScaleIndex{ - .scale = scale, + .scale = @as(u4, 1) << scale, .index = index_reg_disp_imm.index, }; return emit.encode(mnemonic, .{ @@ -688,7 +688,7 @@ fn mirArithMemIndexImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.In 0b11 => .qword, }; const scale_index = Memory.ScaleIndex{ - .scale = 0, + .scale = 1, .index = index_reg_disp_imm.index, }; return emit.encode(mnemonic, .{ @@ -777,7 +777,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } else emit.mir.instructions.items(.data)[inst].imm; return emit.encode(.mov, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = @bitCast(i64, imm) }, + .op2 = .{ .imm = imm }, }); }, 0b01 => { @@ -983,7 +983,7 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; const scale_index = Memory.ScaleIndex{ - .scale = 0, + .scale = 1, .index = index_reg_disp.index, }; return emit.encode(.lea, .{ diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 2cccded7ec..7cf8910924 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -390,9 +390,9 @@ pub const Op = enum { .imm => |imm| { if (imm == 1) return .unity; - if (math.cast(i8, imm)) |_| return .imm8; - if (math.cast(i16, imm)) |_| return .imm16; - if (math.cast(i32, imm)) |_| return .imm32; + if (math.cast(u8, imm)) |_| return .imm8; + if (math.cast(u16, imm)) |_| return .imm16; + if (math.cast(u32, imm)) |_| return .imm32; return .imm64; }, } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index b3be08e86b..4124592627 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -594,9 +594,9 @@ pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { mir.* = undefined; } -pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } { +pub fn extraData(mir: Mir, comptime T: type, index: u32) struct { data: T, end: u32 } { const fields = std.meta.fields(T); - var i: usize = index; + var i: u32 = index; var result: T = undefined; inline for (fields) |field| { @field(result, field.name) = switch (field.type) { diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index 3daffc7ad2..eefc7fd6e2 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -22,7 +22,7 @@ pub const Instruction = struct { none, reg: Register, mem: Memory, - imm: i64, + imm: u64, /// Returns the bitsize of the operand. /// Asserts the operand is either register or memory. @@ -47,6 +47,7 @@ pub const Instruction = struct { } pub fn fmtPrint(op: Operand, enc_op: Encoding.Op, writer: anytype) !void { + _ = enc_op; switch (op) { .none => {}, .reg => |reg| try writer.writeAll(@tagName(reg)), @@ -92,14 +93,7 @@ pub const Instruction = struct { .moffs => |moffs| try writer.print("{s}:0x{x}", .{ @tagName(moffs.seg), moffs.offset }), }, .imm => |imm| { - if (enc_op == .imm64) { - return writer.print("0x{x}", .{@bitCast(u64, imm)}); - } - const imm_abs = try std.math.absInt(imm); - if (sign(imm) < 0) { - try writer.writeByte('-'); - } - try writer.print("0x{x}", .{imm_abs}); + try writer.print("0x{x}", .{imm}); }, } } @@ -117,7 +111,7 @@ pub const Instruction = struct { .op3 = args.op3, .op4 = args.op4, }) orelse return error.InvalidInstruction; - std.log.debug("{}", .{encoding}); + std.log.warn("{}", .{encoding}); return .{ .op1 = args.op1, .op2 = args.op2, @@ -386,12 +380,12 @@ pub const Instruction = struct { } } - fn encodeImm(imm: i64, kind: Encoding.Op, encoder: anytype) !void { + fn encodeImm(imm: u64, kind: Encoding.Op, encoder: anytype) !void { switch (kind) { - .imm8, .rel8 => try encoder.imm8(@truncate(i8, imm)), - .imm16, .rel16 => try encoder.imm16(@truncate(i16, imm)), - .imm32, .rel32 => try encoder.imm32(@truncate(i32, imm)), - .imm64 => try encoder.imm64(@bitCast(u64, imm)), + .imm8, .rel8 => try encoder.imm8(@bitCast(i8, @truncate(u8, imm))), + .imm16, .rel16 => try encoder.imm16(@bitCast(i16, @truncate(u16, imm))), + .imm32, .rel32 => try encoder.imm32(@bitCast(i32, @truncate(u32, imm))), + .imm64 => try encoder.imm64(imm), else => unreachable, } } From ea3b3e94aba405d4364fdb06231208467ac71f87 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 5 Mar 2023 18:55:04 +0100 Subject: [PATCH 03/31] x86_64: clean up call semantics in codegen --- src/arch/x86_64/CodeGen.zig | 6 +++--- src/arch/x86_64/Emit.zig | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index ef599092de..9f5438a9fa 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -4091,11 +4091,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier if (self.bin_file.cast(link.File.Elf)) |elf_file| { const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl); const atom = elf_file.getAtom(atom_index); - const got_addr = @intCast(u32, atom.getOffsetTableAddress(elf_file)); + const got_addr = @intCast(i32, atom.getOffsetTableAddress(elf_file)); _ = try self.addInst(.{ .tag = .call, .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), - .data = .{ .imm = got_addr }, + .data = .{ .disp = got_addr }, }); } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { const atom_index = try coff_file.getOrCreateAtomForDecl(func.owner_decl); @@ -4142,7 +4142,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier _ = try self.addInst(.{ .tag = .call, .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), - .data = .{ .imm = @intCast(u32, fn_got_addr) }, + .data = .{ .disp = @intCast(i32, fn_got_addr) }, }); } else unreachable; } else if (func_value.castTag(.extern_fn)) |func_payload| { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 5d52b87d87..8bc182d773 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -340,9 +340,9 @@ fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) }, 0b01 => { if (ops.reg1 == .none) { - const imm = emit.mir.instructions.items(.data)[inst].imm; + const disp = emit.mir.instructions.items(.data)[inst].disp; return emit.encode(mnemonic, .{ - .op1 = .{ .imm = imm }, + .op1 = .{ .mem = Memory.sib(.qword, .{ .disp = disp }) }, }); } return emit.encode(mnemonic, .{ From bc43cee7756d48064be0dd87f92e24c577788eec Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 7 Mar 2023 12:57:06 +0100 Subject: [PATCH 04/31] Get more things passing --- src/arch/x86_64/CodeGen.zig | 161 ++++++------- src/arch/x86_64/Emit.zig | 74 +++--- src/arch/x86_64/Encoding.zig | 89 +++++--- src/arch/x86_64/Mir.zig | 3 + src/arch/x86_64/bits.zig | 69 +++++- src/arch/x86_64/encoder.zig | 85 +++---- src/arch/x86_64/encodings.zig | 413 +++++++++++++++++----------------- 7 files changed, 476 insertions(+), 418 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 9f5438a9fa..90286a1bb2 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -852,10 +852,10 @@ fn processDeath(self: *Self, inst: Air.Inst.Index) void { branch.inst_table.putAssumeCapacity(inst, .dead); switch (prev_value) { .register => |reg| { - self.register_manager.freeReg(reg.to64()); + self.register_manager.freeReg(reg); }, .register_overflow => |ro| { - self.register_manager.freeReg(ro.reg.to64()); + self.register_manager.freeReg(ro.reg); self.eflags_inst = null; }, .eflags => { @@ -1253,7 +1253,13 @@ fn airNot(self: *Self, inst: Air.Inst.Index) !void { }; defer if (dst_mcv_lock) |lock| self.register_manager.unlockReg(lock); - const mask = ~@as(u64, 0); + const mask = switch (operand_ty.abiSize(self.target.*)) { + 1 => ~@as(u8, 0), + 2 => ~@as(u16, 0), + 4 => ~@as(u32, 0), + 8 => ~@as(u64, 0), + else => unreachable, + }; try self.genBinOpMir(.xor, operand_ty, dst_mcv, .{ .immediate = mask }); break :result dst_mcv; @@ -2777,15 +2783,9 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type 1, 2, 4 => { // TODO this is wasteful! // introduce new MIR tag specifically for mov [reg + 0], imm - const operand = switch (abi_size) { - 1 => @truncate(u8, imm), - 2 => @truncate(u16, imm), - 4 => @truncate(u32, imm), - else => unreachable, - }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = 0, - .operand = operand, + .operand = @intCast(u32, imm), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -2896,17 +2896,10 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type return self.fail("TODO saving imm to memory for abi_size {}", .{abi_size}); } - const operand = switch (abi_size) { - 1 => @truncate(u8, imm), - 2 => @truncate(u16, imm), - 4 => @truncate(u32, imm), - 8 => @truncate(u32, imm), - else => unreachable, - }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = 0, // TODO check if this logic is correct - .operand = operand, + .operand = @intCast(u32, imm), }); const flags: u2 = switch (abi_size) { 1 => 0b00, @@ -3102,8 +3095,8 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { const shift = @intCast(u8, struct_field_offset * @sizeOf(usize)); try self.genShiftBinOpMir(.shr, Type.usize, dst_mcv.register, .{ .immediate = shift }); - // Mask with reg.size() - struct_field_size - const max_reg_bit_width = Register.rax.size(); + // Mask with reg.bitSize() - struct_field_size + const max_reg_bit_width = Register.rax.bitSize(); const mask_shift = @intCast(u6, (max_reg_bit_width - struct_field_ty.bitSize(self.target.*))); const mask = (~@as(u64, 0)) >> mask_shift; @@ -3631,13 +3624,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu _ = try self.addInst(.{ .tag = mir_tag, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(dst_reg, abi_size) }), - .data = .{ .imm = switch (abi_size) { - 1 => @truncate(u8, imm), - 2 => @truncate(u16, imm), - 4 => @truncate(u32, imm), - 8 => @truncate(u32, imm), - else => unreachable, - } }, + .data = .{ .imm = @intCast(u32, imm) }, }); }, .memory, @@ -3708,16 +3695,9 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu 8 => 0b11, else => unreachable, }; - const operand = switch (abi_size) { - 1 => @truncate(u8, imm), - 2 => @truncate(u16, imm), - 4 => @truncate(u32, imm), - 8 => @truncate(u32, imm), - else => unreachable, - }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -off, - .operand = operand, + .operand = @intCast(u32, imm), }); _ = try self.addInst(.{ .tag = tag, @@ -3791,7 +3771,7 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M .reg2 = dst_reg.to32(), .flags = 0b10, }), - .data = .{ .imm = @truncate(u32, imm) }, + .data = .{ .imm = @intCast(u32, imm) }, }); } else { // TODO verify we don't spill and assign to the same register as dst_mcv @@ -4899,13 +4879,7 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u _ = try self.addInst(.{ .tag = .xor, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(cond_reg, abi_size) }), - .data = .{ .imm = switch (abi_size) { - 1 => @truncate(u8, imm), - 2 => @truncate(u16, imm), - 4 => @truncate(u32, imm), - 8 => @truncate(u32, imm), - else => unreachable, - } }, + .data = .{ .imm = @intCast(u32, imm) }, }); }, .register => |reg| { @@ -5416,12 +5390,6 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE // We have a positive stack offset value but we want a twos complement negative // offset from rbp, which is at the top of the stack frame. // mov [rbp+offset], immediate - const operand = switch (abi_size) { - 1 => @truncate(u8, imm), - 2 => @truncate(u16, imm), - 4 => @truncate(u32, imm), - else => unreachable, - }; const flags: u2 = switch (abi_size) { 1 => 0b00, 2 => 0b01, @@ -5430,7 +5398,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = operand, + .operand = @intCast(u32, imm), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5575,7 +5543,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl assert(ty.isError()); const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = @truncate(u8, x_big), + .operand = @intCast(u32, x_big), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5587,15 +5555,9 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl }); }, 1, 2, 4 => { - const operand = switch (abi_size) { - 1 => @truncate(u8, x_big), - 2 => @truncate(u16, x_big), - 4 => @truncate(u32, x_big), - else => unreachable, - }; const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = operand, + .operand = @intCast(u32, x_big), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5724,7 +5686,7 @@ fn genInlineMemcpyRegisterRegister( src_reg: Register, offset: i32, ) InnerError!void { - assert(dst_reg.size() == 64); + assert(dst_reg.bitSize() == 64); const dst_reg_lock = self.register_manager.lockReg(dst_reg); defer if (dst_reg_lock) |lock| self.register_manager.unlockReg(lock); @@ -5823,7 +5785,7 @@ fn genInlineMemcpy( _ = try self.addInst(.{ .tag = .mov, .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_addr_reg, @divExact(reg.size(), 8)), + .reg1 = registerAlias(dst_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), .reg2 = reg, }), .data = undefined, @@ -5852,7 +5814,7 @@ fn genInlineMemcpy( _ = try self.addInst(.{ .tag = .mov, .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(src_addr_reg, @divExact(reg.size(), 8)), + .reg1 = registerAlias(src_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), .reg2 = reg, }), .data = undefined, @@ -5976,7 +5938,7 @@ fn genInlineMemset( _ = try self.addInst(.{ .tag = .mov, .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(addr_reg, @divExact(reg.size(), 8)), + .reg1 = registerAlias(addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), .reg2 = reg, }), .data = undefined, @@ -5994,8 +5956,11 @@ fn genInlineMemset( // cmp index_reg, -1 const loop_start = try self.addInst(.{ .tag = .cmp, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - .data = .{ .imm = @bitCast(u8, @as(i8, -1)) }, + .ops = Mir.Inst.Ops.encode(.{ + .reg1 = index_reg, + .flags = 0b11, + }), + .data = .{ .imm_s = -1 }, }); // je end @@ -6016,8 +5981,14 @@ fn genInlineMemset( // mov byte ptr [rbp + index_reg + stack_offset], imm _ = try self.addInst(.{ .tag = .mov_mem_index_imm, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = addr_reg }), - .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDispImm.encode(index_reg, 0, @truncate(u32, x))) }, + .ops = Mir.Inst.Ops.encode(.{ + .reg1 = addr_reg, + }), + .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDispImm.encode( + index_reg, + 0, + @intCast(u32, x), + )) }, }); }, else => return self.fail("TODO inline memset for value of type {}", .{value}), @@ -6064,7 +6035,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (!self.wantSafety()) return; // The already existing value will do just fine. // Write the debug undefined value. - switch (registerAlias(reg, abi_size).size()) { + switch (registerAlias(reg, abi_size).bitSize()) { 8 => return self.genSetReg(ty, reg, .{ .immediate = 0xaa }), 16 => return self.genSetReg(ty, reg, .{ .immediate = 0xaaaa }), 32 => return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaa }), @@ -6100,13 +6071,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void _ = try self.addInst(.{ .tag = .mov, .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }), - .data = .{ .imm = switch (abi_size) { - 1 => @truncate(u8, x), - 2 => @truncate(u16, x), - 4 => @truncate(u32, x), - 8 => @truncate(u32, x), - else => unreachable, - } }, + .data = .{ .imm = @intCast(u32, x) }, }); return; } @@ -6273,13 +6238,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .reg1 = registerAlias(reg, abi_size), .flags = 0b01, }), - .data = .{ .imm = switch (abi_size) { - 1 => @truncate(u8, x), - 2 => @truncate(u16, x), - 4 => @truncate(u32, x), - 8 => @truncate(u32, x), - else => unreachable, - } }, + .data = .{ .disp = @intCast(i32, x) }, }); } else { // If this is RAX, we can use a direct load. @@ -6949,7 +6908,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues { if (ret_ty_size == 0) { assert(ret_ty.isError()); result.return_value = .{ .immediate = 0 }; - } else if (ret_ty_size <= 8) { + } else if (ret_ty_size <= 8 and !ret_ty.isRuntimeFloat()) { const aliased_reg = registerAlias(abi.getCAbiIntReturnRegs(self.target.*)[0], ret_ty_size); result.return_value = .{ .register = aliased_reg }; } else { @@ -7024,28 +6983,34 @@ fn parseRegName(name: []const u8) ?Register { /// Returns register wide enough to hold at least `size_bytes`. fn registerAlias(reg: Register, size_bytes: u32) Register { - if (size_bytes == 0) { - unreachable; // should be comptime-known - } else if (size_bytes <= 1) { - return reg.to8(); - } else if (size_bytes <= 2) { - return reg.to16(); - } else if (size_bytes <= 4) { - return reg.to32(); - } else if (size_bytes <= 8) { - return reg.to64(); - } else if (size_bytes <= 16) { - return reg.to128(); - } else if (size_bytes <= 32) { - return reg.to256(); - } else unreachable; + return switch (reg.class()) { + .general_purpose => if (size_bytes == 0) + unreachable // should be comptime-known + else if (size_bytes <= 1) + reg.to8() + else if (size_bytes <= 2) + reg.to16() + else if (size_bytes <= 4) + reg.to32() + else if (size_bytes <= 8) + reg.to64() + else + unreachable, + .floating_point => if (size_bytes <= 16) + reg.to128() + else if (size_bytes <= 32) + reg.to256() + else + unreachable, + .segment => unreachable, + }; } /// Truncates the value in the register in place. /// Clobbers any remaining bits. fn truncateRegister(self: *Self, ty: Type, reg: Register) !void { const int_info = ty.intInfo(self.target.*); - const max_reg_bit_width = Register.rax.size(); + const max_reg_bit_width = Register.rax.bitSize(); switch (int_info.signedness) { .signed => { const shift = @intCast(u6, max_reg_bit_width - int_info.bits); diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 8bc182d773..e23b500fb8 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -21,6 +21,7 @@ const CodeGen = @import("CodeGen.zig"); const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; const Encoder = bits.Encoder; const ErrorMsg = Module.ErrorMsg; +const Immediate = bits.Immediate; const Instruction = encoder.Instruction; const MCValue = @import("CodeGen.zig").MCValue; const Memory = bits.Memory; @@ -283,7 +284,7 @@ fn mirPushPop(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) 0b10 => { const imm = emit.mir.instructions.items(.data)[inst].imm; return emit.encode(.push, .{ - .op1 = .{ .imm = imm }, + .op1 = .{ .imm = Immediate.u(imm) }, }); }, 0b11 => unreachable, @@ -327,9 +328,7 @@ fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) const target = emit.mir.instructions.items(.data)[inst].inst; const source = emit.code.items.len; try emit.encode(mnemonic, .{ - .op1 = .{ - .imm = 0, - }, + .op1 = .{ .imm = Immediate.s(0) }, }); try emit.relocs.append(emit.bin_file.allocator, .{ .source = source, @@ -400,7 +399,7 @@ fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { }; const source = emit.code.items.len; try emit.encode(mnemonic, .{ - .op1 = .{ .imm = 0 }, + .op1 = .{ .imm = Immediate.s(0) }, }); try emit.relocs.append(emit.bin_file.allocator, .{ .source = source, @@ -521,7 +520,7 @@ fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const imm = emit.mir.instructions.items(.data)[inst].imm; return emit.encode(.@"test", .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = imm }, + .op2 = .{ .imm = Immediate.u(imm) }, }); } return emit.encode(.@"test", .{ @@ -543,7 +542,7 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { 0b10 => { const imm = emit.mir.instructions.items(.data)[inst].imm; return emit.encode(.ret, .{ - .op1 = .{ .imm = imm }, + .op1 = .{ .imm = Immediate.u(imm) }, }); }, 0b11 => { @@ -560,7 +559,7 @@ fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) I const imm = emit.mir.instructions.items(.data)[inst].imm; return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = imm }, + .op2 = .{ .imm = Immediate.u(imm) }, }); } return emit.encode(mnemonic, .{ @@ -573,7 +572,7 @@ fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) I const base: ?Register = if (ops.reg2 != .none) ops.reg2 else null; return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ .base = base, .disp = disp, }) }, @@ -585,7 +584,7 @@ fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) I } const disp = emit.mir.instructions.items(.data)[inst].disp; return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg2.bitSize()), .{ .base = ops.reg1, .disp = disp, }) }, @@ -593,7 +592,11 @@ fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) I }); }, 0b11 => { - return emit.fail("TODO unused variant: mov reg1, reg2, 0b11", .{}); + const imm_s = emit.mir.instructions.items(.data)[inst].imm_s; + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = ops.reg1 }, + .op2 = .{ .imm = Immediate.s(imm_s) }, + }); }, } } @@ -614,7 +617,7 @@ fn mirArithMemImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.In .disp = imm_pair.dest_off, .base = ops.reg1, }) }, - .op2 = .{ .imm = imm_pair.operand }, + .op2 = .{ .imm = Immediate.u(imm_pair.operand) }, }); } @@ -629,7 +632,7 @@ fn mirArithScaleSrc(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst. }; return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ .base = ops.reg2, .scale_index = scale_index, .disp = index_reg_disp.disp, @@ -648,7 +651,7 @@ fn mirArithScaleDst(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst. }; assert(ops.reg2 != .none); return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg2.bitSize()), .{ .base = ops.reg1, .scale_index = scale_index, .disp = index_reg_disp.disp, @@ -672,7 +675,7 @@ fn mirArithScaleImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst. .disp = index_reg_disp_imm.disp, .scale_index = scale_index, }) }, - .op2 = .{ .imm = index_reg_disp_imm.imm }, + .op2 = .{ .imm = Immediate.u(index_reg_disp_imm.imm) }, }); } @@ -697,7 +700,7 @@ fn mirArithMemIndexImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.In .base = ops.reg1, .scale_index = scale_index, }) }, - .op2 = .{ .imm = index_reg_disp_imm.imm }, + .op2 = .{ .imm = Immediate.u(index_reg_disp_imm.imm) }, }); } @@ -708,7 +711,7 @@ fn mirMovSignExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const disp = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].disp else undefined; switch (ops.flags) { 0b00 => { - const mnemonic: Instruction.Mnemonic = if (ops.reg2.size() == 32) .movsxd else .movsx; + const mnemonic: Instruction.Mnemonic = if (ops.reg2.bitSize() == 32) .movsxd else .movsx; return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, .op2 = .{ .reg = ops.reg2 }, @@ -718,14 +721,15 @@ fn mirMovSignExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ptr_size: Memory.PtrSize = switch (ops.flags) { 0b01 => .byte, 0b10 => .word, - 0b11 => .qword, + 0b11 => .dword, else => unreachable, }; - return emit.encode(.movsx, .{ + const mnemonic: Instruction.Mnemonic = if (ops.flags == 0b11) .movsxd else .movsx; + return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, .op2 = .{ .mem = Memory.sib(ptr_size, .{ - .disp = disp, .base = ops.reg2, + .disp = disp, }) }, }); }, @@ -770,19 +774,19 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst].decode(); switch (ops.flags) { 0b00 => { - const imm: u64 = if (ops.reg1.size() == 64) blk: { + const imm: u64 = if (ops.reg1.bitSize() == 64) blk: { const payload = emit.mir.instructions.items(.data)[inst].payload; const imm = emit.mir.extraData(Mir.Imm64, payload).data; break :blk imm.decode(); } else emit.mir.instructions.items(.data)[inst].imm; return emit.encode(.mov, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = imm }, + .op2 = .{ .imm = Immediate.u(imm) }, }); }, 0b01 => { if (ops.reg1 == .none) { - const imm: u64 = if (ops.reg2.size() == 64) blk: { + const imm: u64 = if (ops.reg2.bitSize() == 64) blk: { const payload = emit.mir.instructions.items(.data)[inst].payload; const imm = emit.mir.extraData(Mir.Imm64, payload).data; break :blk imm.decode(); @@ -792,7 +796,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .op2 = .{ .reg = .rax }, }); } - const imm: u64 = if (ops.reg1.size() == 64) blk: { + const imm: u64 = if (ops.reg1.bitSize() == 64) blk: { const payload = emit.mir.instructions.items(.data)[inst].payload; const imm = emit.mir.extraData(Mir.Imm64, payload).data; break :blk imm.decode(); @@ -847,7 +851,7 @@ fn mirShift(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) I 0b00 => { return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = 1 }, + .op2 = .{ .imm = Immediate.u(1) }, }); }, 0b01 => { @@ -860,7 +864,7 @@ fn mirShift(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) I const imm = @truncate(u8, emit.mir.instructions.items(.data)[inst].imm); return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = imm }, + .op2 = .{ .imm = Immediate.u(imm) }, }); }, 0b11 => { @@ -920,7 +924,7 @@ fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { return emit.encode(.imul, .{ .op1 = .{ .reg = ops.reg1 }, .op2 = .{ .reg = ops.reg2 }, - .op3 = .{ .imm = imm }, + .op3 = .{ .imm = Immediate.u(imm) }, }); }, 0b11 => { @@ -932,7 +936,7 @@ fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .base = ops.reg2, .disp = imm_pair.dest_off, }) }, - .op3 = .{ .imm = imm_pair.operand }, + .op3 = .{ .imm = Immediate.u(imm_pair.operand) }, }); }, } @@ -959,7 +963,7 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; return emit.encode(.lea, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ .base = src_reg, .disp = disp, }) }, @@ -969,7 +973,7 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const start_offset = emit.code.items.len; try emit.encode(.lea, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromSize(ops.reg1.size()), 0) }, + .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, }); const end_offset = emit.code.items.len; // Backpatch the displacement @@ -988,7 +992,7 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { }; return emit.encode(.lea, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ .base = src_reg, .scale_index = scale_index, .disp = index_reg_disp.disp, @@ -1012,7 +1016,7 @@ fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { try emit.encode(.lea, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromSize(ops.reg1.size()), 0) }, + .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, }); const end_offset = emit.code.items.len; @@ -1065,7 +1069,7 @@ fn mirMovFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index const disp = emit.mir.instructions.items(.data)[inst].disp; return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg2.size()), .{ + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg2.bitSize()), .{ .base = ops.reg2, .disp = disp, }) }, @@ -1074,7 +1078,7 @@ fn mirMovFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index 0b01 => { const disp = emit.mir.instructions.items(.data)[inst].disp; return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromSize(ops.reg1.size()), .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ .base = ops.reg1, .disp = disp, }) }, @@ -1127,7 +1131,7 @@ fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const offset = blk: { // callq try emit.encode(.call, .{ - .op1 = .{ .imm = 0 }, + .op1 = .{ .imm = Immediate.s(0) }, }); break :blk @intCast(u32, emit.code.items.len) - 4; }; diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 7cf8910924..5d3944a554 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -144,20 +144,20 @@ pub fn findByOpcode(opc: []const u8, prefixes: struct { // rex.W mov dil, 0x1 // Here, rex.W is not needed. const rex_w_allowed = blk: { - const bit_size = enc.operandSize(); + const bit_size = enc.operandBitSize(); break :blk bit_size == 64 or bit_size == 8; }; if (rex_w_allowed) return enc; }, } } else if (prefixes.legacy.prefix_66) { - switch (enc.operandSize()) { + switch (enc.operandBitSize()) { 16 => return enc, else => {}, } } else { if (enc.mode == .none) { - switch (enc.operandSize()) { + switch (enc.operandBitSize()) { 16 => {}, else => return enc, } @@ -187,17 +187,17 @@ pub fn modRmExt(encoding: Encoding) u3 { }; } -pub fn operandSize(encoding: Encoding) u32 { +pub fn operandBitSize(encoding: Encoding) u64 { if (encoding.mode == .long) return 64; - const bit_size: u32 = switch (encoding.op_en) { + const bit_size: u64 = switch (encoding.op_en) { .np => switch (encoding.op1) { .o16 => 16, .o32 => 32, .o64 => 64, else => 32, }, - .td => encoding.op2.size(), - else => encoding.op1.size(), + .td => encoding.op2.bitSize(), + else => encoding.op1.bitSize(), }; return bit_size; } @@ -244,9 +244,9 @@ pub fn format( else => unreachable, }; const tag = switch (op) { - .imm8 => "ib", - .imm16 => "iw", - .imm32 => "id", + .imm8, .imm8s => "ib", + .imm16, .imm16s => "iw", + .imm32, .imm32s => "id", .imm64 => "io", .rel8 => "cb", .rel16 => "cw", @@ -330,6 +330,7 @@ pub const Op = enum { o16, o32, o64, unity, imm8, imm16, imm32, imm64, + imm8s, imm16s, imm32s, al, ax, eax, rax, cl, r8, r16, r32, r64, @@ -349,7 +350,7 @@ pub const Op = enum { .reg => |reg| { switch (reg.class()) { .segment => return .sreg, - .floating_point => return switch (reg.size()) { + .floating_point => return switch (reg.bitSize()) { 128 => .xmm, else => unreachable, }, @@ -362,7 +363,7 @@ pub const Op = enum { else => unreachable, }; if (reg == .cl) return .cl; - return switch (reg.size()) { + return switch (reg.bitSize()) { 8 => .r8, 16 => .r16, 32 => .r32, @@ -376,7 +377,7 @@ pub const Op = enum { .mem => |mem| switch (mem) { .moffs => return .moffs, .sib, .rip => { - const bit_size = mem.size(); + const bit_size = mem.bitSize(); return switch (bit_size) { 8 => .m8, 16 => .m16, @@ -389,21 +390,34 @@ pub const Op = enum { }, .imm => |imm| { - if (imm == 1) return .unity; - if (math.cast(u8, imm)) |_| return .imm8; - if (math.cast(u16, imm)) |_| return .imm16; - if (math.cast(u32, imm)) |_| return .imm32; - return .imm64; + switch (imm) { + .signed => |x| { + if (x == 1) return .unity; + if (math.cast(i8, x)) |_| return .imm8s; + if (math.cast(i16, x)) |_| return .imm16s; + return .imm32s; + }, + .unsigned => |x| { + if (x == 1) return .unity; + if (math.cast(i8, x)) |_| return .imm8s; + if (math.cast(u8, x)) |_| return .imm8; + if (math.cast(i16, x)) |_| return .imm16s; + if (math.cast(u16, x)) |_| return .imm16; + if (math.cast(i32, x)) |_| return .imm32s; + if (math.cast(u32, x)) |_| return .imm32; + return .imm64; + }, + } }, } } - pub fn size(op: Op) u32 { + pub fn bitSize(op: Op) u64 { return switch (op) { - .none, .o16, .o32, .o64, .moffs, .m, .sreg, .unity => unreachable, - .imm8, .al, .cl, .r8, .m8, .rm8, .rel8 => 8, - .imm16, .ax, .r16, .m16, .rm16, .rel16 => 16, - .imm32, .eax, .r32, .m32, .rm32, .rel32, .xmm_m32 => 32, + .none, .o16, .o32, .o64, .moffs, .m, .sreg => unreachable, + .unity, .imm8, .imm8s, .al, .cl, .r8, .m8, .rm8, .rel8 => 8, + .imm16, .imm16s, .ax, .r16, .m16, .rm16, .rel16 => 16, + .imm32, .imm32s, .eax, .r32, .m32, .rm32, .rel32, .xmm_m32 => 32, .imm64, .rax, .r64, .m64, .rm64, .xmm_m64 => 64, .m80 => 80, .xmm => 128, @@ -428,6 +442,7 @@ pub const Op = enum { // zig fmt: off return switch (op) { .imm8, .imm16, .imm32, .imm64, + .imm8s, .imm16s, .imm32s, .rel8, .rel16, .rel32, .unity, => true, @@ -479,28 +494,40 @@ pub const Op = enum { .sse, .sse2 => return op.isFloatingPointRegister() and target.isFloatingPointRegister(), else => switch (target) { .cl, .al, .ax, .eax, .rax => return op == target, - else => return op.size() == target.size(), + else => return op.bitSize() == target.bitSize(), }, } } if (op.isMemory() and target.isMemory()) { switch (target) { .m => return true, - else => return op.size() == target.size(), + else => return op.bitSize() == target.bitSize(), } } if (op.isImmediate() and target.isImmediate()) { switch (target) { - .imm32, .rel32 => switch (op) { - .unity, .imm8, .imm16, .imm32 => return true, + .imm32s, .rel32 => switch (op) { + .unity, .imm8s, .imm8, .imm16s, .imm16, .imm32s => return true, else => return op == target, }, - .imm16, .rel16 => switch (op) { - .unity, .imm8, .imm16 => return true, + .imm32 => switch (op) { + .unity, .imm8, .imm8s, .imm16, .imm16s, .imm32, .imm32s => return true, else => return op == target, }, - .imm8, .rel8 => switch (op) { - .unity, .imm8 => return true, + .imm16s, .rel16 => switch (op) { + .unity, .imm8s, .imm8, .imm16s => return true, + else => return op == target, + }, + .imm16 => switch (op) { + .unity, .imm8, .imm8s, .imm16, .imm16s => return true, + else => return op == target, + }, + .imm8s, .rel8 => switch (op) { + .unity, .imm8s => return true, + else => return op == target, + }, + .imm8 => switch (op) { + .unity, .imm8, .imm8s => return true, else => return op == target, }, else => return op == target, diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 4124592627..43c24216a8 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -34,6 +34,7 @@ pub const Inst = struct { /// 0b01 reg1, [reg2 + imm32] /// 0b01 reg1, [ds:imm32] /// 0b10 [reg1 + imm32], reg2 + /// 0b11 reg1, imm_s /// Notes: /// * If reg2 is `none` then it means Data field `imm` is used as the immediate. /// * When two imm32 values are required, Data field `payload` points at `ImmPair`. @@ -421,6 +422,8 @@ pub const Inst = struct { inst: Index, /// A 32-bit immediate value. imm: u32, + /// A 32-bit signed immediate value. + imm_s: i32, /// A 32-bit signed displacement value. disp: i32, /// A condition code for use with EFLAGS register. diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index 9166550f16..cd7ed91849 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -207,7 +207,7 @@ pub const Register = enum(u7) { return @intCast(u6, @enumToInt(reg) - base); } - pub fn size(reg: Register) u32 { + pub fn bitSize(reg: Register) u64 { return switch (@enumToInt(reg)) { // zig fmt: off @enumToInt(Register.rax) ... @enumToInt(Register.r15) => 64, @@ -273,7 +273,7 @@ pub const Register = enum(u7) { return @truncate(u3, reg.enc()); } - pub fn toSize(reg: Register, bit_size: u32) Register { + pub fn toBitSize(reg: Register, bit_size: u64) Register { return switch (bit_size) { 8 => reg.to8(), 16 => reg.to16(), @@ -334,7 +334,17 @@ pub const Register = enum(u7) { pub fn dwarfLocOp(reg: Register) u8 { return switch (reg.class()) { - .general_purpose => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.reg0, + .general_purpose => switch (reg.to64()) { + .rax => DW.OP.reg0, + .rdx => DW.OP.reg1, + .rcx => DW.OP.reg2, + .rbx => DW.OP.reg3, + .rsi => DW.OP.reg4, + .rdi => DW.OP.reg5, + .rbp => DW.OP.reg6, + .rsp => DW.OP.reg7, + else => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.reg0, + }, .floating_point => @intCast(u8, @enumToInt(reg) - reg.fpBase()) + DW.OP.reg17, else => unreachable, }; @@ -345,7 +355,17 @@ pub const Register = enum(u7) { /// register to a given signed offset. pub fn dwarfLocOpDeref(reg: Register) u8 { return switch (reg.class()) { - .general_purpose => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.breg0, + .general_purpose => switch (reg.to64()) { + .rax => DW.OP.breg0, + .rdx => DW.OP.breg1, + .rcx => DW.OP.breg2, + .rbx => DW.OP.breg3, + .rsi => DW.OP.breg4, + .rdi => DW.OP.breg5, + .rbp => DW.OP.breg6, + .rsp => DW.OP.breg7, + else => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.breg0, + }, .floating_point => @intCast(u8, @enumToInt(reg) - reg.fpBase()) + DW.OP.breg17, else => unreachable, }; @@ -397,7 +417,7 @@ pub const Memory = union(enum) { qword, tbyte, - pub fn fromSize(bit_size: u32) PtrSize { + pub fn fromBitSize(bit_size: u64) PtrSize { return switch (bit_size) { 8 => .byte, 16 => .word, @@ -408,7 +428,7 @@ pub const Memory = union(enum) { }; } - pub fn size(s: PtrSize) u32 { + pub fn bitSize(s: PtrSize) u64 { return switch (s) { .byte => 8, .word => 16, @@ -481,11 +501,42 @@ pub const Memory = union(enum) { }; } - pub fn size(mem: Memory) u32 { + pub fn bitSize(mem: Memory) u64 { return switch (mem) { - .rip => |r| r.ptr_size.size(), - .sib => |s| s.ptr_size.size(), + .rip => |r| r.ptr_size.bitSize(), + .sib => |s| s.ptr_size.bitSize(), .moffs => unreachable, }; } }; + +pub const Immediate = union(enum) { + signed: i32, + unsigned: u64, + + pub fn u(x: u64) Immediate { + return .{ .unsigned = x }; + } + + pub fn s(x: i32) Immediate { + return .{ .signed = x }; + } + + pub fn asUnsigned(imm: Immediate, bit_size: u64) u64 { + return switch (imm) { + .signed => |x| switch (bit_size) { + 8 => @bitCast(u8, @intCast(i8, x)), + 16 => @bitCast(u16, @intCast(i16, x)), + 32 => @bitCast(u32, @intCast(i32, x)), + else => unreachable, + }, + .unsigned => |x| switch (bit_size) { + 8 => @intCast(u8, x), + 16 => @intCast(u16, x), + 32 => @intCast(u32, x), + 64 => x, + else => unreachable, + }, + }; + } +}; diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index eefc7fd6e2..d4ca925891 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -4,6 +4,7 @@ const math = std.math; const bits = @import("bits.zig"); const Encoding = @import("Encoding.zig"); +const Immediate = bits.Immediate; const Memory = bits.Memory; const Moffs = bits.Moffs; const PtrSize = bits.PtrSize; @@ -22,15 +23,14 @@ pub const Instruction = struct { none, reg: Register, mem: Memory, - imm: u64, + imm: Immediate, /// Returns the bitsize of the operand. - /// Asserts the operand is either register or memory. - pub fn size(op: Operand) u64 { + pub fn bitSize(op: Operand) u64 { return switch (op) { .none => unreachable, - .reg => |reg| reg.size(), - .mem => |mem| mem.size(), + .reg => |reg| reg.bitSize(), + .mem => |mem| mem.bitSize(), .imm => unreachable, }; } @@ -47,7 +47,6 @@ pub const Instruction = struct { } pub fn fmtPrint(op: Operand, enc_op: Encoding.Op, writer: anytype) !void { - _ = enc_op; switch (op) { .none => {}, .reg => |reg| try writer.writeAll(@tagName(reg)), @@ -92,9 +91,7 @@ pub const Instruction = struct { }, .moffs => |moffs| try writer.print("{s}:0x{x}", .{ @tagName(moffs.seg), moffs.offset }), }, - .imm => |imm| { - try writer.print("0x{x}", .{imm}); - }, + .imm => |imm| try writer.print("0x{x}", .{imm.asUnsigned(enc_op.bitSize())}), } } }; @@ -110,8 +107,17 @@ pub const Instruction = struct { .op2 = args.op2, .op3 = args.op3, .op4 = args.op4, - }) orelse return error.InvalidInstruction; - std.log.warn("{}", .{encoding}); + }) orelse { + std.log.debug("{s} {s} {s} {s} {s}", .{ + @tagName(mnemonic), + @tagName(Encoding.Op.fromOperand(args.op1)), + @tagName(Encoding.Op.fromOperand(args.op2)), + @tagName(Encoding.Op.fromOperand(args.op3)), + @tagName(Encoding.Op.fromOperand(args.op4)), + }); + return error.InvalidInstruction; + }; + std.log.debug("{}", .{encoding}); return .{ .op1 = args.op1, .op2 = args.op2, @@ -210,7 +216,7 @@ pub const Instruction = struct { var legacy = LegacyPrefixes{}; if (enc.mode == .none) { - const bit_size = enc.operandSize(); + const bit_size = enc.operandBitSize(); if (bit_size == 16) { legacy.set16BitOverride(); } @@ -380,12 +386,13 @@ pub const Instruction = struct { } } - fn encodeImm(imm: u64, kind: Encoding.Op, encoder: anytype) !void { - switch (kind) { - .imm8, .rel8 => try encoder.imm8(@bitCast(i8, @truncate(u8, imm))), - .imm16, .rel16 => try encoder.imm16(@bitCast(i16, @truncate(u16, imm))), - .imm32, .rel32 => try encoder.imm32(@bitCast(i32, @truncate(u32, imm))), - .imm64 => try encoder.imm64(imm), + fn encodeImm(imm: Immediate, kind: Encoding.Op, encoder: anytype) !void { + const raw = imm.asUnsigned(kind.bitSize()); + switch (kind.bitSize()) { + 8 => try encoder.imm8(@intCast(u8, raw)), + 16 => try encoder.imm16(@intCast(u16, raw)), + 32 => try encoder.imm32(@intCast(u32, raw)), + 64 => try encoder.imm64(raw), else => unreachable, } } @@ -732,13 +739,6 @@ fn Encoder(comptime T: type) type { // Trivial (no bit fiddling) // ------------------------- - /// Encode an 8 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm8(self: Self, imm: i8) !void { - try self.writer.writeByte(@bitCast(u8, imm)); - } - /// Encode an 8 bit displacement /// /// It is sign-extended to 64 bits by the cpu. @@ -746,20 +746,6 @@ fn Encoder(comptime T: type) type { try self.writer.writeByte(@bitCast(u8, disp)); } - /// Encode an 16 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm16(self: Self, imm: i16) !void { - try self.writer.writeIntLittle(i16, imm); - } - - /// Encode an 32 bit immediate - /// - /// It is sign-extended to 64 bits by the cpu. - pub fn imm32(self: Self, imm: i32) !void { - try self.writer.writeIntLittle(i32, imm); - } - /// Encode an 32 bit displacement /// /// It is sign-extended to 64 bits by the cpu. @@ -767,6 +753,27 @@ fn Encoder(comptime T: type) type { try self.writer.writeIntLittle(i32, disp); } + /// Encode an 8 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm8(self: Self, imm: u8) !void { + try self.writer.writeByte(imm); + } + + /// Encode an 16 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm16(self: Self, imm: u16) !void { + try self.writer.writeIntLittle(u16, imm); + } + + /// Encode an 32 bit immediate + /// + /// It is sign-extended to 64 bits by the cpu. + pub fn imm32(self: Self, imm: u32) !void { + try self.writer.writeIntLittle(u32, imm); + } + /// Encode an 64 bit immediate /// /// It is sign-extended to 64 bits by the cpu. diff --git a/src/arch/x86_64/encodings.zig b/src/arch/x86_64/encodings.zig index b7099d2ad2..c3fb347f22 100644 --- a/src/arch/x86_64/encodings.zig +++ b/src/arch/x86_64/encodings.zig @@ -13,65 +13,65 @@ const Entry = struct { Mnemonic, OpEn, Op, Op, Op, Op, opcode_len, u8, u8, u8, m // zig fmt: off pub const table = &[_]Entry{ // General-purpose - .{ .adc, .zi, .al, .imm8, .none, .none, 1, 0x14, 0x00, 0x00, 0, .none }, - .{ .adc, .zi, .ax, .imm16, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, - .{ .adc, .zi, .eax, .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, - .{ .adc, .zi, .rax, .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .long }, - .{ .adc, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 2, .none }, - .{ .adc, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, - .{ .adc, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, - .{ .adc, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .long }, - .{ .adc, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, - .{ .adc, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, - .{ .adc, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 2, .long }, - .{ .adc, .mr, .rm8, .r8, .none, .none, 1, 0x10, 0x00, 0x00, 0, .none }, - .{ .adc, .mr, .rm16, .r16, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, - .{ .adc, .mr, .rm32, .r32, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, - .{ .adc, .mr, .rm64, .r64, .none, .none, 1, 0x11, 0x00, 0x00, 0, .long }, - .{ .adc, .rm, .r8, .rm8, .none, .none, 1, 0x12, 0x00, 0x00, 0, .none }, - .{ .adc, .rm, .r16, .rm16, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, - .{ .adc, .rm, .r32, .rm32, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, - .{ .adc, .rm, .r64, .rm64, .none, .none, 1, 0x13, 0x00, 0x00, 0, .long }, + .{ .adc, .zi, .al, .imm8, .none, .none, 1, 0x14, 0x00, 0x00, 0, .none }, + .{ .adc, .zi, .ax, .imm16, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, + .{ .adc, .zi, .eax, .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, + .{ .adc, .zi, .rax, .imm32s, .none, .none, 1, 0x15, 0x00, 0x00, 0, .long }, + .{ .adc, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 2, .long }, + .{ .adc, .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 2, .long }, + .{ .adc, .mr, .rm8, .r8, .none, .none, 1, 0x10, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm16, .r16, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm32, .r32, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm64, .r64, .none, .none, 1, 0x11, 0x00, 0x00, 0, .long }, + .{ .adc, .rm, .r8, .rm8, .none, .none, 1, 0x12, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r16, .rm16, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r32, .rm32, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r64, .rm64, .none, .none, 1, 0x13, 0x00, 0x00, 0, .long }, - .{ .add, .zi, .al, .imm8, .none, .none, 1, 0x04, 0x00, 0x00, 0, .none }, - .{ .add, .zi, .ax, .imm16, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, - .{ .add, .zi, .eax, .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, - .{ .add, .zi, .rax, .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .long }, - .{ .add, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 0, .none }, - .{ .add, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, - .{ .add, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, - .{ .add, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .long }, - .{ .add, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, - .{ .add, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, - .{ .add, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 0, .long }, - .{ .add, .mr, .rm8, .r8, .none, .none, 1, 0x00, 0x00, 0x00, 0, .none }, - .{ .add, .mr, .rm16, .r16, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, - .{ .add, .mr, .rm32, .r32, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, - .{ .add, .mr, .rm64, .r64, .none, .none, 1, 0x01, 0x00, 0x00, 0, .long }, - .{ .add, .rm, .r8, .rm8, .none, .none, 1, 0x02, 0x00, 0x00, 0, .none }, - .{ .add, .rm, .r16, .rm16, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, - .{ .add, .rm, .r32, .rm32, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, - .{ .add, .rm, .r64, .rm64, .none, .none, 1, 0x03, 0x00, 0x00, 0, .long }, + .{ .add, .zi, .al, .imm8, .none, .none, 1, 0x04, 0x00, 0x00, 0, .none }, + .{ .add, .zi, .ax, .imm16, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, + .{ .add, .zi, .eax, .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, + .{ .add, .zi, .rax, .imm32s, .none, .none, 1, 0x05, 0x00, 0x00, 0, .long }, + .{ .add, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 0, .long }, + .{ .add, .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 0, .long }, + .{ .add, .mr, .rm8, .r8, .none, .none, 1, 0x00, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm16, .r16, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm32, .r32, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm64, .r64, .none, .none, 1, 0x01, 0x00, 0x00, 0, .long }, + .{ .add, .rm, .r8, .rm8, .none, .none, 1, 0x02, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r16, .rm16, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r32, .rm32, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r64, .rm64, .none, .none, 1, 0x03, 0x00, 0x00, 0, .long }, - .{ .@"and", .zi, .al, .imm8, .none, .none, 1, 0x24, 0x00, 0x00, 0, .none }, - .{ .@"and", .zi, .ax, .imm16, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, - .{ .@"and", .zi, .eax, .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, - .{ .@"and", .zi, .rax, .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .long }, - .{ .@"and", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 4, .none }, - .{ .@"and", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, - .{ .@"and", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, - .{ .@"and", .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .long }, - .{ .@"and", .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, - .{ .@"and", .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, - .{ .@"and", .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 4, .long }, - .{ .@"and", .mr, .rm8, .r8, .none, .none, 1, 0x20, 0x00, 0x00, 0, .none }, - .{ .@"and", .mr, .rm16, .r16, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, - .{ .@"and", .mr, .rm32, .r32, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, - .{ .@"and", .mr, .rm64, .r64, .none, .none, 1, 0x21, 0x00, 0x00, 0, .long }, - .{ .@"and", .rm, .r8, .rm8, .none, .none, 1, 0x22, 0x00, 0x00, 0, .none }, - .{ .@"and", .rm, .r16, .rm16, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, - .{ .@"and", .rm, .r32, .rm32, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, - .{ .@"and", .rm, .r64, .rm64, .none, .none, 1, 0x23, 0x00, 0x00, 0, .long }, + .{ .@"and", .zi, .al, .imm8, .none, .none, 1, 0x24, 0x00, 0x00, 0, .none }, + .{ .@"and", .zi, .ax, .imm16, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, + .{ .@"and", .zi, .eax, .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, + .{ .@"and", .zi, .rax, .imm32s, .none, .none, 1, 0x25, 0x00, 0x00, 0, .long }, + .{ .@"and", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 4, .long }, + .{ .@"and", .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 4, .long }, + .{ .@"and", .mr, .rm8, .r8, .none, .none, 1, 0x20, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm16, .r16, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm32, .r32, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm64, .r64, .none, .none, 1, 0x21, 0x00, 0x00, 0, .long }, + .{ .@"and", .rm, .r8, .rm8, .none, .none, 1, 0x22, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r16, .rm16, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r32, .rm32, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r64, .rm64, .none, .none, 1, 0x23, 0x00, 0x00, 0, .long }, // This is M encoding according to Intel, but D makes more sense here. .{ .call, .d, .rel32, .none, .none, .none, 1, 0xe8, 0x00, 0x00, 0, .none }, @@ -176,25 +176,25 @@ pub const table = &[_]Entry{ .{ .cmovz, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .none }, .{ .cmovz, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0x44, 0x00, 0, .long }, - .{ .cmp, .zi, .al, .imm8, .none, .none, 1, 0x3c, 0x00, 0x00, 0, .none }, - .{ .cmp, .zi, .ax, .imm16, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, - .{ .cmp, .zi, .eax, .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, - .{ .cmp, .zi, .rax, .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .long }, - .{ .cmp, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 7, .none }, - .{ .cmp, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, - .{ .cmp, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, - .{ .cmp, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .long }, - .{ .cmp, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, - .{ .cmp, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, - .{ .cmp, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 7, .long }, - .{ .cmp, .mr, .rm8, .r8, .none, .none, 1, 0x38, 0x00, 0x00, 0, .none }, - .{ .cmp, .mr, .rm16, .r16, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, - .{ .cmp, .mr, .rm32, .r32, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, - .{ .cmp, .mr, .rm64, .r64, .none, .none, 1, 0x39, 0x00, 0x00, 0, .long }, - .{ .cmp, .rm, .r8, .rm8, .none, .none, 1, 0x3a, 0x00, 0x00, 0, .none }, - .{ .cmp, .rm, .r16, .rm16, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, - .{ .cmp, .rm, .r32, .rm32, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, - .{ .cmp, .rm, .r64, .rm64, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .long }, + .{ .cmp, .zi, .al, .imm8, .none, .none, 1, 0x3c, 0x00, 0x00, 0, .none }, + .{ .cmp, .zi, .ax, .imm16, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, + .{ .cmp, .zi, .eax, .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, + .{ .cmp, .zi, .rax, .imm32s, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .long }, + .{ .cmp, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 7, .long }, + .{ .cmp, .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 7, .long }, + .{ .cmp, .mr, .rm8, .r8, .none, .none, 1, 0x38, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm16, .r16, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm32, .r32, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm64, .r64, .none, .none, 1, 0x39, 0x00, 0x00, 0, .long }, + .{ .cmp, .rm, .r8, .rm8, .none, .none, 1, 0x3a, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r16, .rm16, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r32, .rm32, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r64, .rm64, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .long }, .{ .div, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 6, .none }, .{ .div, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .none }, @@ -214,19 +214,19 @@ pub const table = &[_]Entry{ .{ .idiv, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .none }, .{ .idiv, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .long }, - .{ .imul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 5, .none }, - .{ .imul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, - .{ .imul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, - .{ .imul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .long }, - .{ .imul, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .none }, - .{ .imul, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .none }, - .{ .imul, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .long }, - .{ .imul, .rmi, .r16, .rm16, .imm8, .none, 1, 0x6b, 0x00, 0x00, 0, .none }, - .{ .imul, .rmi, .r32, .rm32, .imm8, .none, 1, 0x6b, 0x00, 0x00, 0, .none }, - .{ .imul, .rmi, .r64, .rm64, .imm8, .none, 1, 0x6b, 0x00, 0x00, 0, .long }, - .{ .imul, .rmi, .r16, .rm16, .imm16, .none, 1, 0x69, 0x00, 0x00, 0, .none }, - .{ .imul, .rmi, .r32, .rm32, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .none }, - .{ .imul, .rmi, .r64, .rm64, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .long }, + .{ .imul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .long }, + .{ .imul, .rm, .r16, .rm16, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .none }, + .{ .imul, .rm, .r32, .rm32, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .none }, + .{ .imul, .rm, .r64, .rm64, .none, .none, 2, 0x0f, 0xaf, 0x00, 0, .long }, + .{ .imul, .rmi, .r16, .rm16, .imm8s, .none, 1, 0x6b, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r32, .rm32, .imm8s, .none, 1, 0x6b, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r64, .rm64, .imm8s, .none, 1, 0x6b, 0x00, 0x00, 0, .long }, + .{ .imul, .rmi, .r16, .rm16, .imm16, .none, 1, 0x69, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r32, .rm32, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .none }, + .{ .imul, .rmi, .r64, .rm64, .imm32, .none, 1, 0x69, 0x00, 0x00, 0, .long }, .{ .int3, .np, .none, .none, .none, .none, 1, 0xcc, 0x00, 0x00, 0, .none }, @@ -269,34 +269,34 @@ pub const table = &[_]Entry{ .{ .lea, .rm, .r32, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .none }, .{ .lea, .rm, .r64, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .long }, - .{ .mov, .mr, .rm8, .r8, .none, .none, 1, 0x88, 0x00, 0x00, 0, .none }, - .{ .mov, .mr, .rm16, .r16, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, - .{ .mov, .mr, .rm32, .r32, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, - .{ .mov, .mr, .rm64, .r64, .none, .none, 1, 0x89, 0x00, 0x00, 0, .long }, - .{ .mov, .rm, .r8, .rm8, .none, .none, 1, 0x8a, 0x00, 0x00, 0, .none }, - .{ .mov, .rm, .r16, .rm16, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, - .{ .mov, .rm, .r32, .rm32, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, - .{ .mov, .rm, .r64, .rm64, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .long }, - .{ .mov, .mr, .rm16, .sreg, .none, .none, 1, 0x8c, 0x00, 0x00, 0, .none }, - .{ .mov, .mr, .rm64, .sreg, .none, .none, 1, 0x8c, 0x00, 0x00, 0, .long }, - .{ .mov, .rm, .sreg, .rm16, .none, .none, 1, 0x8e, 0x00, 0x00, 0, .none }, - .{ .mov, .rm, .sreg, .rm64, .none, .none, 1, 0x8e, 0x00, 0x00, 0, .long }, - .{ .mov, .fd, .al, .moffs, .none, .none, 1, 0xa0, 0x00, 0x00, 0, .none }, - .{ .mov, .fd, .ax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none }, - .{ .mov, .fd, .eax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none }, - .{ .mov, .fd, .rax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .long }, - .{ .mov, .td, .moffs, .al, .none, .none, 1, 0xa2, 0x00, 0x00, 0, .none }, - .{ .mov, .td, .moffs, .ax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, - .{ .mov, .td, .moffs, .eax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, - .{ .mov, .td, .moffs, .rax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .long }, - .{ .mov, .oi, .r8, .imm8, .none, .none, 1, 0xb0, 0x00, 0x00, 0, .none }, - .{ .mov, .oi, .r16, .imm16, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, - .{ .mov, .oi, .r32, .imm32, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, - .{ .mov, .oi, .r64, .imm64, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .long }, - .{ .mov, .mi, .rm8, .imm8, .none, .none, 1, 0xc6, 0x00, 0x00, 0, .none }, - .{ .mov, .mi, .rm16, .imm16, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, - .{ .mov, .mi, .rm32, .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, - .{ .mov, .mi, .rm64, .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .long }, + .{ .mov, .mr, .rm8, .r8, .none, .none, 1, 0x88, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm16, .r16, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm32, .r32, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm64, .r64, .none, .none, 1, 0x89, 0x00, 0x00, 0, .long }, + .{ .mov, .rm, .r8, .rm8, .none, .none, 1, 0x8a, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r16, .rm16, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r32, .rm32, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r64, .rm64, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .long }, + .{ .mov, .mr, .rm16, .sreg, .none, .none, 1, 0x8c, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm64, .sreg, .none, .none, 1, 0x8c, 0x00, 0x00, 0, .long }, + .{ .mov, .rm, .sreg, .rm16, .none, .none, 1, 0x8e, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .sreg, .rm64, .none, .none, 1, 0x8e, 0x00, 0x00, 0, .long }, + .{ .mov, .fd, .al, .moffs, .none, .none, 1, 0xa0, 0x00, 0x00, 0, .none }, + .{ .mov, .fd, .ax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none }, + .{ .mov, .fd, .eax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .none }, + .{ .mov, .fd, .rax, .moffs, .none, .none, 1, 0xa1, 0x00, 0x00, 0, .long }, + .{ .mov, .td, .moffs, .al, .none, .none, 1, 0xa2, 0x00, 0x00, 0, .none }, + .{ .mov, .td, .moffs, .ax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, + .{ .mov, .td, .moffs, .eax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, + .{ .mov, .td, .moffs, .rax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .long }, + .{ .mov, .oi, .r8, .imm8, .none, .none, 1, 0xb0, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r16, .imm16, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r32, .imm32, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r64, .imm64, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .long }, + .{ .mov, .mi, .rm8, .imm8, .none, .none, 1, 0xc6, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm16, .imm16, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm32, .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm64, .imm32s, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .long }, .{ .movsx, .rm, .r16, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none }, .{ .movsx, .rm, .r32, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none }, @@ -321,25 +321,25 @@ pub const table = &[_]Entry{ .{ .nop, .np, .none, .none, .none, .none, 1, 0x90, 0x00, 0x00, 0, .none }, - .{ .@"or", .zi, .al, .imm8, .none, .none, 1, 0x0c, 0x00, 0x00, 0, .none }, - .{ .@"or", .zi, .ax, .imm16, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, - .{ .@"or", .zi, .eax, .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, - .{ .@"or", .zi, .rax, .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .long }, - .{ .@"or", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 1, .none }, - .{ .@"or", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, - .{ .@"or", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, - .{ .@"or", .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .long }, - .{ .@"or", .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, - .{ .@"or", .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, - .{ .@"or", .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 1, .long }, - .{ .@"or", .mr, .rm8, .r8, .none, .none, 1, 0x08, 0x00, 0x00, 0, .none }, - .{ .@"or", .mr, .rm16, .r16, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, - .{ .@"or", .mr, .rm32, .r32, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, - .{ .@"or", .mr, .rm64, .r64, .none, .none, 1, 0x09, 0x00, 0x00, 0, .long }, - .{ .@"or", .rm, .r8, .rm8, .none, .none, 1, 0x0a, 0x00, 0x00, 0, .none }, - .{ .@"or", .rm, .r16, .rm16, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, - .{ .@"or", .rm, .r32, .rm32, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, - .{ .@"or", .rm, .r64, .rm64, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .long }, + .{ .@"or", .zi, .al, .imm8, .none, .none, 1, 0x0c, 0x00, 0x00, 0, .none }, + .{ .@"or", .zi, .ax, .imm16, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, + .{ .@"or", .zi, .eax, .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, + .{ .@"or", .zi, .rax, .imm32s, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .long }, + .{ .@"or", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 1, .long }, + .{ .@"or", .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 1, .long }, + .{ .@"or", .mr, .rm8, .r8, .none, .none, 1, 0x08, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm16, .r16, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm32, .r32, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm64, .r64, .none, .none, 1, 0x09, 0x00, 0x00, 0, .long }, + .{ .@"or", .rm, .r8, .rm8, .none, .none, 1, 0x0a, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r16, .rm16, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r32, .rm32, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r64, .rm64, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .long }, .{ .pop, .o, .r16, .none, .none, .none, 1, 0x58, 0x00, 0x00, 0, .none }, .{ .pop, .o, .r64, .none, .none, .none, 1, 0x58, 0x00, 0x00, 0, .none }, @@ -382,25 +382,25 @@ pub const table = &[_]Entry{ .{ .sar, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .none }, .{ .sar, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .long }, - .{ .sbb, .zi, .al, .imm8, .none, .none, 1, 0x1c, 0x00, 0x00, 0, .none }, - .{ .sbb, .zi, .ax, .imm16, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, - .{ .sbb, .zi, .eax, .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, - .{ .sbb, .zi, .rax, .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .long }, - .{ .sbb, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 3, .none }, - .{ .sbb, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, - .{ .sbb, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, - .{ .sbb, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .long }, - .{ .sbb, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, - .{ .sbb, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, - .{ .sbb, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 3, .long }, - .{ .sbb, .mr, .rm8, .r8, .none, .none, 1, 0x18, 0x00, 0x00, 0, .none }, - .{ .sbb, .mr, .rm16, .r16, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, - .{ .sbb, .mr, .rm32, .r32, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, - .{ .sbb, .mr, .rm64, .r64, .none, .none, 1, 0x19, 0x00, 0x00, 0, .long }, - .{ .sbb, .rm, .r8, .rm8, .none, .none, 1, 0x1a, 0x00, 0x00, 0, .none }, - .{ .sbb, .rm, .r16, .rm16, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, - .{ .sbb, .rm, .r32, .rm32, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, - .{ .sbb, .rm, .r64, .rm64, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .long }, + .{ .sbb, .zi, .al, .imm8, .none, .none, 1, 0x1c, 0x00, 0x00, 0, .none }, + .{ .sbb, .zi, .ax, .imm16, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, + .{ .sbb, .zi, .eax, .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, + .{ .sbb, .zi, .rax, .imm32s, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .long }, + .{ .sbb, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 3, .long }, + .{ .sbb, .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 3, .long }, + .{ .sbb, .mr, .rm8, .r8, .none, .none, 1, 0x18, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm16, .r16, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm32, .r32, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm64, .r64, .none, .none, 1, 0x19, 0x00, 0x00, 0, .long }, + .{ .sbb, .rm, .r8, .rm8, .none, .none, 1, 0x1a, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r16, .rm16, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r32, .rm32, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r64, .rm64, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .long }, .{ .seta, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .none }, .{ .setae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, @@ -459,62 +459,62 @@ pub const table = &[_]Entry{ .{ .shr, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .none }, .{ .shr, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .long }, - .{ .sub, .zi, .al, .imm8, .none, .none, 1, 0x2c, 0x00, 0x00, 0, .none }, - .{ .sub, .zi, .ax, .imm16, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, - .{ .sub, .zi, .eax, .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, - .{ .sub, .zi, .rax, .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .long }, - .{ .sub, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 5, .none }, - .{ .sub, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, - .{ .sub, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, - .{ .sub, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .long }, - .{ .sub, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, - .{ .sub, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, - .{ .sub, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 5, .long }, - .{ .sub, .mr, .rm8, .r8, .none, .none, 1, 0x28, 0x00, 0x00, 0, .none }, - .{ .sub, .mr, .rm16, .r16, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, - .{ .sub, .mr, .rm32, .r32, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, - .{ .sub, .mr, .rm64, .r64, .none, .none, 1, 0x29, 0x00, 0x00, 0, .long }, - .{ .sub, .rm, .r8, .rm8, .none, .none, 1, 0x2a, 0x00, 0x00, 0, .none }, - .{ .sub, .rm, .r16, .rm16, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, - .{ .sub, .rm, .r32, .rm32, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, - .{ .sub, .rm, .r64, .rm64, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .long }, + .{ .sub, .zi, .al, .imm8, .none, .none, 1, 0x2c, 0x00, 0x00, 0, .none }, + .{ .sub, .zi, .ax, .imm16, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, + .{ .sub, .zi, .eax, .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, + .{ .sub, .zi, .rax, .imm32s, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .long }, + .{ .sub, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 5, .long }, + .{ .sub, .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 5, .long }, + .{ .sub, .mr, .rm8, .r8, .none, .none, 1, 0x28, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm16, .r16, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm32, .r32, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm64, .r64, .none, .none, 1, 0x29, 0x00, 0x00, 0, .long }, + .{ .sub, .rm, .r8, .rm8, .none, .none, 1, 0x2a, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r16, .rm16, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r32, .rm32, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r64, .rm64, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .long }, .{ .syscall, .np, .none, .none, .none, .none, 2, 0x0f, 0x05, 0x00, 0, .none }, - .{ .@"test", .zi, .al, .imm8, .none, .none, 1, 0xa8, 0x00, 0x00, 0, .none }, - .{ .@"test", .zi, .ax, .imm16, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, - .{ .@"test", .zi, .eax, .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, - .{ .@"test", .zi, .rax, .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .long }, - .{ .@"test", .mi, .rm8, .imm8, .none, .none, 1, 0xf6, 0x00, 0x00, 0, .none }, - .{ .@"test", .mi, .rm16, .imm16, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, - .{ .@"test", .mi, .rm32, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, - .{ .@"test", .mi, .rm64, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .long }, - .{ .@"test", .mr, .rm8, .r8, .none, .none, 1, 0x84, 0x00, 0x00, 0, .none }, - .{ .@"test", .mr, .rm16, .r16, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, - .{ .@"test", .mr, .rm32, .r32, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, - .{ .@"test", .mr, .rm64, .r64, .none, .none, 1, 0x85, 0x00, 0x00, 0, .long }, + .{ .@"test", .zi, .al, .imm8, .none, .none, 1, 0xa8, 0x00, 0x00, 0, .none }, + .{ .@"test", .zi, .ax, .imm16, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, + .{ .@"test", .zi, .eax, .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, + .{ .@"test", .zi, .rax, .imm32s, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .long }, + .{ .@"test", .mi, .rm8, .imm8, .none, .none, 1, 0xf6, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm16, .imm16, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm32, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm64, .imm32s, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .long }, + .{ .@"test", .mr, .rm8, .r8, .none, .none, 1, 0x84, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm16, .r16, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm32, .r32, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm64, .r64, .none, .none, 1, 0x85, 0x00, 0x00, 0, .long }, - .{ .ud2, .np, .none, .none, .none, .none, 2, 0x0f, 0x0b, 0x00, 0, .none }, + .{ .ud2, .np, .none, .none, .none, .none, 2, 0x0f, 0x0b, 0x00, 0, .none }, - .{ .xor, .zi, .al, .imm8, .none, .none, 1, 0x34, 0x00, 0x00, 0, .none }, - .{ .xor, .zi, .ax, .imm16, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, - .{ .xor, .zi, .eax, .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, - .{ .xor, .zi, .rax, .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .long }, - .{ .xor, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 6, .none }, - .{ .xor, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, - .{ .xor, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, - .{ .xor, .mi, .rm64, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .long }, - .{ .xor, .mi, .rm16, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, - .{ .xor, .mi, .rm32, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, - .{ .xor, .mi, .rm64, .imm8, .none, .none, 1, 0x83, 0x00, 0x00, 6, .long }, - .{ .xor, .mr, .rm8, .r8, .none, .none, 1, 0x30, 0x00, 0x00, 0, .none }, - .{ .xor, .mr, .rm16, .r16, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, - .{ .xor, .mr, .rm32, .r32, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, - .{ .xor, .mr, .rm64, .r64, .none, .none, 1, 0x31, 0x00, 0x00, 0, .long }, - .{ .xor, .rm, .r8, .rm8, .none, .none, 1, 0x32, 0x00, 0x00, 0, .none }, - .{ .xor, .rm, .r16, .rm16, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, - .{ .xor, .rm, .r32, .rm32, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, - .{ .xor, .rm, .r64, .rm64, .none, .none, 1, 0x33, 0x00, 0x00, 0, .long }, + .{ .xor, .zi, .al, .imm8, .none, .none, 1, 0x34, 0x00, 0x00, 0, .none }, + .{ .xor, .zi, .ax, .imm16, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, + .{ .xor, .zi, .eax, .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, + .{ .xor, .zi, .rax, .imm32s, .none, .none, 1, 0x35, 0x00, 0x00, 0, .long }, + .{ .xor, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 6, .long }, + .{ .xor, .mi, .rm16, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 6, .long }, + .{ .xor, .mr, .rm8, .r8, .none, .none, 1, 0x30, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm16, .r16, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm32, .r32, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm64, .r64, .none, .none, 1, 0x31, 0x00, 0x00, 0, .long }, + .{ .xor, .rm, .r8, .rm8, .none, .none, 1, 0x32, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r16, .rm16, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r32, .rm32, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r64, .rm64, .none, .none, 1, 0x33, 0x00, 0x00, 0, .long }, // SSE .{ .addss, .rm, .xmm, .xmm_m32, .none, .none, 3, 0xf3, 0x0f, 0x58, 0, .sse }, @@ -540,3 +540,4 @@ pub const table = &[_]Entry{ .{ .ucomisd, .rm, .xmm, .xmm_m64, .none, .none, 3, 0x66, 0x0f, 0x2e, 0, .sse2 }, }; // zig fmt: on + From 292f91aef2d2ed360f4dbb1f8cf6e22500a682ca Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 7 Mar 2023 16:59:38 +0100 Subject: [PATCH 05/31] Handle .ah vs .spl register aliases --- src/arch/x86_64/Encoding.zig | 77 +++++++++++++++++++++++++--------- src/arch/x86_64/encoder.zig | 22 ++++------ src/arch/x86_64/encodings.zig | 78 +++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 34 deletions(-) diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 5d3944a554..3c2e9f848c 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -29,12 +29,42 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { op2: Instruction.Operand, op3: Instruction.Operand, op4: Instruction.Operand, -}) ?Encoding { +}) !?Encoding { const input_op1 = Op.fromOperand(args.op1); const input_op2 = Op.fromOperand(args.op2); const input_op3 = Op.fromOperand(args.op3); const input_op4 = Op.fromOperand(args.op4); + const ops = &[_]Instruction.Operand{ args.op1, args.op2, args.op3, args.op4 }; + const rex_required = for (ops) |op| switch (op) { + .reg => |r| switch (r) { + .spl, .bpl, .sil, .dil => break true, + else => {}, + }, + else => {}, + } else false; + const rex_invalid = for (ops) |op| switch (op) { + .reg => |r| switch (r) { + .ah, .bh, .ch, .dh => break true, + else => {}, + }, + else => {}, + } else false; + const rex_extended = for (ops) |op| switch (op) { + .reg => |r| if (r.isExtended()) break true, + .mem => |m| { + if (m.base()) |base| { + if (base.isExtended()) break true; + } + if (m.scaleIndex()) |si| { + if (si.index.isExtended()) break true; + } + }, + else => {}, + } else false; + + if ((rex_required or rex_extended) and rex_invalid) return error.CannotEncode; + // TODO work out what is the maximum number of variants we can actually find in one swoop. var candidates: [10]Encoding = undefined; var count: usize = 0; @@ -57,13 +87,24 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { input_op3.isSubset(enc.op3, enc.mode) and input_op4.isSubset(enc.op4, enc.mode)) { - candidates[count] = enc; - count += 1; + if (rex_required) { + switch (enc.mode) { + .rex, .long => { + candidates[count] = enc; + count += 1; + }, + else => {}, + } + } else { + if (enc.mode != .rex) { + candidates[count] = enc; + count += 1; + } + } } } if (count == 0) return null; - if (count == 1) return candidates[0]; const EncodingLength = struct { fn estimate(encoding: Encoding, params: struct { @@ -71,7 +112,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { op2: Instruction.Operand, op3: Instruction.Operand, op4: Instruction.Operand, - }) usize { + }) !usize { var inst = Instruction{ .op1 = params.op1, .op2 = params.op2, @@ -91,7 +132,13 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { } = null; var i: usize = 0; while (i < count) : (i += 1) { - const len = EncodingLength.estimate(candidates[i], .{ + const candidate = candidates[i]; + switch (candidate.mode) { + .long, .rex => if (rex_invalid) return error.CannotEncode, + else => {}, + } + + const len = try EncodingLength.estimate(candidate, .{ .op1 = args.op1, .op2 = args.op2, .op3 = args.op3, @@ -136,20 +183,11 @@ pub fn findByOpcode(opc: []const u8, prefixes: struct { if (match) { if (prefixes.rex.w) { switch (enc.mode) { - .fpu, .sse, .sse2 => {}, - .long => return enc, - .none => { - // TODO this is a hack to allow parsing of instructions which contain - // spurious prefix bytes such as - // rex.W mov dil, 0x1 - // Here, rex.W is not needed. - const rex_w_allowed = blk: { - const bit_size = enc.operandBitSize(); - break :blk bit_size == 64 or bit_size == 8; - }; - if (rex_w_allowed) return enc; - }, + .fpu, .sse, .sse2, .none => {}, + .long, .rex => return enc, } + } else if (prefixes.rex.present and !prefixes.rex.isSet()) { + if (enc.mode == .rex) return enc; } else if (prefixes.legacy.prefix_66) { switch (enc.operandBitSize()) { 16 => return enc, @@ -542,6 +580,7 @@ pub const Op = enum { pub const Mode = enum { none, fpu, + rex, long, sse, sse2, diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index d4ca925891..066a733960 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -102,12 +102,12 @@ pub const Instruction = struct { op3: Operand = .none, op4: Operand = .none, }) !Instruction { - const encoding = Encoding.findByMnemonic(mnemonic, .{ + const encoding = (try Encoding.findByMnemonic(mnemonic, .{ .op1 = args.op1, .op2 = args.op2, .op3 = args.op3, .op4 = args.op4, - }) orelse { + })) orelse { std.log.debug("{s} {s} {s} {s} {s}", .{ @tagName(mnemonic), @tagName(Encoding.Op.fromOperand(args.op1)), @@ -251,13 +251,8 @@ pub const Instruction = struct { fn encodeRexPrefix(inst: Instruction, encoder: anytype) !void { const op_en = inst.encoding.op_en; - // Check if we need REX and can actually encode it - const is_rex_invalid = for (&[_]Operand{ inst.op1, inst.op2, inst.op3, inst.op4 }) |op| switch (op) { - .reg => |r| if (r.isRexInvalid()) break true, - else => {}, - } else false; - var rex = Rex{}; + rex.present = inst.encoding.mode == .rex; rex.w = inst.encoding.mode == .long; switch (op_en) { @@ -293,8 +288,6 @@ pub const Instruction = struct { }, } - if (rex.isSet() and is_rex_invalid) return error.CannotEncode; - try encoder.rex(rex); } @@ -507,9 +500,9 @@ fn Encoder(comptime T: type) type { /// or one of reg, index, r/m, base, or opcode-reg might be extended. /// /// See struct `Rex` for a description of each field. - /// - /// Does not add a prefix byte if none of the fields are set! pub fn rex(self: Self, byte: Rex) !void { + if (!byte.present and !byte.isSet()) return; + var value: u8 = 0b0100_0000; if (byte.w) value |= 0b1000; @@ -517,9 +510,7 @@ fn Encoder(comptime T: type) type { if (byte.x) value |= 0b0010; if (byte.b) value |= 0b0001; - if (value != 0b0100_0000) { - try self.writer.writeByte(value); - } + try self.writer.writeByte(value); } // ------ @@ -788,6 +779,7 @@ pub const Rex = struct { r: bool = false, x: bool = false, b: bool = false, + present: bool = false, pub fn isSet(rex: Rex) bool { return rex.w or rex.r or rex.x or rex.b; diff --git a/src/arch/x86_64/encodings.zig b/src/arch/x86_64/encodings.zig index c3fb347f22..b008eb9f3e 100644 --- a/src/arch/x86_64/encodings.zig +++ b/src/arch/x86_64/encodings.zig @@ -18,6 +18,7 @@ pub const table = &[_]Entry{ .{ .adc, .zi, .eax, .imm32, .none, .none, 1, 0x15, 0x00, 0x00, 0, .none }, .{ .adc, .zi, .rax, .imm32s, .none, .none, 1, 0x15, 0x00, 0x00, 0, .long }, .{ .adc, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 2, .none }, + .{ .adc, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 2, .rex }, .{ .adc, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, .{ .adc, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 2, .none }, .{ .adc, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 2, .long }, @@ -25,10 +26,12 @@ pub const table = &[_]Entry{ .{ .adc, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 2, .none }, .{ .adc, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 2, .long }, .{ .adc, .mr, .rm8, .r8, .none, .none, 1, 0x10, 0x00, 0x00, 0, .none }, + .{ .adc, .mr, .rm8, .r8, .none, .none, 1, 0x10, 0x00, 0x00, 0, .rex }, .{ .adc, .mr, .rm16, .r16, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, .{ .adc, .mr, .rm32, .r32, .none, .none, 1, 0x11, 0x00, 0x00, 0, .none }, .{ .adc, .mr, .rm64, .r64, .none, .none, 1, 0x11, 0x00, 0x00, 0, .long }, .{ .adc, .rm, .r8, .rm8, .none, .none, 1, 0x12, 0x00, 0x00, 0, .none }, + .{ .adc, .rm, .r8, .rm8, .none, .none, 1, 0x12, 0x00, 0x00, 0, .rex }, .{ .adc, .rm, .r16, .rm16, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, .{ .adc, .rm, .r32, .rm32, .none, .none, 1, 0x13, 0x00, 0x00, 0, .none }, .{ .adc, .rm, .r64, .rm64, .none, .none, 1, 0x13, 0x00, 0x00, 0, .long }, @@ -38,6 +41,7 @@ pub const table = &[_]Entry{ .{ .add, .zi, .eax, .imm32, .none, .none, 1, 0x05, 0x00, 0x00, 0, .none }, .{ .add, .zi, .rax, .imm32s, .none, .none, 1, 0x05, 0x00, 0x00, 0, .long }, .{ .add, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 0, .none }, + .{ .add, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 0, .rex }, .{ .add, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, .{ .add, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 0, .none }, .{ .add, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 0, .long }, @@ -45,10 +49,12 @@ pub const table = &[_]Entry{ .{ .add, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 0, .none }, .{ .add, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 0, .long }, .{ .add, .mr, .rm8, .r8, .none, .none, 1, 0x00, 0x00, 0x00, 0, .none }, + .{ .add, .mr, .rm8, .r8, .none, .none, 1, 0x00, 0x00, 0x00, 0, .rex }, .{ .add, .mr, .rm16, .r16, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, .{ .add, .mr, .rm32, .r32, .none, .none, 1, 0x01, 0x00, 0x00, 0, .none }, .{ .add, .mr, .rm64, .r64, .none, .none, 1, 0x01, 0x00, 0x00, 0, .long }, .{ .add, .rm, .r8, .rm8, .none, .none, 1, 0x02, 0x00, 0x00, 0, .none }, + .{ .add, .rm, .r8, .rm8, .none, .none, 1, 0x02, 0x00, 0x00, 0, .rex }, .{ .add, .rm, .r16, .rm16, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, .{ .add, .rm, .r32, .rm32, .none, .none, 1, 0x03, 0x00, 0x00, 0, .none }, .{ .add, .rm, .r64, .rm64, .none, .none, 1, 0x03, 0x00, 0x00, 0, .long }, @@ -58,6 +64,7 @@ pub const table = &[_]Entry{ .{ .@"and", .zi, .eax, .imm32, .none, .none, 1, 0x25, 0x00, 0x00, 0, .none }, .{ .@"and", .zi, .rax, .imm32s, .none, .none, 1, 0x25, 0x00, 0x00, 0, .long }, .{ .@"and", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 4, .none }, + .{ .@"and", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 4, .rex }, .{ .@"and", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, .{ .@"and", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 4, .none }, .{ .@"and", .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 4, .long }, @@ -65,10 +72,12 @@ pub const table = &[_]Entry{ .{ .@"and", .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 4, .none }, .{ .@"and", .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 4, .long }, .{ .@"and", .mr, .rm8, .r8, .none, .none, 1, 0x20, 0x00, 0x00, 0, .none }, + .{ .@"and", .mr, .rm8, .r8, .none, .none, 1, 0x20, 0x00, 0x00, 0, .rex }, .{ .@"and", .mr, .rm16, .r16, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, .{ .@"and", .mr, .rm32, .r32, .none, .none, 1, 0x21, 0x00, 0x00, 0, .none }, .{ .@"and", .mr, .rm64, .r64, .none, .none, 1, 0x21, 0x00, 0x00, 0, .long }, .{ .@"and", .rm, .r8, .rm8, .none, .none, 1, 0x22, 0x00, 0x00, 0, .none }, + .{ .@"and", .rm, .r8, .rm8, .none, .none, 1, 0x22, 0x00, 0x00, 0, .rex }, .{ .@"and", .rm, .r16, .rm16, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, .{ .@"and", .rm, .r32, .rm32, .none, .none, 1, 0x23, 0x00, 0x00, 0, .none }, .{ .@"and", .rm, .r64, .rm64, .none, .none, 1, 0x23, 0x00, 0x00, 0, .long }, @@ -181,6 +190,7 @@ pub const table = &[_]Entry{ .{ .cmp, .zi, .eax, .imm32, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .none }, .{ .cmp, .zi, .rax, .imm32s, .none, .none, 1, 0x3d, 0x00, 0x00, 0, .long }, .{ .cmp, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 7, .none }, + .{ .cmp, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 7, .rex }, .{ .cmp, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, .{ .cmp, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 7, .none }, .{ .cmp, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 7, .long }, @@ -188,15 +198,18 @@ pub const table = &[_]Entry{ .{ .cmp, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 7, .none }, .{ .cmp, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 7, .long }, .{ .cmp, .mr, .rm8, .r8, .none, .none, 1, 0x38, 0x00, 0x00, 0, .none }, + .{ .cmp, .mr, .rm8, .r8, .none, .none, 1, 0x38, 0x00, 0x00, 0, .rex }, .{ .cmp, .mr, .rm16, .r16, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, .{ .cmp, .mr, .rm32, .r32, .none, .none, 1, 0x39, 0x00, 0x00, 0, .none }, .{ .cmp, .mr, .rm64, .r64, .none, .none, 1, 0x39, 0x00, 0x00, 0, .long }, .{ .cmp, .rm, .r8, .rm8, .none, .none, 1, 0x3a, 0x00, 0x00, 0, .none }, + .{ .cmp, .rm, .r8, .rm8, .none, .none, 1, 0x3a, 0x00, 0x00, 0, .rex }, .{ .cmp, .rm, .r16, .rm16, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, .{ .cmp, .rm, .r32, .rm32, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .none }, .{ .cmp, .rm, .r64, .rm64, .none, .none, 1, 0x3b, 0x00, 0x00, 0, .long }, .{ .div, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 6, .none }, + .{ .div, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 6, .rex }, .{ .div, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .none }, .{ .div, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .none }, .{ .div, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 6, .long }, @@ -210,11 +223,13 @@ pub const table = &[_]Entry{ .{ .fld, .m, .m80, .none, .none, .none, 1, 0xdb, 0x00, 0x00, 5, .fpu }, .{ .idiv, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 7, .none }, + .{ .idiv, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 7, .rex }, .{ .idiv, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .none }, .{ .idiv, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .none }, .{ .idiv, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 7, .long }, .{ .imul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 5, .none }, + .{ .imul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 5, .rex }, .{ .imul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, .{ .imul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .none }, .{ .imul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 5, .long }, @@ -270,10 +285,12 @@ pub const table = &[_]Entry{ .{ .lea, .rm, .r64, .m, .none, .none, 1, 0x8d, 0x00, 0x00, 0, .long }, .{ .mov, .mr, .rm8, .r8, .none, .none, 1, 0x88, 0x00, 0x00, 0, .none }, + .{ .mov, .mr, .rm8, .r8, .none, .none, 1, 0x88, 0x00, 0x00, 0, .rex }, .{ .mov, .mr, .rm16, .r16, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, .{ .mov, .mr, .rm32, .r32, .none, .none, 1, 0x89, 0x00, 0x00, 0, .none }, .{ .mov, .mr, .rm64, .r64, .none, .none, 1, 0x89, 0x00, 0x00, 0, .long }, .{ .mov, .rm, .r8, .rm8, .none, .none, 1, 0x8a, 0x00, 0x00, 0, .none }, + .{ .mov, .rm, .r8, .rm8, .none, .none, 1, 0x8a, 0x00, 0x00, 0, .rex }, .{ .mov, .rm, .r16, .rm16, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, .{ .mov, .rm, .r32, .rm32, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .none }, .{ .mov, .rm, .r64, .rm64, .none, .none, 1, 0x8b, 0x00, 0x00, 0, .long }, @@ -290,16 +307,20 @@ pub const table = &[_]Entry{ .{ .mov, .td, .moffs, .eax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .none }, .{ .mov, .td, .moffs, .rax, .none, .none, 1, 0xa3, 0x00, 0x00, 0, .long }, .{ .mov, .oi, .r8, .imm8, .none, .none, 1, 0xb0, 0x00, 0x00, 0, .none }, + .{ .mov, .oi, .r8, .imm8, .none, .none, 1, 0xb0, 0x00, 0x00, 0, .rex }, .{ .mov, .oi, .r16, .imm16, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, .{ .mov, .oi, .r32, .imm32, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .none }, .{ .mov, .oi, .r64, .imm64, .none, .none, 1, 0xb8, 0x00, 0x00, 0, .long }, .{ .mov, .mi, .rm8, .imm8, .none, .none, 1, 0xc6, 0x00, 0x00, 0, .none }, + .{ .mov, .mi, .rm8, .imm8, .none, .none, 1, 0xc6, 0x00, 0x00, 0, .rex }, .{ .mov, .mi, .rm16, .imm16, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, .{ .mov, .mi, .rm32, .imm32, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .none }, .{ .mov, .mi, .rm64, .imm32s, .none, .none, 1, 0xc7, 0x00, 0x00, 0, .long }, .{ .movsx, .rm, .r16, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none }, + .{ .movsx, .rm, .r16, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .rex }, .{ .movsx, .rm, .r32, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .none }, + .{ .movsx, .rm, .r32, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .rex }, .{ .movsx, .rm, .r64, .rm8, .none, .none, 2, 0x0f, 0xbe, 0x00, 0, .long }, .{ .movsx, .rm, .r32, .rm16, .none, .none, 2, 0x0f, 0xbf, 0x00, 0, .none }, .{ .movsx, .rm, .r64, .rm16, .none, .none, 2, 0x0f, 0xbf, 0x00, 0, .long }, @@ -315,6 +336,7 @@ pub const table = &[_]Entry{ .{ .movzx, .rm, .r64, .rm16, .none, .none, 2, 0x0f, 0xb7, 0x00, 0, .long }, .{ .mul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 4, .none }, + .{ .mul, .m, .rm8, .none, .none, .none, 1, 0xf6, 0x00, 0x00, 4, .rex }, .{ .mul, .m, .rm16, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .none }, .{ .mul, .m, .rm32, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .none }, .{ .mul, .m, .rm64, .none, .none, .none, 1, 0xf7, 0x00, 0x00, 4, .long }, @@ -326,6 +348,7 @@ pub const table = &[_]Entry{ .{ .@"or", .zi, .eax, .imm32, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .none }, .{ .@"or", .zi, .rax, .imm32s, .none, .none, 1, 0x0d, 0x00, 0x00, 0, .long }, .{ .@"or", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 1, .none }, + .{ .@"or", .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 1, .rex }, .{ .@"or", .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, .{ .@"or", .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 1, .none }, .{ .@"or", .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 1, .long }, @@ -333,10 +356,12 @@ pub const table = &[_]Entry{ .{ .@"or", .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 1, .none }, .{ .@"or", .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 1, .long }, .{ .@"or", .mr, .rm8, .r8, .none, .none, 1, 0x08, 0x00, 0x00, 0, .none }, + .{ .@"or", .mr, .rm8, .r8, .none, .none, 1, 0x08, 0x00, 0x00, 0, .rex }, .{ .@"or", .mr, .rm16, .r16, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, .{ .@"or", .mr, .rm32, .r32, .none, .none, 1, 0x09, 0x00, 0x00, 0, .none }, .{ .@"or", .mr, .rm64, .r64, .none, .none, 1, 0x09, 0x00, 0x00, 0, .long }, .{ .@"or", .rm, .r8, .rm8, .none, .none, 1, 0x0a, 0x00, 0x00, 0, .none }, + .{ .@"or", .rm, .r8, .rm8, .none, .none, 1, 0x0a, 0x00, 0x00, 0, .rex }, .{ .@"or", .rm, .r16, .rm16, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, .{ .@"or", .rm, .r32, .rm32, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .none }, .{ .@"or", .rm, .r64, .rm64, .none, .none, 1, 0x0b, 0x00, 0x00, 0, .long }, @@ -357,27 +382,33 @@ pub const table = &[_]Entry{ .{ .ret, .np, .none, .none, .none, .none, 1, 0xc3, 0x00, 0x00, 0, .none }, .{ .sal, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .none }, + .{ .sal, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .rex }, .{ .sal, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, .{ .sal, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, .{ .sal, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .long }, .{ .sal, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 4, .none }, + .{ .sal, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 4, .rex }, .{ .sal, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, .{ .sal, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, .{ .sal, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .long }, .{ .sal, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 4, .none }, + .{ .sal, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 4, .rex }, .{ .sal, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, .{ .sal, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, .{ .sal, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .long }, .{ .sar, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 7, .none }, + .{ .sar, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 7, .rex }, .{ .sar, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .none }, .{ .sar, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .none }, .{ .sar, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 7, .long }, .{ .sar, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 7, .none }, + .{ .sar, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 7, .rex }, .{ .sar, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 7, .none }, .{ .sar, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 7, .none }, .{ .sar, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 7, .long }, .{ .sar, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 7, .none }, + .{ .sar, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 7, .rex }, .{ .sar, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .none }, .{ .sar, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .none }, .{ .sar, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 7, .long }, @@ -387,6 +418,7 @@ pub const table = &[_]Entry{ .{ .sbb, .zi, .eax, .imm32, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .none }, .{ .sbb, .zi, .rax, .imm32s, .none, .none, 1, 0x1d, 0x00, 0x00, 0, .long }, .{ .sbb, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 3, .none }, + .{ .sbb, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 3, .rex }, .{ .sbb, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, .{ .sbb, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 3, .none }, .{ .sbb, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 3, .long }, @@ -394,67 +426,105 @@ pub const table = &[_]Entry{ .{ .sbb, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 3, .none }, .{ .sbb, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 3, .long }, .{ .sbb, .mr, .rm8, .r8, .none, .none, 1, 0x18, 0x00, 0x00, 0, .none }, + .{ .sbb, .mr, .rm8, .r8, .none, .none, 1, 0x18, 0x00, 0x00, 0, .rex }, .{ .sbb, .mr, .rm16, .r16, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, .{ .sbb, .mr, .rm32, .r32, .none, .none, 1, 0x19, 0x00, 0x00, 0, .none }, .{ .sbb, .mr, .rm64, .r64, .none, .none, 1, 0x19, 0x00, 0x00, 0, .long }, .{ .sbb, .rm, .r8, .rm8, .none, .none, 1, 0x1a, 0x00, 0x00, 0, .none }, + .{ .sbb, .rm, .r8, .rm8, .none, .none, 1, 0x1a, 0x00, 0x00, 0, .rex }, .{ .sbb, .rm, .r16, .rm16, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, .{ .sbb, .rm, .r32, .rm32, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .none }, .{ .sbb, .rm, .r64, .rm64, .none, .none, 1, 0x1b, 0x00, 0x00, 0, .long }, .{ .seta, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .none }, + .{ .seta, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .rex }, .{ .setae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, + .{ .setae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .rex }, .{ .setb, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none }, + .{ .setb, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .rex }, .{ .setbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .none }, + .{ .setbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .rex }, .{ .setc, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none }, + .{ .setc, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .rex }, .{ .sete, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .none }, + .{ .sete, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .rex }, .{ .setg, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .none }, + .{ .setg, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .rex }, .{ .setge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .none }, + .{ .setge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .rex }, .{ .setl, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .none }, + .{ .setl, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .rex }, .{ .setle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .none }, + .{ .setle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .rex }, .{ .setna, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .none }, + .{ .setna, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x96, 0x00, 0, .rex }, .{ .setnae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .none }, + .{ .setnae, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x92, 0x00, 0, .rex }, .{ .setnb, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, + .{ .setnb, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .rex }, .{ .setnbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .none }, + .{ .setnbe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x97, 0x00, 0, .rex }, .{ .setnc, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .none }, + .{ .setnc, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x93, 0x00, 0, .rex }, .{ .setne, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .none }, + .{ .setne, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .rex }, .{ .setng, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .none }, + .{ .setng, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9e, 0x00, 0, .rex }, .{ .setnge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .none }, + .{ .setnge, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9c, 0x00, 0, .rex }, .{ .setnl, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .none }, + .{ .setnl, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9d, 0x00, 0, .rex }, .{ .setnle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .none }, + .{ .setnle, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9f, 0x00, 0, .rex }, .{ .setno, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x91, 0x00, 0, .none }, + .{ .setno, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x91, 0x00, 0, .rex }, .{ .setnp, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .none }, + .{ .setnp, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .rex }, .{ .setns, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x99, 0x00, 0, .none }, + .{ .setns, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x99, 0x00, 0, .rex }, .{ .setnz, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .none }, + .{ .setnz, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x95, 0x00, 0, .rex }, .{ .seto, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x90, 0x00, 0, .none }, + .{ .seto, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x90, 0x00, 0, .rex }, .{ .setp, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .none }, + .{ .setp, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .rex }, .{ .setpe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .none }, + .{ .setpe, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9a, 0x00, 0, .rex }, .{ .setpo, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .none }, + .{ .setpo, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x9b, 0x00, 0, .rex }, .{ .sets, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x98, 0x00, 0, .none }, + .{ .sets, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x98, 0x00, 0, .rex }, .{ .setz, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .none }, + .{ .setz, .m, .rm8, .none, .none, .none, 2, 0x0f, 0x94, 0x00, 0, .rex }, .{ .shl, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .none }, + .{ .shl, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 4, .rex }, .{ .shl, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, .{ .shl, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .none }, .{ .shl, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 4, .long }, .{ .shl, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 4, .none }, + .{ .shl, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 4, .rex }, .{ .shl, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, .{ .shl, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .none }, .{ .shl, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 4, .long }, .{ .shl, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 4, .none }, + .{ .shl, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 4, .rex }, .{ .shl, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, .{ .shl, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .none }, .{ .shl, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 4, .long }, .{ .shr, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 5, .none }, + .{ .shr, .m1, .rm8, .unity, .none, .none, 1, 0xd0, 0x00, 0x00, 5, .rex }, .{ .shr, .m1, .rm16, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .none }, .{ .shr, .m1, .rm32, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .none }, .{ .shr, .m1, .rm64, .unity, .none, .none, 1, 0xd1, 0x00, 0x00, 5, .long }, .{ .shr, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 5, .none }, + .{ .shr, .mc, .rm8, .cl, .none, .none, 1, 0xd2, 0x00, 0x00, 5, .rex }, .{ .shr, .mc, .rm16, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 5, .none }, .{ .shr, .mc, .rm32, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 5, .none }, .{ .shr, .mc, .rm64, .cl, .none, .none, 1, 0xd3, 0x00, 0x00, 5, .long }, .{ .shr, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 5, .none }, + .{ .shr, .mi, .rm8, .imm8, .none, .none, 1, 0xc0, 0x00, 0x00, 5, .rex }, .{ .shr, .mi, .rm16, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .none }, .{ .shr, .mi, .rm32, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .none }, .{ .shr, .mi, .rm64, .imm8, .none, .none, 1, 0xc1, 0x00, 0x00, 5, .long }, @@ -464,6 +534,7 @@ pub const table = &[_]Entry{ .{ .sub, .zi, .eax, .imm32, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .none }, .{ .sub, .zi, .rax, .imm32s, .none, .none, 1, 0x2d, 0x00, 0x00, 0, .long }, .{ .sub, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 5, .none }, + .{ .sub, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 5, .rex }, .{ .sub, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, .{ .sub, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 5, .none }, .{ .sub, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 5, .long }, @@ -471,10 +542,12 @@ pub const table = &[_]Entry{ .{ .sub, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 5, .none }, .{ .sub, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 5, .long }, .{ .sub, .mr, .rm8, .r8, .none, .none, 1, 0x28, 0x00, 0x00, 0, .none }, + .{ .sub, .mr, .rm8, .r8, .none, .none, 1, 0x28, 0x00, 0x00, 0, .rex }, .{ .sub, .mr, .rm16, .r16, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, .{ .sub, .mr, .rm32, .r32, .none, .none, 1, 0x29, 0x00, 0x00, 0, .none }, .{ .sub, .mr, .rm64, .r64, .none, .none, 1, 0x29, 0x00, 0x00, 0, .long }, .{ .sub, .rm, .r8, .rm8, .none, .none, 1, 0x2a, 0x00, 0x00, 0, .none }, + .{ .sub, .rm, .r8, .rm8, .none, .none, 1, 0x2a, 0x00, 0x00, 0, .rex }, .{ .sub, .rm, .r16, .rm16, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, .{ .sub, .rm, .r32, .rm32, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .none }, .{ .sub, .rm, .r64, .rm64, .none, .none, 1, 0x2b, 0x00, 0x00, 0, .long }, @@ -486,10 +559,12 @@ pub const table = &[_]Entry{ .{ .@"test", .zi, .eax, .imm32, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .none }, .{ .@"test", .zi, .rax, .imm32s, .none, .none, 1, 0xa9, 0x00, 0x00, 0, .long }, .{ .@"test", .mi, .rm8, .imm8, .none, .none, 1, 0xf6, 0x00, 0x00, 0, .none }, + .{ .@"test", .mi, .rm8, .imm8, .none, .none, 1, 0xf6, 0x00, 0x00, 0, .rex }, .{ .@"test", .mi, .rm16, .imm16, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, .{ .@"test", .mi, .rm32, .imm32, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .none }, .{ .@"test", .mi, .rm64, .imm32s, .none, .none, 1, 0xf7, 0x00, 0x00, 0, .long }, .{ .@"test", .mr, .rm8, .r8, .none, .none, 1, 0x84, 0x00, 0x00, 0, .none }, + .{ .@"test", .mr, .rm8, .r8, .none, .none, 1, 0x84, 0x00, 0x00, 0, .rex }, .{ .@"test", .mr, .rm16, .r16, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, .{ .@"test", .mr, .rm32, .r32, .none, .none, 1, 0x85, 0x00, 0x00, 0, .none }, .{ .@"test", .mr, .rm64, .r64, .none, .none, 1, 0x85, 0x00, 0x00, 0, .long }, @@ -501,6 +576,7 @@ pub const table = &[_]Entry{ .{ .xor, .zi, .eax, .imm32, .none, .none, 1, 0x35, 0x00, 0x00, 0, .none }, .{ .xor, .zi, .rax, .imm32s, .none, .none, 1, 0x35, 0x00, 0x00, 0, .long }, .{ .xor, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 6, .none }, + .{ .xor, .mi, .rm8, .imm8, .none, .none, 1, 0x80, 0x00, 0x00, 6, .rex }, .{ .xor, .mi, .rm16, .imm16, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, .{ .xor, .mi, .rm32, .imm32, .none, .none, 1, 0x81, 0x00, 0x00, 6, .none }, .{ .xor, .mi, .rm64, .imm32s, .none, .none, 1, 0x81, 0x00, 0x00, 6, .long }, @@ -508,10 +584,12 @@ pub const table = &[_]Entry{ .{ .xor, .mi, .rm32, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 6, .none }, .{ .xor, .mi, .rm64, .imm8s, .none, .none, 1, 0x83, 0x00, 0x00, 6, .long }, .{ .xor, .mr, .rm8, .r8, .none, .none, 1, 0x30, 0x00, 0x00, 0, .none }, + .{ .xor, .mr, .rm8, .r8, .none, .none, 1, 0x30, 0x00, 0x00, 0, .rex }, .{ .xor, .mr, .rm16, .r16, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, .{ .xor, .mr, .rm32, .r32, .none, .none, 1, 0x31, 0x00, 0x00, 0, .none }, .{ .xor, .mr, .rm64, .r64, .none, .none, 1, 0x31, 0x00, 0x00, 0, .long }, .{ .xor, .rm, .r8, .rm8, .none, .none, 1, 0x32, 0x00, 0x00, 0, .none }, + .{ .xor, .rm, .r8, .rm8, .none, .none, 1, 0x32, 0x00, 0x00, 0, .rex }, .{ .xor, .rm, .r16, .rm16, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, .{ .xor, .rm, .r32, .rm32, .none, .none, 1, 0x33, 0x00, 0x00, 0, .none }, .{ .xor, .rm, .r64, .rm64, .none, .none, 1, 0x33, 0x00, 0x00, 0, .long }, From 219c1261a5f46636425be50fdc6e82534bb003a4 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 7 Mar 2023 20:22:45 +0100 Subject: [PATCH 06/31] x86_64: all behavior tests passing --- src/arch/x86_64/CodeGen.zig | 58 +++++++++++++++++++----------------- src/arch/x86_64/Emit.zig | 7 ++++- src/arch/x86_64/Encoding.zig | 4 +++ src/arch/x86_64/encoder.zig | 2 +- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 90286a1bb2..fe44adcfa1 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2785,7 +2785,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type // introduce new MIR tag specifically for mov [reg + 0], imm const payload = try self.addExtra(Mir.ImmPair{ .dest_off = 0, - .operand = @intCast(u32, imm), + .operand = @truncate(u32, imm), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5385,31 +5385,33 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE return self.genSetStackArg(ty, stack_offset, .{ .register = reg }); }, .immediate => |imm| { + _ = imm; switch (abi_size) { - 1, 2, 4 => { - // We have a positive stack offset value but we want a twos complement negative - // offset from rbp, which is at the top of the stack frame. - // mov [rbp+offset], immediate - const flags: u2 = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - else => unreachable, - }; - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = -stack_offset, - .operand = @intCast(u32, imm), - }); - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rsp, - .flags = flags, - }), - .data = .{ .payload = payload }, - }); - }, - 8 => { + // TODO + // 1, 2, 4 => { + // // We have a positive stack offset value but we want a twos complement negative + // // offset from rbp, which is at the top of the stack frame. + // // mov [rbp+offset], immediate + // const flags: u2 = switch (abi_size) { + // 1 => 0b00, + // 2 => 0b01, + // 4 => 0b10, + // else => unreachable, + // }; + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = -stack_offset, + // .operand = @intCast(u32, imm), + // }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rsp, + // .flags = flags, + // }), + // .data = .{ .payload = payload }, + // }); + // }, + 1, 2, 4, 8 => { const reg = try self.copyToTmpRegister(ty, mcv); return self.genSetStackArg(ty, stack_offset, MCValue{ .register = reg }); }, @@ -5543,7 +5545,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl assert(ty.isError()); const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = @intCast(u32, x_big), + .operand = @truncate(u32, x_big), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -5557,7 +5559,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl 1, 2, 4 => { const payload = try self.addExtra(Mir.ImmPair{ .dest_off = -stack_offset, - .operand = @intCast(u32, x_big), + .operand = @truncate(u32, x_big), }); _ = try self.addInst(.{ .tag = .mov_mem_imm, @@ -7020,7 +7022,7 @@ fn truncateRegister(self: *Self, ty: Type, reg: Register) !void { .unsigned => { const shift = @intCast(u6, max_reg_bit_width - int_info.bits); const mask = (~@as(u64, 0)) >> shift; - if (int_info.bits <= 32) { + if (int_info.bits < 32) { try self.genBinOpMir(.@"and", Type.usize, .{ .register = reg }, .{ .immediate = mask }); } else { const tmp_reg = try self.copyToTmpRegister(Type.usize, .{ .immediate = mask }); diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index e23b500fb8..e69601c40b 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -612,12 +612,17 @@ fn mirArithMemImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.In 0b10 => .dword, 0b11 => .qword, }; + const imm = switch (ops.flags) { + 0b00 => @truncate(u8, imm_pair.operand), + 0b01 => @truncate(u16, imm_pair.operand), + 0b10, 0b11 => @truncate(u32, imm_pair.operand), + }; return emit.encode(mnemonic, .{ .op1 = .{ .mem = Memory.sib(ptr_size, .{ .disp = imm_pair.dest_off, .base = ops.reg1, }) }, - .op2 = .{ .imm = Immediate.u(imm_pair.operand) }, + .op2 = .{ .imm = Immediate.u(imm) }, }); } diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 3c2e9f848c..635a09512b 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -544,6 +544,10 @@ pub const Op = enum { } if (op.isImmediate() and target.isImmediate()) { switch (target) { + .imm64 => switch (op) { + .unity, .imm8s, .imm8, .imm16s, .imm16, .imm32s, .imm32, .imm64 => return true, + else => return op == target, + }, .imm32s, .rel32 => switch (op) { .unity, .imm8s, .imm8, .imm16s, .imm16, .imm32s => return true, else => return op == target, diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index 066a733960..925e3fe181 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -108,7 +108,7 @@ pub const Instruction = struct { .op3 = args.op3, .op4 = args.op4, })) orelse { - std.log.debug("{s} {s} {s} {s} {s}", .{ + std.log.warn("{s} {s} {s} {s} {s}", .{ @tagName(mnemonic), @tagName(Encoding.Op.fromOperand(args.op1)), @tagName(Encoding.Op.fromOperand(args.op2)), From 5b3770102845b17207f85d3a43a3b6cab551a329 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 8 Mar 2023 09:49:59 +0100 Subject: [PATCH 07/31] x86_64: refactor immediate selection logic --- src/arch/x86_64/Encoding.zig | 59 +++++++++++++++++------------------- src/arch/x86_64/bits.zig | 4 +-- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 635a09512b..94f816eaa1 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -453,7 +453,8 @@ pub const Op = enum { pub fn bitSize(op: Op) u64 { return switch (op) { .none, .o16, .o32, .o64, .moffs, .m, .sreg => unreachable, - .unity, .imm8, .imm8s, .al, .cl, .r8, .m8, .rm8, .rel8 => 8, + .unity => 1, + .imm8, .imm8s, .al, .cl, .r8, .m8, .rm8, .rel8 => 8, .imm16, .imm16s, .ax, .r16, .m16, .rm16, .rel16 => 16, .imm32, .imm32s, .eax, .r32, .m32, .rm32, .rel32, .xmm_m32 => 32, .imm64, .rax, .r64, .m64, .rm64, .xmm_m64 => 64, @@ -462,6 +463,18 @@ pub const Op = enum { }; } + pub fn isSigned(op: Op) bool { + return switch (op) { + .unity, .imm8, .imm16, .imm32, .imm64 => false, + .imm8s, .imm16s, .imm32s => true, + else => unreachable, + }; + } + + pub fn isUnsigned(op: Op) bool { + return !op.isSigned(); + } + pub fn isRegister(op: Op) bool { // zig fmt: off return switch (op) { @@ -516,8 +529,7 @@ pub const Op = enum { }; } - /// Given an operand `op` checks if `target` is a subset for the purposes - /// of the encoding. + /// Given an operand `op` checks if `target` is a subset for the purposes of the encoding. pub fn isSubset(op: Op, target: Op, mode: Mode) bool { switch (op) { .m, .o16, .o32, .o64 => unreachable, @@ -544,36 +556,19 @@ pub const Op = enum { } if (op.isImmediate() and target.isImmediate()) { switch (target) { - .imm64 => switch (op) { - .unity, .imm8s, .imm8, .imm16s, .imm16, .imm32s, .imm32, .imm64 => return true, - else => return op == target, - }, - .imm32s, .rel32 => switch (op) { - .unity, .imm8s, .imm8, .imm16s, .imm16, .imm32s => return true, - else => return op == target, - }, - .imm32 => switch (op) { - .unity, .imm8, .imm8s, .imm16, .imm16s, .imm32, .imm32s => return true, - else => return op == target, - }, - .imm16s, .rel16 => switch (op) { - .unity, .imm8s, .imm8, .imm16s => return true, - else => return op == target, - }, - .imm16 => switch (op) { - .unity, .imm8, .imm8s, .imm16, .imm16s => return true, - else => return op == target, - }, - .imm8s, .rel8 => switch (op) { - .unity, .imm8s => return true, - else => return op == target, - }, - .imm8 => switch (op) { - .unity, .imm8, .imm8s => return true, - else => return op == target, - }, - else => return op == target, + .imm64 => if (op.bitSize() <= 64) return true, + .imm32s, .rel32 => if (op.bitSize() < 32 or (op.bitSize() == 32 and op.isSigned())) + return true, + .imm32 => if (op.bitSize() <= 32) return true, + .imm16s, .rel16 => if (op.bitSize() < 16 or (op.bitSize() == 16 and op.isSigned())) + return true, + .imm16 => if (op.bitSize() <= 16) return true, + .imm8s, .rel8 => if (op.bitSize() < 8 or (op.bitSize() == 8 and op.isSigned())) + return true, + .imm8 => if (op.bitSize() <= 8) return true, + else => {}, } + return op == target; } return false; }, diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index cd7ed91849..ad9a6f7f23 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -525,13 +525,13 @@ pub const Immediate = union(enum) { pub fn asUnsigned(imm: Immediate, bit_size: u64) u64 { return switch (imm) { .signed => |x| switch (bit_size) { - 8 => @bitCast(u8, @intCast(i8, x)), + 1, 8 => @bitCast(u8, @intCast(i8, x)), 16 => @bitCast(u16, @intCast(i16, x)), 32 => @bitCast(u32, @intCast(i32, x)), else => unreachable, }, .unsigned => |x| switch (bit_size) { - 8 => @intCast(u8, x), + 1, 8 => @intCast(u8, x), 16 => @intCast(u16, x), 32 => @intCast(u32, x), 64 => x, From 6e882d730b2ed1f2494e7b28f1fed2726e4a1ac0 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 8 Mar 2023 23:45:05 +0100 Subject: [PATCH 08/31] x86_64: introduce assemble() helper which encodes/decodes into MIR -> Instruction --- src/arch/x86_64/CodeGen.zig | 2154 ++++++++++++++++++----------------- src/arch/x86_64/Emit.zig | 1411 +++++++---------------- src/arch/x86_64/Mir.zig | 688 ++++------- src/arch/x86_64/encoder.zig | 11 +- 4 files changed, 1750 insertions(+), 2514 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index fe44adcfa1..be8d07a2f6 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -374,71 +374,99 @@ pub fn generate( fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index { const gpa = self.gpa; try self.mir_instructions.ensureUnusedCapacity(gpa, 1); - const result_index = @intCast(Air.Inst.Index, self.mir_instructions.len); + const result_index = @intCast(Mir.Inst.Index, self.mir_instructions.len); self.mir_instructions.appendAssumeCapacity(inst); return result_index; } -pub fn addExtra(self: *Self, extra: anytype) Allocator.Error!u32 { +fn addExtra(self: *Self, extra: anytype) Allocator.Error!u32 { const fields = std.meta.fields(@TypeOf(extra)); try self.mir_extra.ensureUnusedCapacity(self.gpa, fields.len); return self.addExtraAssumeCapacity(extra); } -fn extraData(self: *Self, comptime T: type, index: u32) struct { data: T, end: u32 } { - const fields = std.meta.fields(T); - var i: u32 = index; - var result: T = undefined; - inline for (fields) |field| { - @field(result, field.name) = switch (field.type) { - u32 => self.mir_extra.items[i], - i32 => @bitCast(i32, self.mir_extra.items[i]), - else => @compileError("bad field type"), - }; - i += 1; - } - return .{ - .data = result, - .end = i, - }; -} - -pub fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { +fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { const fields = std.meta.fields(@TypeOf(extra)); const result = @intCast(u32, self.mir_extra.items.len); inline for (fields) |field| { self.mir_extra.appendAssumeCapacity(switch (field.type) { u32 => @field(extra, field.name), i32 => @bitCast(u32, @field(extra, field.name)), - else => @compileError("bad field type"), + else => @compileError("bad field type: " ++ field.name ++ ": " ++ @typeName(field.type)), }); } return result; } +fn assemble(self: *Self, tag: Mir.Inst.Tag, args: struct { + op1: Mir.Operand = .none, + op2: Mir.Operand = .none, + op3: Mir.Operand = .none, + op4: Mir.Operand = .none, +}) !void { + const ops: Mir.Inst.Ops = blk: { + if (args.op1 == .none and args.op2 == .none and args.op3 == .none and args.op4 == .none) + break :blk .none; + + if (args.op1 == .reg and args.op2 == .reg) + break :blk .rr; + if (args.op1 == .reg and args.op2 == .imm) switch (args.op2.imm) { + .signed => break :blk .ri_s, + .unsigned => break :blk .ri_u, + }; + if (args.op1 == .reg) + break :blk .r; + if (args.op1 == .imm) switch (args.op1.imm) { + .signed => break :blk .imm_s, + .unsigned => break :blk .imm_u, // TODO 64bits + }; + + unreachable; + }; + const data: Mir.Inst.Data = switch (ops) { + .none => undefined, + .imm_s => .{ .imm_s = args.op1.imm.signed }, + .imm_u => .{ .imm_u = @intCast(u32, args.op1.imm.unsigned) }, + .r => .{ .r = args.op1.reg }, + .rr => .{ .rr = .{ + .r1 = args.op1.reg, + .r2 = args.op2.reg, + } }, + .ri_s => .{ .ri_s = .{ + .r1 = args.op1.reg, + .imm = args.op2.imm.signed, + } }, + .ri_u => .{ .ri_u = .{ + .r1 = args.op1.reg, + .imm = @intCast(u32, args.op2.imm.unsigned), + } }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + fn gen(self: *Self) InnerError!void { const cc = self.fn_type.fnCallingConvention(); if (cc != .Naked) { - _ = try self.addInst(.{ - .tag = .push, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rbp }), - .data = undefined, // unused for push reg, + try self.assemble(.push, .{ + .op1 = .{ .reg = .rbp }, }); - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rbp, - .reg2 = .rsp, - }), - .data = undefined, + try self.assemble(.mov, .{ + .op1 = .{ .reg = .rbp }, + .op2 = .{ .reg = .rsp }, }); + // We want to subtract the aligned stack frame size from rsp here, but we don't // yet know how big it will be, so we leave room for a 4-byte stack size. // TODO During semantic analysis, check if there are no function calls. If there // are none, here we can omit the part where we subtract and then add rsp. const backpatch_stack_sub = try self.addInst(.{ .tag = .nop, - .ops = undefined, + .ops = .none, .data = undefined, }); @@ -465,7 +493,7 @@ fn gen(self: *Self) InnerError!void { // Push callee-preserved regs that were used actually in use. const backpatch_push_callee_preserved_regs = try self.addInst(.{ .tag = .nop, - .ops = undefined, + .ops = .none, .data = undefined, }); @@ -496,7 +524,7 @@ fn gen(self: *Self) InnerError!void { // Pop saved callee-preserved regs. const backpatch_pop_callee_preserved_regs = try self.addInst(.{ .tag = .nop, - .ops = undefined, + .ops = .none, .data = undefined, }); @@ -509,21 +537,12 @@ fn gen(self: *Self) InnerError!void { // Maybe add rsp, x if required. This is backpatched later. const backpatch_stack_add = try self.addInst(.{ .tag = .nop, - .ops = undefined, + .ops = .none, .data = undefined, }); - _ = try self.addInst(.{ - .tag = .pop, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rbp }), - .data = undefined, - }); - - _ = try self.addInst(.{ - .tag = .ret, - .ops = Mir.Inst.Ops.encode(.{ .flags = 0b11 }), - .data = undefined, - }); + try self.assemble(.pop, .{ .op1 = .{ .reg = .rbp } }); + try self.assemble(.ret, .{}); // Adjust the stack if (self.max_end_stack > math.maxInt(i32)) { @@ -537,27 +556,34 @@ fn gen(self: *Self) InnerError!void { if (aligned_stack_end > 0) { self.mir_instructions.set(backpatch_stack_sub, .{ .tag = .sub, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), - .data = .{ .imm = aligned_stack_end }, + .ops = .ri_u, + .data = .{ .ri_u = .{ + .r1 = .rsp, + .imm = aligned_stack_end, + } }, }); self.mir_instructions.set(backpatch_stack_add, .{ .tag = .add, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), - .data = .{ .imm = aligned_stack_end }, + .ops = .ri_u, + .data = .{ .ri_u = .{ + .r1 = .rsp, + .imm = aligned_stack_end, + } }, }); const save_reg_list = try self.addExtra(Mir.SaveRegisterList{ + .base_reg = @enumToInt(Register.rbp), .register_list = reg_list.asInt(), .stack_end = aligned_stack_end, }); self.mir_instructions.set(backpatch_push_callee_preserved_regs, .{ .tag = .push_regs, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rbp }), + .ops = undefined, .data = .{ .payload = save_reg_list }, }); self.mir_instructions.set(backpatch_pop_callee_preserved_regs, .{ .tag = .pop_regs, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rbp }), + .ops = undefined, .data = .{ .payload = save_reg_list }, }); } @@ -1306,14 +1332,15 @@ fn airMin(self: *Self, inst: Air.Inst.Index) !void { .unsigned => .b, .signed => .l, }; - _ = try self.addInst(.{ - .tag = .cond_mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_mcv.register, - .reg2 = lhs_reg, - }), - .data = .{ .cc = cc }, - }); + _ = cc; + // _ = try self.addInst(.{ + // .tag = .cond_mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_mcv.register, + // .reg2 = lhs_reg, + // }), + // .data = .{ .cc = cc }, + // }); break :result dst_mcv; }; @@ -1513,13 +1540,14 @@ fn genSetStackTruncatedOverflowCompare( .signed => .o, .unsigned => .c, }; - _ = try self.addInst(.{ - .tag = .cond_set_byte, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = overflow_reg.to8(), - }), - .data = .{ .cc = cc }, - }); + _ = cc; + // _ = try self.addInst(.{ + // .tag = .cond_set_byte, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = overflow_reg.to8(), + // }), + // .data = .{ .cc = cc }, + // }); const scratch_reg = temp_regs[1]; try self.genSetReg(extended_ty, scratch_reg, .{ .register = reg }); @@ -1532,11 +1560,11 @@ fn genSetStackTruncatedOverflowCompare( ); const eq_reg = temp_regs[2]; - _ = try self.addInst(.{ - .tag = .cond_set_byte, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = eq_reg.to8() }), - .data = .{ .cc = .ne }, - }); + // _ = try self.addInst(.{ + // .tag = .cond_set_byte, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = eq_reg.to8() }), + // .data = .{ .cc = .ne }, + // }); try self.genBinOpMir( .@"or", @@ -1680,25 +1708,26 @@ fn genIntMulDivOpMir( try self.genSetReg(ty, .rax, lhs); } - switch (signedness) { - .signed => { - _ = try self.addInst(.{ - .tag = .cwd, - .ops = Mir.Inst.Ops.encode(.{ .flags = 0b11 }), - .data = undefined, - }); - }, - .unsigned => { - _ = try self.addInst(.{ - .tag = .xor, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rdx, - .reg2 = .rdx, - }), - .data = undefined, - }); - }, - } + _ = signedness; + // switch (signedness) { + // .signed => { + // _ = try self.addInst(.{ + // .tag = .cwd, + // .ops = Mir.Inst.Ops.encode(.{ .flags = 0b11 }), + // .data = undefined, + // }); + // }, + // .unsigned => { + // _ = try self.addInst(.{ + // .tag = .xor, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rdx, + // .reg2 = .rdx, + // }), + // .data = undefined, + // }); + // }, + // } const factor = switch (rhs) { .register => rhs, @@ -1708,33 +1737,35 @@ fn genIntMulDivOpMir( break :blk MCValue{ .register = reg }; }, }; + _ = factor; + _ = tag; - switch (factor) { - .register => |reg| { - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), - .data = undefined, - }); - }, - .stack_offset => |off| { - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg2 = .rbp, - .flags = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - 8 => 0b11, - else => unreachable, - }, - }), - .data = .{ .disp = -off }, - }); - }, - else => unreachable, - } + // switch (factor) { + // .register => |reg| { + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), + // .data = undefined, + // }); + // }, + // .stack_offset => |off| { + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg2 = .rbp, + // .flags = switch (abi_size) { + // 1 => 0b00, + // 2 => 0b01, + // 4 => 0b10, + // 8 => 0b11, + // else => unreachable, + // }, + // }), + // .data = .{ .disp = -off }, + // }); + // }, + // else => unreachable, + // } } /// Always returns a register. @@ -1760,38 +1791,38 @@ fn genInlineIntDivFloor(self: *Self, ty: Type, lhs: MCValue, rhs: MCValue) !MCVa .unsigned => .div, }, Type.isize, signedness, .{ .register = dividend }, .{ .register = divisor }); - _ = try self.addInst(.{ - .tag = .xor, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = divisor.to64(), - .reg2 = dividend.to64(), - }), - .data = undefined, - }); - _ = try self.addInst(.{ - .tag = .sar, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = divisor.to64(), - .flags = 0b10, - }), - .data = .{ .imm = 63 }, - }); - _ = try self.addInst(.{ - .tag = .@"test", - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rdx, - .reg2 = .rdx, - }), - .data = undefined, - }); - _ = try self.addInst(.{ - .tag = .cond_mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = divisor.to64(), - .reg2 = .rdx, - }), - .data = .{ .cc = .e }, - }); + // _ = try self.addInst(.{ + // .tag = .xor, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = divisor.to64(), + // .reg2 = dividend.to64(), + // }), + // .data = undefined, + // }); + // _ = try self.addInst(.{ + // .tag = .sar, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = divisor.to64(), + // .flags = 0b10, + // }), + // .data = .{ .imm = 63 }, + // }); + // _ = try self.addInst(.{ + // .tag = .@"test", + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rdx, + // .reg2 = .rdx, + // }), + // .data = undefined, + // }); + // _ = try self.addInst(.{ + // .tag = .cond_mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = divisor.to64(), + // .reg2 = .rdx, + // }), + // .data = .{ .cc = .e }, + // }); try self.genBinOpMir(.add, Type.isize, .{ .register = divisor }, .{ .register = .rax }); return MCValue{ .register = divisor }; } @@ -2226,16 +2257,17 @@ fn genSliceElemPtr(self: *Self, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref) !MCValue { const addr_reg = try self.register_manager.allocReg(null, gp); switch (slice_mcv) { .stack_offset => |off| { + _ = off; // mov reg, [rbp - 8] - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg.to64(), - .reg2 = .rbp, - .flags = 0b01, - }), - .data = .{ .disp = -@intCast(i32, off) }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg.to64(), + // .reg2 = .rbp, + // .flags = 0b01, + // }), + // .data = .{ .disp = -@intCast(i32, off) }, + // }); }, else => return self.fail("TODO implement slice_elem_ptr when slice is {}", .{slice_mcv}), } @@ -2312,25 +2344,26 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void { )); try self.genSetStack(array_ty, off, array, .{}); // lea reg, [rbp] - _ = try self.addInst(.{ - .tag = .lea, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg.to64(), - .reg2 = .rbp, - }), - .data = .{ .disp = -off }, - }); + // _ = try self.addInst(.{ + // .tag = .lea, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg.to64(), + // .reg2 = .rbp, + // }), + // .data = .{ .disp = -off }, + // }); }, .stack_offset => |off| { + _ = off; // lea reg, [rbp] - _ = try self.addInst(.{ - .tag = .lea, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg.to64(), - .reg2 = .rbp, - }), - .data = .{ .disp = -off }, - }); + // _ = try self.addInst(.{ + // .tag = .lea, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg.to64(), + // .reg2 = .rbp, + // }), + // .data = .{ .disp = -off }, + // }); }, .memory, .linker_load => { try self.loadMemPtrIntoRegister(addr_reg, Type.usize, array); @@ -2388,15 +2421,15 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { return self.fail("TODO copy value with size {} from pointer", .{elem_abi_size}); } else { // mov dst_mcv, [dst_mcv] - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)), - .reg2 = dst_mcv.register, - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)), + // .reg2 = dst_mcv.register, + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); break :result .{ .register = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)) }; } }; @@ -2650,16 +2683,17 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo .undef => unreachable, .eflags => unreachable, .register => |dst_reg| { + _ = dst_reg; // mov dst_reg, [reg] - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_reg, @intCast(u32, abi_size)), - .reg2 = reg, - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_reg, @intCast(u32, abi_size)), + // .reg2 = reg, + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); }, .stack_offset => |off| { if (abi_size <= 8) { @@ -2724,19 +2758,22 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue .direct => 0b01, .import => 0b10, }; - _ = try self.addInst(.{ - .tag = .lea_pic, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .flags = flags, - }), - .data = .{ - .relocation = .{ - .atom_index = atom_index, - .sym_index = load_struct.sym_index, - }, - }, - }); + _ = abi_size; + _ = atom_index; + _ = flags; + // _ = try self.addInst(.{ + // .tag = .lea_pic, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .flags = flags, + // }), + // .data = .{ + // .relocation = .{ + // .atom_index = atom_index, + // .sym_index = load_struct.sym_index, + // }, + // }, + // }); }, .memory => |addr| { // TODO: in case the address fits in an imm32 we can use [ds:imm32] @@ -2779,27 +2816,28 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type try self.genSetReg(value_ty, reg, value); }, .immediate => |imm| { + _ = imm; switch (abi_size) { 1, 2, 4 => { // TODO this is wasteful! // introduce new MIR tag specifically for mov [reg + 0], imm - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = 0, - .operand = @truncate(u32, imm), - }); - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to64(), - .flags = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - else => unreachable, - }, - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = 0, + // .operand = @truncate(u32, imm), + // }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to64(), + // .flags = switch (abi_size) { + // 1 => 0b00, + // 2 => 0b01, + // 4 => 0b10, + // else => unreachable, + // }, + // }), + // .data = .{ .payload = payload }, + // }); }, 8 => { // TODO: optimization: if the imm is only using the lower @@ -2829,13 +2867,13 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type const overflow_bit_ty = value_ty.structFieldType(1); const overflow_bit_offset = value_ty.structFieldOffset(1, self.target.*); const tmp_reg = try self.register_manager.allocReg(null, gp); - _ = try self.addInst(.{ - .tag = .cond_set_byte, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = tmp_reg.to8(), - }), - .data = .{ .cc = ro.eflags }, - }); + // _ = try self.addInst(.{ + // .tag = .cond_set_byte, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = tmp_reg.to8(), + // }), + // .data = .{ .cc = ro.eflags }, + // }); try self.genInlineMemcpyRegisterRegister( overflow_bit_ty, reg, @@ -2878,15 +2916,15 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type // to get the actual address of the value we want to modify we have to go through the GOT // mov reg, [reg] - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg.to64(), - .reg2 = addr_reg.to64(), - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg.to64(), + // .reg2 = addr_reg.to64(), + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); const new_ptr = MCValue{ .register = addr_reg.to64() }; @@ -2896,11 +2934,11 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type return self.fail("TODO saving imm to memory for abi_size {}", .{abi_size}); } - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = 0, - // TODO check if this logic is correct - .operand = @intCast(u32, imm), - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = 0, + // // TODO check if this logic is correct + // .operand = @intCast(u32, imm), + // }); const flags: u2 = switch (abi_size) { 1 => 0b00, 2 => 0b01, @@ -2919,14 +2957,14 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type return self.fail("TODO imm64 would get incorrectly sign extended", .{}); } } - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg.to64(), - .flags = flags, - }), - .data = .{ .payload = payload }, - }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg.to64(), + // .flags = flags, + // }), + // .data = .{ .payload = payload }, + // }); }, .register => { return self.store(new_ptr, value, ptr_ty, value_ty); @@ -2939,15 +2977,15 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type try self.loadMemPtrIntoRegister(tmp_reg, value_ty, value); - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = tmp_reg, - .reg2 = tmp_reg, - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = tmp_reg, + // .reg2 = tmp_reg, + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); return self.store(new_ptr, .{ .register = tmp_reg }, ptr_ty, value_ty); } @@ -3109,14 +3147,14 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { }; const field_size = @intCast(u32, struct_field_ty.abiSize(self.target.*)); if (signedness == .signed and field_size < 8) { - _ = try self.addInst(.{ - .tag = .mov_sign_extend, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_mcv.register, - .reg2 = registerAlias(dst_mcv.register, field_size), - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .mov_sign_extend, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_mcv.register, + // .reg2 = registerAlias(dst_mcv.register, field_size), + // }), + // .data = undefined, + // }); } break :result dst_mcv; @@ -3133,13 +3171,13 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { defer self.register_manager.unlockReg(reg_lock); const dst_reg = try self.register_manager.allocReg(inst, gp); - _ = try self.addInst(.{ - .tag = .cond_set_byte, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_reg.to8(), - }), - .data = .{ .cc = ro.eflags }, - }); + // _ = try self.addInst(.{ + // .tag = .cond_set_byte, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_reg.to8(), + // }), + // .data = .{ .cc = ro.eflags }, + // }); break :result MCValue{ .register = dst_reg.to8() }; }, else => unreachable, @@ -3176,22 +3214,22 @@ fn genShiftBinOpMir(self: *Self, tag: Mir.Inst.Tag, ty: Type, reg: Register, shi .immediate => |imm| switch (imm) { 0 => return, 1 => { - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }), + // .data = undefined, + // }); return; }, else => { - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .flags = 0b10, - }), - .data = .{ .imm = @intCast(u8, imm) }, - }); + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .flags = 0b10, + // }), + // .data = .{ .imm = @intCast(u8, imm) }, + // }); return; }, }, @@ -3204,15 +3242,16 @@ fn genShiftBinOpMir(self: *Self, tag: Mir.Inst.Tag, ty: Type, reg: Register, shi try self.register_manager.getReg(.rcx, null); try self.genSetReg(Type.u8, .rcx, shift); } + _ = abi_size; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .flags = 0b01, - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .flags = 0b01, + // }), + // .data = undefined, + // }); } /// Result is always a register. @@ -3583,49 +3622,51 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .register => |src_reg| switch (dst_ty.zigTypeTag()) { .Float => { if (intrinsicsAllowed(self.target.*, dst_ty)) { - const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) { - .f32 => switch (mir_tag) { - .add => Mir.Inst.Tag.add_f32, - .cmp => Mir.Inst.Tag.cmp_f32, - else => return self.fail("TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}), - }, - .f64 => switch (mir_tag) { - .add => Mir.Inst.Tag.add_f64, - .cmp => Mir.Inst.Tag.cmp_f64, - else => return self.fail("TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}), - }, - else => return self.fail("TODO genBinOpMir for float register-register and type {}", .{dst_ty.fmtDebug()}), - }; - _ = try self.addInst(.{ - .tag = actual_tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_reg.to128(), - .reg2 = src_reg.to128(), - }), - .data = undefined, - }); + // const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) { + // .f32 => switch (mir_tag) { + // .add => Mir.Inst.Tag.add_f32, + // .cmp => Mir.Inst.Tag.cmp_f32, + // else => return self.fail("TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}), + // }, + // .f64 => switch (mir_tag) { + // .add => Mir.Inst.Tag.add_f64, + // .cmp => Mir.Inst.Tag.cmp_f64, + // else => return self.fail("TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}), + // }, + // else => return self.fail("TODO genBinOpMir for float register-register and type {}", .{dst_ty.fmtDebug()}), + // }; + // _ = try self.addInst(.{ + // .tag = actual_tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_reg.to128(), + // .reg2 = src_reg.to128(), + // }), + // .data = undefined, + // }); return; } return self.fail("TODO genBinOpMir for float register-register and no intrinsics", .{}); }, else => { - _ = try self.addInst(.{ - .tag = mir_tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_reg, abi_size), - .reg2 = registerAlias(src_reg, abi_size), - }), - .data = undefined, - }); + _ = src_reg; + // _ = try self.addInst(.{ + // .tag = mir_tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_reg, abi_size), + // .reg2 = registerAlias(src_reg, abi_size), + // }), + // .data = undefined, + // }); }, }, .immediate => |imm| { - _ = try self.addInst(.{ - .tag = mir_tag, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(dst_reg, abi_size) }), - .data = .{ .imm = @intCast(u32, imm) }, - }); + _ = imm; + // _ = try self.addInst(.{ + // .tag = mir_tag, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(dst_reg, abi_size) }), + // .data = .{ .imm = @intCast(u32, imm) }, + // }); }, .memory, .linker_load, @@ -3642,15 +3683,15 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu if (off > math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } - _ = try self.addInst(.{ - .tag = mir_tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_reg, abi_size), - .reg2 = .rbp, - .flags = 0b01, - }), - .data = .{ .disp = -off }, - }); + // _ = try self.addInst(.{ + // .tag = mir_tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_reg, abi_size), + // .reg2 = .rbp, + // .flags = 0b01, + // }), + // .data = .{ .disp = -off }, + // }); }, } }, @@ -3668,26 +3709,28 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .dead, .unreach => unreachable, .register_overflow => unreachable, .register => |src_reg| { - _ = try self.addInst(.{ - .tag = mir_tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rbp, - .reg2 = registerAlias(src_reg, abi_size), - .flags = 0b10, - }), - .data = .{ .disp = -off }, - }); + _ = src_reg; + // _ = try self.addInst(.{ + // .tag = mir_tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rbp, + // .reg2 = registerAlias(src_reg, abi_size), + // .flags = 0b10, + // }), + // .data = .{ .disp = -off }, + // }); }, .immediate => |imm| { - const tag: Mir.Inst.Tag = switch (mir_tag) { - .add => .add_mem_imm, - .@"or" => .or_mem_imm, - .@"and" => .and_mem_imm, - .sub => .sub_mem_imm, - .xor => .xor_mem_imm, - .cmp => .cmp_mem_imm, - else => unreachable, - }; + _ = imm; + // const tag: Mir.Inst.Tag = switch (mir_tag) { + // .add => .add_mem_imm, + // .@"or" => .or_mem_imm, + // .@"and" => .and_mem_imm, + // .sub => .sub_mem_imm, + // .xor => .xor_mem_imm, + // .cmp => .cmp_mem_imm, + // else => unreachable, + // }; const flags: u2 = switch (abi_size) { 1 => 0b00, 2 => 0b01, @@ -3695,18 +3738,19 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu 8 => 0b11, else => unreachable, }; - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = -off, - .operand = @intCast(u32, imm), - }); - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rbp, - .flags = flags, - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = -off, + // .operand = @intCast(u32, imm), + // }); + _ = flags; + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rbp, + // .flags = flags, + // }), + // .data = .{ .payload = payload }, + // }); }, .memory, .stack_offset, @@ -3735,6 +3779,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu /// Does not support byte-size operands. fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: MCValue) InnerError!void { const abi_size = @intCast(u32, dst_ty.abiSize(self.target.*)); + _ = abi_size; switch (dst_mcv) { .none => unreachable, .undef => unreachable, @@ -3750,29 +3795,30 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M .ptr_stack_offset => unreachable, .register_overflow => unreachable, .register => |src_reg| { + _ = src_reg; // register, register - _ = try self.addInst(.{ - .tag = .imul_complex, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_reg, abi_size), - .reg2 = registerAlias(src_reg, abi_size), - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .imul_complex, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_reg, abi_size), + // .reg2 = registerAlias(src_reg, abi_size), + // }), + // .data = undefined, + // }); }, .immediate => |imm| { // TODO take into account the type's ABI size when selecting the register alias // register, immediate if (math.minInt(i32) <= imm and imm <= math.maxInt(i32)) { - _ = try self.addInst(.{ - .tag = .imul_complex, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_reg.to32(), - .reg2 = dst_reg.to32(), - .flags = 0b10, - }), - .data = .{ .imm = @intCast(u32, imm) }, - }); + // _ = try self.addInst(.{ + // .tag = .imul_complex, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_reg.to32(), + // .reg2 = dst_reg.to32(), + // .flags = 0b10, + // }), + // .data = .{ .imm = @intCast(u32, imm) }, + // }); } else { // TODO verify we don't spill and assign to the same register as dst_mcv const src_reg = try self.copyToTmpRegister(dst_ty, src_mcv); @@ -3780,15 +3826,16 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M } }, .stack_offset => |off| { - _ = try self.addInst(.{ - .tag = .imul_complex, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_reg, abi_size), - .reg2 = .rbp, - .flags = 0b01, - }), - .data = .{ .disp = -off }, - }); + _ = off; + // _ = try self.addInst(.{ + // .tag = .imul_complex, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_reg, abi_size), + // .reg2 = .rbp, + // .flags = 0b01, + // }), + // .data = .{ .disp = -off }, + // }); }, .memory => { return self.fail("TODO implement x86 multiply source memory", .{}); @@ -3811,16 +3858,17 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M .register => |src_reg| { // copy dst to a register const dst_reg = try self.copyToTmpRegister(dst_ty, dst_mcv); + _ = src_reg; // multiply into dst_reg // register, register - _ = try self.addInst(.{ - .tag = .imul_complex, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_reg, abi_size), - .reg2 = registerAlias(src_reg, abi_size), - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .imul_complex, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_reg, abi_size), + // .reg2 = registerAlias(src_reg, abi_size), + // }), + // .data = undefined, + // }); // copy dst_reg back out return self.genSetStack(dst_ty, off, .{ .register = dst_reg }, .{}); }, @@ -3946,20 +3994,20 @@ fn genVarDbgInfo( } fn airTrap(self: *Self) !void { - _ = try self.addInst(.{ - .tag = .ud, - .ops = Mir.Inst.Ops.encode(.{}), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .ud, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = undefined, + // }); return self.finishAirBookkeeping(); } fn airBreakpoint(self: *Self) !void { - _ = try self.addInst(.{ - .tag = .interrupt, - .ops = Mir.Inst.Ops.encode(.{}), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .interrupt, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = undefined, + // }); return self.finishAirBookkeeping(); } @@ -4054,11 +4102,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier if (info.stack_byte_count > 0) { // Adjust the stack - _ = try self.addInst(.{ - .tag = .sub, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), - .data = .{ .imm = info.stack_byte_count }, - }); + // _ = try self.addInst(.{ + // .tag = .sub, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), + // .data = .{ .imm = info.stack_byte_count }, + // }); } // Due to incremental compilation, how function calls are generated depends @@ -4072,11 +4120,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl); const atom = elf_file.getAtom(atom_index); const got_addr = @intCast(i32, atom.getOffsetTableAddress(elf_file)); - _ = try self.addInst(.{ - .tag = .call, - .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), - .data = .{ .disp = got_addr }, - }); + _ = got_addr; + // _ = try self.addInst(.{ + // .tag = .call, + // .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), + // .data = .{ .disp = got_addr }, + // }); } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { const atom_index = try coff_file.getOrCreateAtomForDecl(func.owner_decl); const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?; @@ -4086,14 +4135,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .sym_index = sym_index, }, }); - _ = try self.addInst(.{ - .tag = .call, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rax, - .flags = 0b01, - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .call, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rax, + // .flags = 0b01, + // }), + // .data = undefined, + // }); } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { const atom_index = try macho_file.getOrCreateAtomForDecl(func.owner_decl); const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?; @@ -4103,14 +4152,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .sym_index = sym_index, }, }); - _ = try self.addInst(.{ - .tag = .call, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rax, - .flags = 0b01, - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .call, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rax, + // .flags = 0b01, + // }), + // .data = undefined, + // }); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { const decl_block_index = try p9.seeDecl(func.owner_decl); const decl_block = p9.getDeclBlock(decl_block_index); @@ -4119,11 +4168,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const got_addr = p9.bases.data; const got_index = decl_block.got_index.?; const fn_got_addr = got_addr + got_index * ptr_bytes; - _ = try self.addInst(.{ - .tag = .call, - .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), - .data = .{ .disp = @intCast(i32, fn_got_addr) }, - }); + _ = fn_got_addr; + // _ = try self.addInst(.{ + // .tag = .call, + // .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), + // .data = .{ .disp = @intCast(i32, fn_got_addr) }, + // }); } else unreachable; } else if (func_value.castTag(.extern_fn)) |func_payload| { const extern_fn = func_payload.data; @@ -4143,26 +4193,28 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .sym_index = sym_index, }, }); - _ = try self.addInst(.{ - .tag = .call, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rax, - .flags = 0b01, - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .call, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rax, + // .flags = 0b01, + // }), + // .data = undefined, + // }); } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { const sym_index = try macho_file.getGlobalSymbol(mem.sliceTo(decl_name, 0)); const atom = try macho_file.getOrCreateAtomForDecl(self.mod_fn.owner_decl); const atom_index = macho_file.getAtom(atom).getSymbolIndex().?; - _ = try self.addInst(.{ - .tag = .call_extern, - .ops = undefined, - .data = .{ .relocation = .{ - .atom_index = atom_index, - .sym_index = sym_index, - } }, - }); + _ = sym_index; + _ = atom_index; + // _ = try self.addInst(.{ + // .tag = .call_extern, + // .ops = undefined, + // .data = .{ .relocation = .{ + // .atom_index = atom_index, + // .sym_index = sym_index, + // } }, + // }); } else { return self.fail("TODO implement calling extern functions", .{}); } @@ -4173,23 +4225,23 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier assert(ty.zigTypeTag() == .Pointer); const mcv = try self.resolveInst(callee); try self.genSetReg(Type.initTag(.usize), .rax, mcv); - _ = try self.addInst(.{ - .tag = .call, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rax, - .flags = 0b01, - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .call, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rax, + // .flags = 0b01, + // }), + // .data = undefined, + // }); } if (info.stack_byte_count > 0) { // Readjust the stack - _ = try self.addInst(.{ - .tag = .add, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), - .data = .{ .imm = info.stack_byte_count }, - }); + // _ = try self.addInst(.{ + // .tag = .add, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), + // .data = .{ .imm = info.stack_byte_count }, + // }); } const result: MCValue = result: { @@ -4246,12 +4298,12 @@ fn airRet(self: *Self, inst: Air.Inst.Index) !void { // TODO when implementing defer, this will need to jump to the appropriate defer expression. // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. - const jmp_reloc = try self.addInst(.{ - .tag = .jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst = undefined }, - }); - try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); + // const jmp_reloc = try self.addInst(.{ + // .tag = .jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst = undefined }, + // }); + // try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -4282,12 +4334,12 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { // TODO when implementing defer, this will need to jump to the appropriate defer expression. // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. - const jmp_reloc = try self.addInst(.{ - .tag = .jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst = undefined }, - }); - try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); + // const jmp_reloc = try self.addInst(.{ + // .tag = .jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst = undefined }, + // }); + // try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -4461,33 +4513,35 @@ fn genCondBrMir(self: *Self, ty: Type, mcv: MCValue) !u32 { const abi_size = ty.abiSize(self.target.*); switch (mcv) { .eflags => |cc| { - return self.addInst(.{ - .tag = .cond_jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ - .inst_cc = .{ - .inst = undefined, - // Here we map the opposites since the jump is to the false branch. - .cc = cc.negate(), - }, - }, - }); + _ = cc; + // return self.addInst(.{ + // .tag = .cond_jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ + // .inst_cc = .{ + // .inst = undefined, + // // Here we map the opposites since the jump is to the false branch. + // .cc = cc.negate(), + // }, + // }, + // }); }, .register => |reg| { + _ = reg; try self.spillEflagsIfOccupied(); - _ = try self.addInst(.{ - .tag = .@"test", - .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), - .data = .{ .imm = 1 }, - }); - return self.addInst(.{ - .tag = .cond_jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .e, - } }, - }); + // _ = try self.addInst(.{ + // .tag = .@"test", + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), + // .data = .{ .imm = 1 }, + // }); + // return self.addInst(.{ + // .tag = .cond_jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst_cc = .{ + // .inst = undefined, + // .cc = .e, + // } }, + // }); }, .immediate, .stack_offset, @@ -4501,6 +4555,7 @@ fn genCondBrMir(self: *Self, ty: Type, mcv: MCValue) !u32 { }, else => return self.fail("TODO implement condbr when condition is {s}", .{@tagName(mcv)}), } + return 0; // TODO } fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { @@ -4825,12 +4880,13 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void { const loop = self.air.extraData(Air.Block, ty_pl.payload); const body = self.air.extra[loop.end..][0..loop.data.body_len]; const jmp_target = @intCast(u32, self.mir_instructions.len); + _ = jmp_target; try self.genBody(body); - _ = try self.addInst(.{ - .tag = .jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst = jmp_target }, - }); + // _ = try self.addInst(.{ + // .tag = .jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst = jmp_target }, + // }); return self.finishAirBookkeeping(); } @@ -4876,21 +4932,23 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u .undef => unreachable, .dead, .unreach => unreachable, .immediate => |imm| { - _ = try self.addInst(.{ - .tag = .xor, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(cond_reg, abi_size) }), - .data = .{ .imm = @intCast(u32, imm) }, - }); + _ = imm; + // _ = try self.addInst(.{ + // .tag = .xor, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(cond_reg, abi_size) }), + // .data = .{ .imm = @intCast(u32, imm) }, + // }); }, .register => |reg| { - _ = try self.addInst(.{ - .tag = .xor, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(cond_reg, abi_size), - .reg2 = registerAlias(reg, abi_size), - }), - .data = undefined, - }); + _ = reg; + // _ = try self.addInst(.{ + // .tag = .xor, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(cond_reg, abi_size), + // .reg2 = registerAlias(reg, abi_size), + // }), + // .data = undefined, + // }); }, .stack_offset => { if (abi_size <= 8) { @@ -4905,22 +4963,22 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u }, } - _ = try self.addInst(.{ - .tag = .@"test", - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(cond_reg, abi_size), - .reg2 = registerAlias(cond_reg, abi_size), - }), - .data = undefined, - }); - return self.addInst(.{ - .tag = .cond_jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .ne, - } }, - }); + // _ = try self.addInst(.{ + // .tag = .@"test", + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(cond_reg, abi_size), + // .reg2 = registerAlias(cond_reg, abi_size), + // }), + // .data = undefined, + // }); + // return self.addInst(.{ + // .tag = .cond_jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst_cc = .{ + // .inst = undefined, + // .cc = .ne, + // } }, + // }); }, .stack_offset => { try self.spillEflagsIfOccupied(); @@ -4938,6 +4996,7 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u return self.fail("TODO implemenent switch mir when condition is {}", .{condition}); }, } + return 0; // TODO } fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { @@ -5132,9 +5191,9 @@ fn canonicaliseBranches(self: *Self, parent_branch: *Branch, canon_branch: *Bran fn performReloc(self: *Self, reloc: Mir.Inst.Index) !void { const next_inst = @intCast(u32, self.mir_instructions.len); switch (self.mir_instructions.items(.tag)[reloc]) { - .cond_jmp => { - self.mir_instructions.items(.data)[reloc].inst_cc.inst = next_inst; - }, + // .cond_jmp => { + // self.mir_instructions.items(.data)[reloc].inst_cc.inst = next_inst; + // }, .jmp => { self.mir_instructions.items(.data)[reloc].inst = next_inst; }, @@ -5177,12 +5236,12 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { // Emit a jump with a relocation. It will be patched up after the block ends. try block_data.relocs.ensureUnusedCapacity(self.gpa, 1); // Leave the jump offset undefined - const jmp_reloc = try self.addInst(.{ - .tag = .jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst = undefined }, - }); - block_data.relocs.appendAssumeCapacity(jmp_reloc); + // const jmp_reloc = try self.addInst(.{ + // .tag = .jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst = undefined }, + // }); + // block_data.relocs.appendAssumeCapacity(jmp_reloc); } fn airAsm(self: *Self, inst: Air.Inst.Index) !void { @@ -5254,30 +5313,22 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { var iter = std.mem.tokenize(u8, asm_source, "\n\r"); while (iter.next()) |ins| { if (mem.eql(u8, ins, "syscall")) { - _ = try self.addInst(.{ - .tag = .syscall, - .ops = undefined, - .data = undefined, - }); + try self.assemble(.syscall, .{}); } else if (mem.indexOf(u8, ins, "push")) |_| { const arg = ins[4..]; if (mem.indexOf(u8, arg, "$")) |l| { const n = std.fmt.parseInt(u8, ins[4 + l + 1 ..], 10) catch { return self.fail("TODO implement more inline asm int parsing", .{}); }; - _ = try self.addInst(.{ - .tag = .push, - .ops = Mir.Inst.Ops.encode(.{ .flags = 0b10 }), - .data = .{ .imm = n }, + try self.assemble(.push, .{ + .op1 = .{ .imm = Mir.Operand.Immediate.u(n) }, }); } else if (mem.indexOf(u8, arg, "%%")) |l| { const reg_name = ins[4 + l + 2 ..]; const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - _ = try self.addInst(.{ - .tag = .push, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), - .data = undefined, + try self.assemble(.push, .{ + .op1 = .{ .reg = reg }, }); } else return self.fail("TODO more push operands", .{}); } else if (mem.indexOf(u8, ins, "pop")) |_| { @@ -5286,10 +5337,8 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { const reg_name = ins[3 + l + 2 ..]; const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - _ = try self.addInst(.{ - .tag = .pop, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), - .data = undefined, + try self.assemble(.pop, .{ + .op1 = .{ .reg = reg }, }); } else return self.fail("TODO more pop operands", .{}); } else { @@ -5433,39 +5482,40 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE switch (ty.zigTypeTag()) { .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => Mir.Inst.Tag.mov_f32, - .f64 => Mir.Inst.Tag.mov_f64, - else => return self.fail("TODO genSetStackArg for register for type {}", .{ty.fmtDebug()}), - }; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = switch (ty.tag()) { - .f32 => .esp, - .f64 => .rsp, - else => unreachable, - }, - .reg2 = reg.to128(), - .flags = 0b01, - }), - .data = .{ .disp = -stack_offset }, - }); + // const tag: Mir.Inst.Tag = switch (ty.tag()) { + // .f32 => Mir.Inst.Tag.mov_f32, + // .f64 => Mir.Inst.Tag.mov_f64, + // else => return self.fail("TODO genSetStackArg for register for type {}", .{ty.fmtDebug()}), + // }; + _ = reg; + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = switch (ty.tag()) { + // .f32 => .esp, + // .f64 => .rsp, + // else => unreachable, + // }, + // .reg2 = reg.to128(), + // .flags = 0b01, + // }), + // .data = .{ .disp = -stack_offset }, + // }); return; } return self.fail("TODO genSetStackArg for register with no intrinsics", .{}); }, else => { - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rsp, - .reg2 = registerAlias(reg, @intCast(u32, abi_size)), - .flags = 0b10, - }), - .data = .{ .disp = -stack_offset }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rsp, + // .reg2 = registerAlias(reg, @intCast(u32, abi_size)), + // .flags = 0b10, + // }), + // .data = .{ .disp = -stack_offset }, + // }); }, } }, @@ -5519,13 +5569,13 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl const overflow_bit_ty = ty.structFieldType(1); const overflow_bit_offset = ty.structFieldOffset(1, self.target.*); const tmp_reg = try self.register_manager.allocReg(null, gp); - _ = try self.addInst(.{ - .tag = .cond_set_byte, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = tmp_reg.to8(), - }), - .data = .{ .cc = ro.eflags }, - }); + // _ = try self.addInst(.{ + // .tag = .cond_set_byte, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = tmp_reg.to8(), + // }), + // .data = .{ .cc = ro.eflags }, + // }); return self.genSetStack( overflow_bit_ty, @@ -5539,72 +5589,74 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl return self.genSetStack(ty, stack_offset, .{ .register = reg }, opts); }, .immediate => |x_big| { + _ = x_big; const base_reg = opts.dest_stack_base orelse .rbp; + _ = base_reg; switch (abi_size) { 0 => { assert(ty.isError()); - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = -stack_offset, - .operand = @truncate(u32, x_big), - }); - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = base_reg, - .flags = 0b00, - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = -stack_offset, + // .operand = @truncate(u32, x_big), + // }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = base_reg, + // .flags = 0b00, + // }), + // .data = .{ .payload = payload }, + // }); }, 1, 2, 4 => { - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = -stack_offset, - .operand = @truncate(u32, x_big), - }); - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = base_reg, - .flags = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - else => unreachable, - }, - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = -stack_offset, + // .operand = @truncate(u32, x_big), + // }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = base_reg, + // .flags = switch (abi_size) { + // 1 => 0b00, + // 2 => 0b01, + // 4 => 0b10, + // else => unreachable, + // }, + // }), + // .data = .{ .payload = payload }, + // }); }, 8 => { // 64 bit write to memory would take two mov's anyways so we // insted just use two 32 bit writes to avoid register allocation { - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = -stack_offset + 4, - .operand = @truncate(u32, x_big >> 32), - }); - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = base_reg, - .flags = 0b10, - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = -stack_offset + 4, + // .operand = @truncate(u32, x_big >> 32), + // }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = base_reg, + // .flags = 0b10, + // }), + // .data = .{ .payload = payload }, + // }); } { - const payload = try self.addExtra(Mir.ImmPair{ - .dest_off = -stack_offset, - .operand = @truncate(u32, x_big), - }); - _ = try self.addInst(.{ - .tag = .mov_mem_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = base_reg, - .flags = 0b10, - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.ImmPair{ + // .dest_off = -stack_offset, + // .operand = @truncate(u32, x_big), + // }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = base_reg, + // .flags = 0b10, + // }), + // .data = .{ .payload = payload }, + // }); } }, else => { @@ -5622,24 +5674,24 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl switch (ty.zigTypeTag()) { .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => Mir.Inst.Tag.mov_f32, - .f64 => Mir.Inst.Tag.mov_f64, - else => return self.fail("TODO genSetStack for register for type {}", .{ty.fmtDebug()}), - }; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = switch (ty.tag()) { - .f32 => base_reg.to32(), - .f64 => base_reg.to64(), - else => unreachable, - }, - .reg2 = reg.to128(), - .flags = 0b01, - }), - .data = .{ .disp = -stack_offset }, - }); + // const tag: Mir.Inst.Tag = switch (ty.tag()) { + // .f32 => Mir.Inst.Tag.mov_f32, + // .f64 => Mir.Inst.Tag.mov_f64, + // else => return self.fail("TODO genSetStack for register for type {}", .{ty.fmtDebug()}), + // }; + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = switch (ty.tag()) { + // .f32 => base_reg.to32(), + // .f64 => base_reg.to64(), + // else => unreachable, + // }, + // .reg2 = reg.to128(), + // .flags = 0b01, + // }), + // .data = .{ .disp = -stack_offset }, + // }); return; } @@ -5706,15 +5758,15 @@ fn genInlineMemcpyRegisterRegister( while (remainder > 0) { const nearest_power_of_two = @as(u6, 1) << math.log2_int(u3, @intCast(u3, remainder)); - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_reg, - .reg2 = registerAlias(tmp_reg, nearest_power_of_two), - .flags = 0b10, - }), - .data = .{ .disp = -next_offset }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_reg, + // .reg2 = registerAlias(tmp_reg, nearest_power_of_two), + // .flags = 0b10, + // }), + // .data = .{ .disp = -next_offset }, + // }); if (nearest_power_of_two > 1) { try self.genShiftBinOpMir(.shr, ty, tmp_reg, .{ @@ -5726,15 +5778,15 @@ fn genInlineMemcpyRegisterRegister( next_offset -= nearest_power_of_two; } } else { - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_reg, - .reg2 = registerAlias(src_reg, @intCast(u32, abi_size)), - .flags = 0b10, - }), - .data = .{ .disp = -offset }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_reg, + // .reg2 = registerAlias(src_reg, @intCast(u32, abi_size)), + // .flags = 0b10, + // }), + // .data = .{ .disp = -offset }, + // }); } } @@ -5768,30 +5820,34 @@ fn genInlineMemcpy( const index_reg = regs[2].to64(); const count_reg = regs[3].to64(); const tmp_reg = regs[4].to8(); + _ = index_reg; + _ = tmp_reg; switch (dst_ptr) { .memory, .linker_load => { try self.loadMemPtrIntoRegister(dst_addr_reg, Type.usize, dst_ptr); }, .ptr_stack_offset, .stack_offset => |off| { - _ = try self.addInst(.{ - .tag = .lea, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_addr_reg.to64(), - .reg2 = opts.dest_stack_base orelse .rbp, - }), - .data = .{ .disp = -off }, - }); + _ = off; + // _ = try self.addInst(.{ + // .tag = .lea, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_addr_reg.to64(), + // .reg2 = opts.dest_stack_base orelse .rbp, + // }), + // .data = .{ .disp = -off }, + // }); }, .register => |reg| { - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(dst_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), - .reg2 = reg, - }), - .data = undefined, - }); + _ = reg; + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(dst_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), + // .reg2 = reg, + // }), + // .data = undefined, + // }); }, else => { return self.fail("TODO implement memcpy for setting stack when dest is {}", .{dst_ptr}); @@ -5803,24 +5859,26 @@ fn genInlineMemcpy( try self.loadMemPtrIntoRegister(src_addr_reg, Type.usize, src_ptr); }, .ptr_stack_offset, .stack_offset => |off| { - _ = try self.addInst(.{ - .tag = .lea, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = src_addr_reg.to64(), - .reg2 = opts.source_stack_base orelse .rbp, - }), - .data = .{ .disp = -off }, - }); + _ = off; + // _ = try self.addInst(.{ + // .tag = .lea, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = src_addr_reg.to64(), + // .reg2 = opts.source_stack_base orelse .rbp, + // }), + // .data = .{ .disp = -off }, + // }); }, .register => |reg| { - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(src_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), - .reg2 = reg, - }), - .data = undefined, - }); + _ = reg; + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(src_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), + // .reg2 = reg, + // }), + // .data = undefined, + // }); }, else => { return self.fail("TODO implement memcpy for setting stack when src is {}", .{src_ptr}); @@ -5830,73 +5888,73 @@ fn genInlineMemcpy( try self.genSetReg(Type.usize, count_reg, len); // mov index_reg, 0 - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - .data = .{ .imm = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), + // .data = .{ .imm = 0 }, + // }); // loop: // cmp count, 0 - const loop_start = try self.addInst(.{ - .tag = .cmp, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = count_reg }), - .data = .{ .imm = 0 }, - }); + // const loop_start = try self.addInst(.{ + // .tag = .cmp, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = count_reg }), + // .data = .{ .imm = 0 }, + // }); // je end - const loop_reloc = try self.addInst(.{ - .tag = .cond_jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .e, - } }, - }); + // const loop_reloc = try self.addInst(.{ + // .tag = .cond_jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst_cc = .{ + // .inst = undefined, + // .cc = .e, + // } }, + // }); // mov tmp, [addr + index_reg] - _ = try self.addInst(.{ - .tag = .mov_scale_src, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = tmp_reg.to8(), - .reg2 = src_addr_reg, - }), - .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, - }); + // _ = try self.addInst(.{ + // .tag = .mov_scale_src, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = tmp_reg.to8(), + // .reg2 = src_addr_reg, + // }), + // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, + // }); // mov [stack_offset + index_reg], tmp - _ = try self.addInst(.{ - .tag = .mov_scale_dst, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = dst_addr_reg, - .reg2 = tmp_reg.to8(), - }), - .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, - }); + // _ = try self.addInst(.{ + // .tag = .mov_scale_dst, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = dst_addr_reg, + // .reg2 = tmp_reg.to8(), + // }), + // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, + // }); // add index_reg, 1 - _ = try self.addInst(.{ - .tag = .add, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - .data = .{ .imm = 1 }, - }); + // _ = try self.addInst(.{ + // .tag = .add, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), + // .data = .{ .imm = 1 }, + // }); // sub count, 1 - _ = try self.addInst(.{ - .tag = .sub, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = count_reg }), - .data = .{ .imm = 1 }, - }); + // _ = try self.addInst(.{ + // .tag = .sub, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = count_reg }), + // .data = .{ .imm = 1 }, + // }); // jmp loop - _ = try self.addInst(.{ - .tag = .jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst = loop_start }, - }); + // _ = try self.addInst(.{ + // .tag = .jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst = loop_start }, + // }); // end: - try self.performReloc(loop_reloc); + // try self.performReloc(loop_reloc); } fn genInlineMemset( @@ -5927,24 +5985,26 @@ fn genInlineMemset( try self.loadMemPtrIntoRegister(addr_reg, Type.usize, dst_ptr); }, .ptr_stack_offset, .stack_offset => |off| { - _ = try self.addInst(.{ - .tag = .lea, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg.to64(), - .reg2 = opts.dest_stack_base orelse .rbp, - }), - .data = .{ .disp = -off }, - }); + _ = off; + // _ = try self.addInst(.{ + // .tag = .lea, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg.to64(), + // .reg2 = opts.dest_stack_base orelse .rbp, + // }), + // .data = .{ .disp = -off }, + // }); }, .register => |reg| { - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), - .reg2 = reg, - }), - .data = undefined, - }); + _ = reg; + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), + // .reg2 = reg, + // }), + // .data = undefined, + // }); }, else => { return self.fail("TODO implement memcpy for setting stack when dest is {}", .{dst_ptr}); @@ -5956,24 +6016,24 @@ fn genInlineMemset( // loop: // cmp index_reg, -1 - const loop_start = try self.addInst(.{ - .tag = .cmp, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = index_reg, - .flags = 0b11, - }), - .data = .{ .imm_s = -1 }, - }); + // const loop_start = try self.addInst(.{ + // .tag = .cmp, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = index_reg, + // .flags = 0b11, + // }), + // .data = .{ .imm_s = -1 }, + // }); // je end - const loop_reloc = try self.addInst(.{ - .tag = .cond_jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .e, - } }, - }); + // const loop_reloc = try self.addInst(.{ + // .tag = .cond_jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst_cc = .{ + // .inst = undefined, + // .cc = .e, + // } }, + // }); switch (value) { .immediate => |x| { @@ -5981,37 +6041,37 @@ fn genInlineMemset( return self.fail("TODO inline memset for value immediate larger than 32bits", .{}); } // mov byte ptr [rbp + index_reg + stack_offset], imm - _ = try self.addInst(.{ - .tag = .mov_mem_index_imm, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = addr_reg, - }), - .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDispImm.encode( - index_reg, - 0, - @intCast(u32, x), - )) }, - }); + // _ = try self.addInst(.{ + // .tag = .mov_mem_index_imm, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = addr_reg, + // }), + // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDispImm.encode( + // index_reg, + // 0, + // @intCast(u32, x), + // )) }, + // }); }, else => return self.fail("TODO inline memset for value of type {}", .{value}), } // sub index_reg, 1 - _ = try self.addInst(.{ - .tag = .sub, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - .data = .{ .imm = 1 }, - }); + // _ = try self.addInst(.{ + // .tag = .sub, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), + // .data = .{ .imm = 1 }, + // }); // jmp loop - _ = try self.addInst(.{ - .tag = .jmp, - .ops = Mir.Inst.Ops.encode(.{}), - .data = .{ .inst = loop_start }, - }); + // _ = try self.addInst(.{ + // .tag = .jmp, + // .ops = Mir.Inst.Ops.encode(.{}), + // .data = .{ .inst = loop_start }, + // }); // end: - try self.performReloc(loop_reloc); + // try self.performReloc(loop_reloc); } fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void { @@ -6023,14 +6083,14 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (off < std.math.minInt(i32) or off > std.math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } - _ = try self.addInst(.{ - .tag = .lea, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .reg2 = .rbp, - }), - .data = .{ .disp = -off }, - }); + // _ = try self.addInst(.{ + // .tag = .lea, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .reg2 = .rbp, + // }), + // .data = .{ .disp = -off }, + // }); }, .unreach, .none => return, // Nothing to do. .undef => { @@ -6046,34 +6106,30 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } }, .eflags => |cc| { - _ = try self.addInst(.{ - .tag = .cond_set_byte, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to8(), - }), - .data = .{ .cc = cc }, - }); + _ = cc; + // _ = try self.addInst(.{ + // .tag = .cond_set_byte, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to8(), + // }), + // .data = .{ .cc = cc }, + // }); }, .immediate => |x| { // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit // register is the fastest way to zero a register. if (x == 0) { - _ = try self.addInst(.{ - .tag = .xor, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to32(), - .reg2 = reg.to32(), - }), - .data = undefined, + try self.assemble(.xor, .{ + .op1 = .{ .reg = reg.to32() }, + .op2 = .{ .reg = reg.to32() }, }); return; } if (x <= math.maxInt(i32)) { // Next best case: if we set the lower four bytes, the upper four will be zeroed. - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }), - .data = .{ .imm = @intCast(u32, x) }, + try self.assemble(.mov, .{ + .op1 = .{ .reg = registerAlias(reg, abi_size) }, + .op2 = .{ .imm = Mir.Operand.Immediate.u(@intCast(u32, x)) }, }); return; } @@ -6084,12 +6140,12 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void // This encoding is, in fact, the *same* as the one used for 32-bit loads. The only // difference is that we set REX.W before the instruction, which extends the load to // 64-bit and uses the full bit-width of the register. - const payload = try self.addExtra(Mir.Imm64.encode(x)); - _ = try self.addInst(.{ - .tag = .movabs, - .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg.to64() }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.Imm64.encode(x)); + // _ = try self.addInst(.{ + // .tag = .movabs, + // .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg.to64() }), + // .data = .{ .payload = payload }, + // }); }, .register => |src_reg| { // If the registers are the same, nothing to do. @@ -6100,47 +6156,47 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .Int => switch (ty.intInfo(self.target.*).signedness) { .signed => { if (abi_size <= 4) { - _ = try self.addInst(.{ - .tag = .mov_sign_extend, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to64(), - .reg2 = registerAlias(src_reg, abi_size), - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .mov_sign_extend, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to64(), + // .reg2 = registerAlias(src_reg, abi_size), + // }), + // .data = undefined, + // }); return; } }, .unsigned => { if (abi_size <= 2) { - _ = try self.addInst(.{ - .tag = .mov_zero_extend, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to64(), - .reg2 = registerAlias(src_reg, abi_size), - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .mov_zero_extend, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to64(), + // .reg2 = registerAlias(src_reg, abi_size), + // }), + // .data = undefined, + // }); return; } }, }, .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => Mir.Inst.Tag.mov_f32, - .f64 => Mir.Inst.Tag.mov_f64, - else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}), - }; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to128(), - .reg2 = src_reg.to128(), - .flags = 0b10, - }), - .data = undefined, - }); + // const tag: Mir.Inst.Tag = switch (ty.tag()) { + // .f32 => Mir.Inst.Tag.mov_f32, + // .f64 => Mir.Inst.Tag.mov_f64, + // else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}), + // }; + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to128(), + // .reg2 = src_reg.to128(), + // .flags = 0b10, + // }), + // .data = undefined, + // }); return; } @@ -6149,14 +6205,14 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => {}, } - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .reg2 = registerAlias(src_reg, abi_size), - }), - .data = undefined, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .reg2 = registerAlias(src_reg, abi_size), + // }), + // .data = undefined, + // }); }, .linker_load => { switch (ty.zigTypeTag()) { @@ -6165,24 +6221,24 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void try self.loadMemPtrIntoRegister(base_reg, Type.usize, mcv); if (intrinsicsAllowed(self.target.*, ty)) { - const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => Mir.Inst.Tag.mov_f32, - .f64 => Mir.Inst.Tag.mov_f64, - else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), - }; + // const tag: Mir.Inst.Tag = switch (ty.tag()) { + // .f32 => Mir.Inst.Tag.mov_f32, + // .f64 => Mir.Inst.Tag.mov_f64, + // else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), + // }; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to128(), - .reg2 = switch (ty.tag()) { - .f32 => base_reg.to32(), - .f64 => base_reg.to64(), - else => unreachable, - }, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to128(), + // .reg2 = switch (ty.tag()) { + // .f32 => base_reg.to32(), + // .f64 => base_reg.to64(), + // else => unreachable, + // }, + // }), + // .data = .{ .disp = 0 }, + // }); return; } @@ -6190,15 +6246,15 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void }, else => { try self.loadMemPtrIntoRegister(reg, Type.usize, mcv); - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .reg2 = reg.to64(), - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .reg2 = reg.to64(), + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); }, } }, @@ -6208,24 +6264,24 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void try self.loadMemPtrIntoRegister(base_reg, Type.usize, mcv); if (intrinsicsAllowed(self.target.*, ty)) { - const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => Mir.Inst.Tag.mov_f32, - .f64 => Mir.Inst.Tag.mov_f64, - else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), - }; + // const tag: Mir.Inst.Tag = switch (ty.tag()) { + // .f32 => Mir.Inst.Tag.mov_f32, + // .f64 => Mir.Inst.Tag.mov_f64, + // else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), + // }; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to128(), - .reg2 = switch (ty.tag()) { - .f32 => base_reg.to32(), - .f64 => base_reg.to64(), - else => unreachable, - }, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to128(), + // .reg2 = switch (ty.tag()) { + // .f32 => base_reg.to32(), + // .f64 => base_reg.to64(), + // else => unreachable, + // }, + // }), + // .data = .{ .disp = 0 }, + // }); return; } @@ -6234,42 +6290,42 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => { if (x <= math.maxInt(i32)) { // mov reg, [ds:imm32] - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .flags = 0b01, - }), - .data = .{ .disp = @intCast(i32, x) }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .flags = 0b01, + // }), + // .data = .{ .disp = @intCast(i32, x) }, + // }); } else { // If this is RAX, we can use a direct load. // Otherwise, we need to load the address, then indirectly load the value. if (reg.id() == 0) { // movabs rax, ds:moffs64 - const payload = try self.addExtra(Mir.Imm64.encode(x)); - _ = try self.addInst(.{ - .tag = .movabs, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rax, - .flags = 0b01, // imm64 will become moffs64 - }), - .data = .{ .payload = payload }, - }); + // const payload = try self.addExtra(Mir.Imm64.encode(x)); + // _ = try self.addInst(.{ + // .tag = .movabs, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rax, + // .flags = 0b01, // imm64 will become moffs64 + // }), + // .data = .{ .payload = payload }, + // }); } else { // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue. try self.genSetReg(ty, reg, MCValue{ .immediate = x }); // mov reg, [reg + 0x0] - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .reg2 = reg.to64(), - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .reg2 = reg.to64(), + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); } } }, @@ -6289,15 +6345,16 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void 4 => 0b11, else => unreachable, }; - _ = try self.addInst(.{ - .tag = .mov_sign_extend, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to64(), - .reg2 = .rbp, - .flags = flags, - }), - .data = .{ .disp = -off }, - }); + _ = flags; + // _ = try self.addInst(.{ + // .tag = .mov_sign_extend, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to64(), + // .reg2 = .rbp, + // .flags = flags, + // }), + // .data = .{ .disp = -off }, + // }); return; } }, @@ -6308,38 +6365,39 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void 2 => 0b10, else => unreachable, }; - _ = try self.addInst(.{ - .tag = .mov_zero_extend, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to64(), - .reg2 = .rbp, - .flags = flags, - }), - .data = .{ .disp = -off }, - }); + _ = flags; + // _ = try self.addInst(.{ + // .tag = .mov_zero_extend, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to64(), + // .reg2 = .rbp, + // .flags = flags, + // }), + // .data = .{ .disp = -off }, + // }); return; } }, }, .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - const tag: Mir.Inst.Tag = switch (ty.tag()) { - .f32 => Mir.Inst.Tag.mov_f32, - .f64 => Mir.Inst.Tag.mov_f64, - else => return self.fail("TODO genSetReg from stack offset for {}", .{ty.fmtDebug()}), - }; - _ = try self.addInst(.{ - .tag = tag, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg.to128(), - .reg2 = switch (ty.tag()) { - .f32 => .ebp, - .f64 => .rbp, - else => unreachable, - }, - }), - .data = .{ .disp = -off }, - }); + // const tag: Mir.Inst.Tag = switch (ty.tag()) { + // .f32 => Mir.Inst.Tag.mov_f32, + // .f64 => Mir.Inst.Tag.mov_f64, + // else => return self.fail("TODO genSetReg from stack offset for {}", .{ty.fmtDebug()}), + // }; + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg.to128(), + // .reg2 = switch (ty.tag()) { + // .f32 => .ebp, + // .f64 => .rbp, + // else => unreachable, + // }, + // }), + // .data = .{ .disp = -off }, + // }); return; } return self.fail("TODO genSetReg from stack offset for float with no intrinsics", .{}); @@ -6347,15 +6405,15 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void else => {}, } - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = registerAlias(reg, abi_size), - .reg2 = .rbp, - .flags = 0b01, - }), - .data = .{ .disp = -off }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = registerAlias(reg, abi_size), + // .reg2 = .rbp, + // .flags = 0b01, + // }), + // .data = .{ .disp = -off }, + // }); }, } } @@ -6419,6 +6477,7 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { const src_ty = self.air.typeOf(ty_op.operand); const dst_ty = self.air.typeOfIndex(inst); const operand = try self.resolveInst(ty_op.operand); + _ = dst_ty; // move float src to ST(0) const stack_offset = switch (operand) { @@ -6433,34 +6492,35 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { break :blk offset; }, }; - _ = try self.addInst(.{ - .tag = .fld, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rbp, - .flags = switch (src_ty.abiSize(self.target.*)) { - 4 => 0b01, - 8 => 0b10, - else => |size| return self.fail("TODO load ST(0) with abiSize={}", .{size}), - }, - }), - .data = .{ .disp = -stack_offset }, - }); + _ = stack_offset; + // _ = try self.addInst(.{ + // .tag = .fld, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rbp, + // .flags = switch (src_ty.abiSize(self.target.*)) { + // 4 => 0b01, + // 8 => 0b10, + // else => |size| return self.fail("TODO load ST(0) with abiSize={}", .{size}), + // }, + // }), + // .data = .{ .disp = -stack_offset }, + // }); // convert const stack_dst = try self.allocRegOrMem(inst, false); - _ = try self.addInst(.{ - .tag = .fisttp, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = .rbp, - .flags = switch (dst_ty.abiSize(self.target.*)) { - 1...2 => 0b00, - 3...4 => 0b01, - 5...8 => 0b10, - else => |size| return self.fail("TODO convert float with abiSize={}", .{size}), - }, - }), - .data = .{ .disp = -stack_dst.stack_offset }, - }); + // _ = try self.addInst(.{ + // .tag = .fisttp, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = .rbp, + // .flags = switch (dst_ty.abiSize(self.target.*)) { + // 1...2 => 0b00, + // 3...4 => 0b01, + // 5...8 => 0b10, + // else => |size| return self.fail("TODO convert float with abiSize={}", .{size}), + // }, + // }), + // .data = .{ .disp = -stack_dst.stack_offset }, + // }); return self.finishAir(inst, stack_dst, .{ ty_op.operand, .none, .none }); } @@ -6551,15 +6611,15 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void { .linker_load, .memory => { const reg = try self.register_manager.allocReg(null, gp); try self.loadMemPtrIntoRegister(reg, src_ty, src_ptr); - _ = try self.addInst(.{ - .tag = .mov, - .ops = Mir.Inst.Ops.encode(.{ - .reg1 = reg, - .reg2 = reg, - .flags = 0b01, - }), - .data = .{ .disp = 0 }, - }); + // _ = try self.addInst(.{ + // .tag = .mov, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg1 = reg, + // .reg2 = reg, + // .flags = 0b01, + // }), + // .data = .{ .disp = 0 }, + // }); break :blk MCValue{ .register = reg }; }, else => break :blk src_ptr, diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index e69601c40b..15f41a943c 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -71,124 +71,53 @@ pub fn lowerMir(emit: *Emit) InnerError!void { const inst = @intCast(u32, index); try emit.code_offset_mapping.putNoClobber(emit.bin_file.allocator, inst, emit.code.items.len); switch (tag) { - // GPR instructions - .adc => try emit.mirArith(.adc, inst), - .add => try emit.mirArith(.add, inst), - .sub => try emit.mirArith(.sub, inst), - .xor => try emit.mirArith(.xor, inst), - .@"and" => try emit.mirArith(.@"and", inst), - .@"or" => try emit.mirArith(.@"or", inst), - .sbb => try emit.mirArith(.sbb, inst), - .cmp => try emit.mirArith(.cmp, inst), - .mov => try emit.mirArith(.mov, inst), + .adc, + .add, + .@"and", + .cbw, + .cwde, + .cdqe, + .cwd, + .cdq, + .cqo, + .cmp, + .div, + .fisttp, + .fld, + .idiv, + .imul, + .int3, + .mov, + .movsx, + .movzx, + .mul, + .nop, + .@"or", + .pop, + .push, + .ret, + .sal, + .sar, + .sbb, + .shl, + .shr, + .sub, + .syscall, + .@"test", + .ud2, + .xor, - .adc_mem_imm => try emit.mirArithMemImm(.adc, inst), - .add_mem_imm => try emit.mirArithMemImm(.add, inst), - .sub_mem_imm => try emit.mirArithMemImm(.sub, inst), - .xor_mem_imm => try emit.mirArithMemImm(.xor, inst), - .and_mem_imm => try emit.mirArithMemImm(.@"and", inst), - .or_mem_imm => try emit.mirArithMemImm(.@"or", inst), - .sbb_mem_imm => try emit.mirArithMemImm(.sbb, inst), - .cmp_mem_imm => try emit.mirArithMemImm(.cmp, inst), - .mov_mem_imm => try emit.mirArithMemImm(.mov, inst), - - .adc_scale_src => try emit.mirArithScaleSrc(.adc, inst), - .add_scale_src => try emit.mirArithScaleSrc(.add, inst), - .sub_scale_src => try emit.mirArithScaleSrc(.sub, inst), - .xor_scale_src => try emit.mirArithScaleSrc(.xor, inst), - .and_scale_src => try emit.mirArithScaleSrc(.@"and", inst), - .or_scale_src => try emit.mirArithScaleSrc(.@"or", inst), - .sbb_scale_src => try emit.mirArithScaleSrc(.sbb, inst), - .cmp_scale_src => try emit.mirArithScaleSrc(.cmp, inst), - .mov_scale_src => try emit.mirArithScaleSrc(.mov, inst), - - .adc_scale_dst => try emit.mirArithScaleDst(.adc, inst), - .add_scale_dst => try emit.mirArithScaleDst(.add, inst), - .sub_scale_dst => try emit.mirArithScaleDst(.sub, inst), - .xor_scale_dst => try emit.mirArithScaleDst(.xor, inst), - .and_scale_dst => try emit.mirArithScaleDst(.@"and", inst), - .or_scale_dst => try emit.mirArithScaleDst(.@"or", inst), - .sbb_scale_dst => try emit.mirArithScaleDst(.sbb, inst), - .cmp_scale_dst => try emit.mirArithScaleDst(.cmp, inst), - .mov_scale_dst => try emit.mirArithScaleDst(.mov, inst), - - .adc_scale_imm => try emit.mirArithScaleImm(.adc, inst), - .add_scale_imm => try emit.mirArithScaleImm(.add, inst), - .sub_scale_imm => try emit.mirArithScaleImm(.sub, inst), - .xor_scale_imm => try emit.mirArithScaleImm(.xor, inst), - .and_scale_imm => try emit.mirArithScaleImm(.@"and", inst), - .or_scale_imm => try emit.mirArithScaleImm(.@"or", inst), - .sbb_scale_imm => try emit.mirArithScaleImm(.sbb, inst), - .cmp_scale_imm => try emit.mirArithScaleImm(.cmp, inst), - .mov_scale_imm => try emit.mirArithScaleImm(.mov, inst), - - .adc_mem_index_imm => try emit.mirArithMemIndexImm(.adc, inst), - .add_mem_index_imm => try emit.mirArithMemIndexImm(.add, inst), - .sub_mem_index_imm => try emit.mirArithMemIndexImm(.sub, inst), - .xor_mem_index_imm => try emit.mirArithMemIndexImm(.xor, inst), - .and_mem_index_imm => try emit.mirArithMemIndexImm(.@"and", inst), - .or_mem_index_imm => try emit.mirArithMemIndexImm(.@"or", inst), - .sbb_mem_index_imm => try emit.mirArithMemIndexImm(.sbb, inst), - .cmp_mem_index_imm => try emit.mirArithMemIndexImm(.cmp, inst), - .mov_mem_index_imm => try emit.mirArithMemIndexImm(.mov, inst), - - .mov_sign_extend => try emit.mirMovSignExtend(inst), - .mov_zero_extend => try emit.mirMovZeroExtend(inst), - - .movabs => try emit.mirMovabs(inst), - - .fisttp => try emit.mirFisttp(inst), - .fld => try emit.mirFld(inst), - - .lea => try emit.mirLea(inst), - .lea_pic => try emit.mirLeaPic(inst), - - .shl => try emit.mirShift(.shl, inst), - .sal => try emit.mirShift(.sal, inst), - .shr => try emit.mirShift(.shr, inst), - .sar => try emit.mirShift(.sar, inst), - - .imul => try emit.mirMulDiv(.imul, inst), - .mul => try emit.mirMulDiv(.mul, inst), - .idiv => try emit.mirMulDiv(.idiv, inst), - .div => try emit.mirMulDiv(.div, inst), - .imul_complex => try emit.mirIMulComplex(inst), - - .cwd => try emit.mirCwd(inst), - - .push => try emit.mirPushPop(.push, inst), - .pop => try emit.mirPushPop(.pop, inst), - - .jmp => try emit.mirJmpCall(.jmp, inst), - .call => try emit.mirJmpCall(.call, inst), - - .cond_jmp => try emit.mirCondJmp(inst), - .cond_set_byte => try emit.mirCondSetByte(inst), - .cond_mov => try emit.mirCondMov(inst), - - .ret => try emit.mirRet(inst), - - .syscall => try emit.mirSyscall(), - - .@"test" => try emit.mirTest(inst), - - .ud => try emit.mirUndefinedInstruction(), - .interrupt => try emit.mirInterrupt(inst), - .nop => {}, // just skip it - - // SSE/AVX instructions - .mov_f64 => try emit.mirMovFloat(.movsd, inst), - .mov_f32 => try emit.mirMovFloat(.movss, inst), - - .add_f64 => try emit.mirAddFloat(.addsd, inst), - .add_f32 => try emit.mirAddFloat(.addss, inst), - - .cmp_f64 => try emit.mirCmpFloat(.ucomisd, inst), - .cmp_f32 => try emit.mirCmpFloat(.ucomiss, inst), + .addss, + .cmpss, + .movss, + .ucomiss, + .addsd, + .cmpsd, + .movsd, + .ucomisd, + => try emit.mirEncodeGeneric(tag, inst), // Pseudo-instructions - .call_extern => try emit.mirCallExtern(inst), - .dbg_line => try emit.mirDbgLine(inst), .dbg_prologue_end => try emit.mirDbgPrologueEnd(inst), .dbg_epilogue_begin => try emit.mirDbgEpilogueBegin(inst), @@ -196,9 +125,7 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .push_regs => try emit.mirPushPopRegisterList(.push, inst), .pop_regs => try emit.mirPushPopRegisterList(.pop, inst), - else => { - return emit.fail("Implement MIR->Emit lowering for x86_64 for pseudo-inst: {}", .{tag}); - }, + else => return emit.fail("Implement MIR->Emit lowering for x86_64 for pseudo-inst: {}", .{tag}), } } @@ -246,66 +173,57 @@ fn encode(emit: *Emit, mnemonic: Instruction.Mnemonic, ops: struct { return inst.encode(emit.code.writer()); } -fn mirUndefinedInstruction(emit: *Emit) InnerError!void { - return emit.encode(.ud2, .{}); -} +fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { + const mnemonic = inline for (@typeInfo(Instruction.Mnemonic).Enum.fields) |field| { + if (mem.eql(u8, field.name, @tagName(tag))) break @field(Instruction.Mnemonic, field.name); + } else unreachable; -fn mirInterrupt(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .interrupt); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => return emit.encode(.int3, .{}), - else => return emit.fail("TODO handle variant 0b{b} of interrupt instruction", .{ops.flags}), + var operands = [4]Instruction.Operand{ .none, .none, .none, .none }; + const ops = emit.mir.instructions.items(.ops)[inst]; + const data = emit.mir.instructions.items(.data)[inst]; + switch (ops) { + .none => {}, + .imm_s => operands[0] = .{ .imm = Immediate.s(data.imm_s) }, + .imm_u => operands[0] = .{ .imm = Immediate.u(data.imm_u) }, + .r => operands[0] = .{ .reg = data.r }, + .rr => operands[0..2].* = .{ + .{ .reg = data.rr.r1 }, + .{ .reg = data.rr.r2 }, + }, + .ri_s => operands[0..2].* = .{ + .{ .reg = data.ri_s.r1 }, + .{ .imm = Immediate.s(data.ri_s.imm) }, + }, + .ri_u => operands[0..2].* = .{ + .{ .reg = data.ri_u.r1 }, + .{ .imm = Immediate.u(data.ri_u.imm) }, + }, + else => unreachable, } + + return emit.encode(mnemonic, .{ + .op1 = operands[0], + .op2 = operands[1], + .op3 = operands[2], + .op4 = operands[3], + }); } -fn mirSyscall(emit: *Emit) InnerError!void { - return emit.encode(.syscall, .{}); -} - -fn mirPushPop(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - }); - }, - 0b01 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(.qword, .{ - .base = ops.reg1, - .disp = disp, - }) }, - }); - }, - 0b10 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.push, .{ - .op1 = .{ .imm = Immediate.u(imm) }, - }); - }, - 0b11 => unreachable, - } -} - -fn mirPushPopRegisterList(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); +fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; const save_reg_list = emit.mir.extraData(Mir.SaveRegisterList, payload).data; + const base = @intToEnum(Register, save_reg_list.base_reg); var disp: i32 = -@intCast(i32, save_reg_list.stack_end); const reg_list = Mir.RegisterList.fromInt(save_reg_list.register_list); const callee_preserved_regs = abi.getCalleePreservedRegs(emit.target.*); for (callee_preserved_regs) |reg| { if (reg_list.isSet(callee_preserved_regs, reg)) { const op1: Instruction.Operand = .{ .mem = Memory.sib(.qword, .{ - .base = ops.reg1, + .base = base, .disp = disp, }) }; const op2: Instruction.Operand = .{ .reg = reg }; - switch (mnemonic) { + switch (tag) { .push => try emit.encode(.mov, .{ .op1 = op1, .op2 = op2, @@ -321,858 +239,345 @@ fn mirPushPopRegisterList(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir } } -fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - const target = emit.mir.instructions.items(.data)[inst].inst; - const source = emit.code.items.len; - try emit.encode(mnemonic, .{ - .op1 = .{ .imm = Immediate.s(0) }, - }); - try emit.relocs.append(emit.bin_file.allocator, .{ - .source = source, - .target = target, - .offset = emit.code.items.len - 4, - .length = 5, - }); - }, - 0b01 => { - if (ops.reg1 == .none) { - const disp = emit.mir.instructions.items(.data)[inst].disp; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(.qword, .{ .disp = disp }) }, - }); - } - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - }); - }, - 0b10 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(.qword, .{ - .base = ops.reg1, - .disp = disp, - }) }, - }); - }, - 0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}), - } -} +// fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { +// const ops = emit.mir.instructions.items(.ops)[inst].decode(); +// switch (ops.flags) { +// 0b00 => { +// const target = emit.mir.instructions.items(.data)[inst].inst; +// const source = emit.code.items.len; +// try emit.encode(mnemonic, .{ +// .op1 = .{ .imm = Immediate.s(0) }, +// }); +// try emit.relocs.append(emit.bin_file.allocator, .{ +// .source = source, +// .target = target, +// .offset = emit.code.items.len - 4, +// .length = 5, +// }); +// }, +// 0b01 => { +// if (ops.reg1 == .none) { +// const disp = emit.mir.instructions.items(.data)[inst].disp; +// return emit.encode(mnemonic, .{ +// .op1 = .{ .mem = Memory.sib(.qword, .{ .disp = disp }) }, +// }); +// } +// return emit.encode(mnemonic, .{ +// .op1 = .{ .reg = ops.reg1 }, +// }); +// }, +// 0b10 => { +// const disp = emit.mir.instructions.items(.data)[inst].disp; +// return emit.encode(mnemonic, .{ +// .op1 = .{ .mem = Memory.sib(.qword, .{ +// .base = ops.reg1, +// .disp = disp, +// }) }, +// }); +// }, +// 0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}), +// } +// } -fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .cond_jmp); - const inst_cc = emit.mir.instructions.items(.data)[inst].inst_cc; - const mnemonic: Instruction.Mnemonic = switch (inst_cc.cc) { - .a => .ja, - .ae => .jae, - .b => .jb, - .be => .jbe, - .c => .jc, - .e => .je, - .g => .jg, - .ge => .jge, - .l => .jl, - .le => .jle, - .na => .jna, - .nae => .jnae, - .nb => .jnb, - .nbe => .jnbe, - .nc => .jnc, - .ne => .jne, - .ng => .jng, - .nge => .jnge, - .nl => .jnl, - .nle => .jnle, - .no => .jno, - .np => .jnp, - .ns => .jns, - .nz => .jnz, - .o => .jo, - .p => .jp, - .pe => .jpe, - .po => .jpo, - .s => .js, - .z => .jz, - }; - const source = emit.code.items.len; - try emit.encode(mnemonic, .{ - .op1 = .{ .imm = Immediate.s(0) }, - }); - try emit.relocs.append(emit.bin_file.allocator, .{ - .source = source, - .target = inst_cc.inst, - .offset = emit.code.items.len - 4, - .length = 6, - }); -} +// fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .cond_jmp); +// const inst_cc = emit.mir.instructions.items(.data)[inst].inst_cc; +// const mnemonic: Instruction.Mnemonic = switch (inst_cc.cc) { +// .a => .ja, +// .ae => .jae, +// .b => .jb, +// .be => .jbe, +// .c => .jc, +// .e => .je, +// .g => .jg, +// .ge => .jge, +// .l => .jl, +// .le => .jle, +// .na => .jna, +// .nae => .jnae, +// .nb => .jnb, +// .nbe => .jnbe, +// .nc => .jnc, +// .ne => .jne, +// .ng => .jng, +// .nge => .jnge, +// .nl => .jnl, +// .nle => .jnle, +// .no => .jno, +// .np => .jnp, +// .ns => .jns, +// .nz => .jnz, +// .o => .jo, +// .p => .jp, +// .pe => .jpe, +// .po => .jpo, +// .s => .js, +// .z => .jz, +// }; +// const source = emit.code.items.len; +// try emit.encode(mnemonic, .{ +// .op1 = .{ .imm = Immediate.s(0) }, +// }); +// try emit.relocs.append(emit.bin_file.allocator, .{ +// .source = source, +// .target = inst_cc.inst, +// .offset = emit.code.items.len - 4, +// .length = 6, +// }); +// } -fn mirCondSetByte(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .cond_set_byte); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const cc = emit.mir.instructions.items(.data)[inst].cc; - const mnemonic: Instruction.Mnemonic = switch (cc) { - .a => .seta, - .ae => .setae, - .b => .setb, - .be => .setbe, - .c => .setc, - .e => .sete, - .g => .setg, - .ge => .setge, - .l => .setl, - .le => .setle, - .na => .setna, - .nae => .setnae, - .nb => .setnb, - .nbe => .setnbe, - .nc => .setnc, - .ne => .setne, - .ng => .setng, - .nge => .setnge, - .nl => .setnl, - .nle => .setnle, - .no => .setno, - .np => .setnp, - .ns => .setns, - .nz => .setnz, - .o => .seto, - .p => .setp, - .pe => .setpe, - .po => .setpo, - .s => .sets, - .z => .setz, - }; - return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 } }); -} +// fn mirCondSetByte(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .cond_set_byte); +// const ops = emit.mir.instructions.items(.ops)[inst].decode(); +// const cc = emit.mir.instructions.items(.data)[inst].cc; +// const mnemonic: Instruction.Mnemonic = switch (cc) { +// .a => .seta, +// .ae => .setae, +// .b => .setb, +// .be => .setbe, +// .c => .setc, +// .e => .sete, +// .g => .setg, +// .ge => .setge, +// .l => .setl, +// .le => .setle, +// .na => .setna, +// .nae => .setnae, +// .nb => .setnb, +// .nbe => .setnbe, +// .nc => .setnc, +// .ne => .setne, +// .ng => .setng, +// .nge => .setnge, +// .nl => .setnl, +// .nle => .setnle, +// .no => .setno, +// .np => .setnp, +// .ns => .setns, +// .nz => .setnz, +// .o => .seto, +// .p => .setp, +// .pe => .setpe, +// .po => .setpo, +// .s => .sets, +// .z => .setz, +// }; +// return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 } }); +// } -fn mirCondMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .cond_mov); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const cc = emit.mir.instructions.items(.data)[inst].cc; - const mnemonic: Instruction.Mnemonic = switch (cc) { - .a => .cmova, - .ae => .cmovae, - .b => .cmovb, - .be => .cmovbe, - .c => .cmovc, - .e => .cmove, - .g => .cmovg, - .ge => .cmovge, - .l => .cmovl, - .le => .cmovle, - .na => .cmovna, - .nae => .cmovnae, - .nb => .cmovnb, - .nbe => .cmovnbe, - .nc => .cmovnc, - .ne => .cmovne, - .ng => .cmovng, - .nge => .cmovnge, - .nl => .cmovnl, - .nle => .cmovnle, - .no => .cmovno, - .np => .cmovnp, - .ns => .cmovns, - .nz => .cmovnz, - .o => .cmovo, - .p => .cmovp, - .pe => .cmovpe, - .po => .cmovpo, - .s => .cmovs, - .z => .cmovz, - }; - const op1: Instruction.Operand = .{ .reg = ops.reg1 }; +// fn mirCondMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .cond_mov); +// const ops = emit.mir.instructions.items(.ops)[inst].decode(); +// const cc = emit.mir.instructions.items(.data)[inst].cc; +// const mnemonic: Instruction.Mnemonic = switch (cc) { +// .a => .cmova, +// .ae => .cmovae, +// .b => .cmovb, +// .be => .cmovbe, +// .c => .cmovc, +// .e => .cmove, +// .g => .cmovg, +// .ge => .cmovge, +// .l => .cmovl, +// .le => .cmovle, +// .na => .cmovna, +// .nae => .cmovnae, +// .nb => .cmovnb, +// .nbe => .cmovnbe, +// .nc => .cmovnc, +// .ne => .cmovne, +// .ng => .cmovng, +// .nge => .cmovnge, +// .nl => .cmovnl, +// .nle => .cmovnle, +// .no => .cmovno, +// .np => .cmovnp, +// .ns => .cmovns, +// .nz => .cmovnz, +// .o => .cmovo, +// .p => .cmovp, +// .pe => .cmovpe, +// .po => .cmovpo, +// .s => .cmovs, +// .z => .cmovz, +// }; +// const op1: Instruction.Operand = .{ .reg = ops.reg1 }; - if (ops.flags == 0b00) { - return emit.encode(mnemonic, .{ - .op1 = op1, - .op2 = .{ .reg = ops.reg2 }, - }); - } - const disp = emit.mir.instructions.items(.data)[inst].disp; - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => unreachable, - 0b01 => .word, - 0b10 => .dword, - 0b11 => .qword, - }; - return emit.encode(mnemonic, .{ - .op1 = op1, - .op2 = .{ .mem = Memory.sib(ptr_size, .{ - .base = ops.reg2, - .disp = disp, - }) }, - }); -} +// if (ops.flags == 0b00) { +// return emit.encode(mnemonic, .{ +// .op1 = op1, +// .op2 = .{ .reg = ops.reg2 }, +// }); +// } +// const disp = emit.mir.instructions.items(.data)[inst].disp; +// const ptr_size: Memory.PtrSize = switch (ops.flags) { +// 0b00 => unreachable, +// 0b01 => .word, +// 0b10 => .dword, +// 0b11 => .qword, +// }; +// return emit.encode(mnemonic, .{ +// .op1 = op1, +// .op2 = .{ .mem = Memory.sib(ptr_size, .{ +// .base = ops.reg2, +// .disp = disp, +// }) }, +// }); +// } -fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .@"test"); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - if (ops.reg2 == .none) { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.@"test", .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = Immediate.u(imm) }, - }); - } - return emit.encode(.@"test", .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - else => return emit.fail("TODO more TEST alternatives", .{}), - } -} +// fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .lea); +// const ops = emit.mir.instructions.items(.ops)[inst].decode(); +// switch (ops.flags) { +// 0b00 => { +// const disp = emit.mir.instructions.items(.data)[inst].disp; +// const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; +// return emit.encode(.lea, .{ +// .op1 = .{ .reg = ops.reg1 }, +// .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ +// .base = src_reg, +// .disp = disp, +// }) }, +// }); +// }, +// 0b01 => { +// const start_offset = emit.code.items.len; +// try emit.encode(.lea, .{ +// .op1 = .{ .reg = ops.reg1 }, +// .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, +// }); +// const end_offset = emit.code.items.len; +// // Backpatch the displacement +// const payload = emit.mir.instructions.items(.data)[inst].payload; +// const imm = emit.mir.extraData(Mir.Imm64, payload).data.decode(); +// const disp = @intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset)); +// mem.writeIntLittle(i32, emit.code.items[end_offset - 4 ..][0..4], disp); +// }, +// 0b10 => { +// const payload = emit.mir.instructions.items(.data)[inst].payload; +// const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); +// const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; +// const scale_index = Memory.ScaleIndex{ +// .scale = 1, +// .index = index_reg_disp.index, +// }; +// return emit.encode(.lea, .{ +// .op1 = .{ .reg = ops.reg1 }, +// .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ +// .base = src_reg, +// .scale_index = scale_index, +// .disp = index_reg_disp.disp, +// }) }, +// }); +// }, +// 0b11 => return emit.fail("TODO unused LEA variant 0b11", .{}), +// } +// } -fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .ret); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => unreachable, - 0b01 => unreachable, - 0b10 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.ret, .{ - .op1 = .{ .imm = Immediate.u(imm) }, - }); - }, - 0b11 => { - return emit.encode(.ret, .{}); - }, - } -} +// fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .lea_pic); +// const ops = emit.mir.instructions.items(.ops)[inst].decode(); +// const relocation = emit.mir.instructions.items(.data)[inst].relocation; -fn mirArith(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - if (ops.reg2 == .none) { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = Immediate.u(imm) }, - }); - } - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - 0b01 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - const base: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ - .base = base, - .disp = disp, - }) }, - }); - }, - 0b10 => { - if (ops.reg2 == .none) { - return emit.fail("TODO unused variant: mov reg1, none, 0b10", .{}); - } - const disp = emit.mir.instructions.items(.data)[inst].disp; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg2.bitSize()), .{ - .base = ops.reg1, - .disp = disp, - }) }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - 0b11 => { - const imm_s = emit.mir.instructions.items(.data)[inst].imm_s; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = Immediate.s(imm_s) }, - }); - }, - } -} +// switch (ops.flags) { +// 0b00, 0b01, 0b10 => {}, +// else => return emit.fail("TODO unused LEA PIC variant 0b11", .{}), +// } -fn mirArithMemImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - assert(ops.reg2 == .none); - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte, - 0b01 => .word, - 0b10 => .dword, - 0b11 => .qword, - }; - const imm = switch (ops.flags) { - 0b00 => @truncate(u8, imm_pair.operand), - 0b01 => @truncate(u16, imm_pair.operand), - 0b10, 0b11 => @truncate(u32, imm_pair.operand), - }; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(ptr_size, .{ - .disp = imm_pair.dest_off, - .base = ops.reg1, - }) }, - .op2 = .{ .imm = Immediate.u(imm) }, - }); -} +// try emit.encode(.lea, .{ +// .op1 = .{ .reg = ops.reg1 }, +// .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, +// }); -fn mirArithScaleSrc(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const scale = ops.flags; - const payload = emit.mir.instructions.items(.data)[inst].payload; - const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); - const scale_index = Memory.ScaleIndex{ - .scale = @as(u4, 1) << scale, - .index = index_reg_disp.index, - }; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ - .base = ops.reg2, - .scale_index = scale_index, - .disp = index_reg_disp.disp, - }) }, - }); -} +// const end_offset = emit.code.items.len; -fn mirArithScaleDst(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const scale = ops.flags; - const payload = emit.mir.instructions.items(.data)[inst].payload; - const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); - const scale_index = Memory.ScaleIndex{ - .scale = @as(u4, 1) << scale, - .index = index_reg_disp.index, - }; - assert(ops.reg2 != .none); - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg2.bitSize()), .{ - .base = ops.reg1, - .scale_index = scale_index, - .disp = index_reg_disp.disp, - }) }, - .op2 = .{ .reg = ops.reg2 }, - }); -} +// if (emit.bin_file.cast(link.File.MachO)) |macho_file| { +// const reloc_type = switch (ops.flags) { +// 0b00 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), +// 0b01 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), +// else => unreachable, +// }; +// const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; +// try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ +// .type = reloc_type, +// .target = .{ .sym_index = relocation.sym_index, .file = null }, +// .offset = @intCast(u32, end_offset - 4), +// .addend = 0, +// .pcrel = true, +// .length = 2, +// }); +// } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { +// const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; +// try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ +// .type = switch (ops.flags) { +// 0b00 => .got, +// 0b01 => .direct, +// 0b10 => .import, +// else => unreachable, +// }, +// .target = switch (ops.flags) { +// 0b00, 0b01 => .{ .sym_index = relocation.sym_index, .file = null }, +// 0b10 => coff_file.getGlobalByIndex(relocation.sym_index), +// else => unreachable, +// }, +// .offset = @intCast(u32, end_offset - 4), +// .addend = 0, +// .pcrel = true, +// .length = 2, +// }); +// } else { +// return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{}); +// } +// } -fn mirArithScaleImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const scale = ops.flags; - const payload = emit.mir.instructions.items(.data)[inst].payload; - const index_reg_disp_imm = emit.mir.extraData(Mir.IndexRegisterDispImm, payload).data.decode(); - const scale_index = Memory.ScaleIndex{ - .scale = @as(u4, 1) << scale, - .index = index_reg_disp_imm.index, - }; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(.qword, .{ - .base = ops.reg1, - .disp = index_reg_disp_imm.disp, - .scale_index = scale_index, - }) }, - .op2 = .{ .imm = Immediate.u(index_reg_disp_imm.imm) }, - }); -} +// fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .call_extern); +// const relocation = emit.mir.instructions.items(.data)[inst].relocation; -fn mirArithMemIndexImm(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - assert(ops.reg2 == .none); - const payload = emit.mir.instructions.items(.data)[inst].payload; - const index_reg_disp_imm = emit.mir.extraData(Mir.IndexRegisterDispImm, payload).data.decode(); - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte, - 0b01 => .word, - 0b10 => .dword, - 0b11 => .qword, - }; - const scale_index = Memory.ScaleIndex{ - .scale = 1, - .index = index_reg_disp_imm.index, - }; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(ptr_size, .{ - .disp = index_reg_disp_imm.disp, - .base = ops.reg1, - .scale_index = scale_index, - }) }, - .op2 = .{ .imm = Immediate.u(index_reg_disp_imm.imm) }, - }); -} +// const offset = blk: { +// // callq +// try emit.encode(.call, .{ +// .op1 = .{ .imm = Immediate.s(0) }, +// }); +// break :blk @intCast(u32, emit.code.items.len) - 4; +// }; -fn mirMovSignExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .mov_sign_extend); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const disp = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].disp else undefined; - switch (ops.flags) { - 0b00 => { - const mnemonic: Instruction.Mnemonic = if (ops.reg2.bitSize() == 32) .movsxd else .movsx; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - else => { - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b01 => .byte, - 0b10 => .word, - 0b11 => .dword, - else => unreachable, - }; - const mnemonic: Instruction.Mnemonic = if (ops.flags == 0b11) .movsxd else .movsx; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(ptr_size, .{ - .base = ops.reg2, - .disp = disp, - }) }, - }); - }, - } -} - -fn mirMovZeroExtend(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .mov_zero_extend); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const disp = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].disp else undefined; - switch (ops.flags) { - 0b00 => { - return emit.encode(.movzx, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - 0b01, 0b10 => { - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b01 => .byte, - 0b10 => .word, - else => unreachable, - }; - return emit.encode(.movzx, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(ptr_size, .{ - .disp = disp, - .base = ops.reg2, - }) }, - }); - }, - 0b11 => { - return emit.fail("TODO unused variant: movzx 0b11", .{}); - }, - } -} - -fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .movabs); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - const imm: u64 = if (ops.reg1.bitSize() == 64) blk: { - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm = emit.mir.extraData(Mir.Imm64, payload).data; - break :blk imm.decode(); - } else emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.mov, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = Immediate.u(imm) }, - }); - }, - 0b01 => { - if (ops.reg1 == .none) { - const imm: u64 = if (ops.reg2.bitSize() == 64) blk: { - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm = emit.mir.extraData(Mir.Imm64, payload).data; - break :blk imm.decode(); - } else emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.mov, .{ - .op1 = .{ .mem = Memory.moffs(ops.reg2, imm) }, - .op2 = .{ .reg = .rax }, - }); - } - const imm: u64 = if (ops.reg1.bitSize() == 64) blk: { - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm = emit.mir.extraData(Mir.Imm64, payload).data; - break :blk imm.decode(); - } else emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.mov, .{ - .op1 = .{ .reg = .rax }, - .op2 = .{ .mem = Memory.moffs(ops.reg1, imm) }, - }); - }, - else => return emit.fail("TODO unused movabs variant", .{}), - } -} - -fn mirFisttp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .fisttp); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .word, - 0b01 => .dword, - 0b10 => .qword, - else => unreachable, - }; - return emit.encode(.fisttp, .{ - .op1 = .{ .mem = Memory.sib(ptr_size, .{ - .base = ops.reg1, - .disp = emit.mir.instructions.items(.data)[inst].disp, - }) }, - }); -} - -fn mirFld(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .fld); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b01 => .dword, - 0b10 => .qword, - else => unreachable, - }; - return emit.encode(.fld, .{ - .op1 = .{ .mem = Memory.sib(ptr_size, .{ - .base = ops.reg1, - .disp = emit.mir.instructions.items(.data)[inst].disp, - }) }, - }); -} - -fn mirShift(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = Immediate.u(1) }, - }); - }, - 0b01 => { - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = .cl }, - }); - }, - 0b10 => { - const imm = @truncate(u8, emit.mir.instructions.items(.data)[inst].imm); - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .imm = Immediate.u(imm) }, - }); - }, - 0b11 => { - return emit.fail("TODO unused variant: SHIFT reg1, 0b11", .{}); - }, - } -} - -fn mirMulDiv(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - if (ops.reg1 != .none) { - assert(ops.reg2 == .none); - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - }); - } - assert(ops.reg2 != .none); - const disp = emit.mir.instructions.items(.data)[inst].disp; - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte, - 0b01 => .word, - 0b10 => .dword, - 0b11 => .qword, - }; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(ptr_size, .{ - .base = ops.reg2, - .disp = disp, - }) }, - }); -} - -fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .imul_complex); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return emit.encode(.imul, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - 0b01 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - return emit.encode(.imul, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(.qword, .{ - .base = src_reg, - .disp = disp, - }) }, - }); - }, - 0b10 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return emit.encode(.imul, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - .op3 = .{ .imm = Immediate.u(imm) }, - }); - }, - 0b11 => { - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; - return emit.encode(.imul, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(.qword, .{ - .base = ops.reg2, - .disp = imm_pair.dest_off, - }) }, - .op3 = .{ .imm = Immediate.u(imm_pair.operand) }, - }); - }, - } -} - -fn mirCwd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const mnemonic: Instruction.Mnemonic = switch (ops.flags) { - 0b00 => .cbw, - 0b01 => .cwd, - 0b10 => .cdq, - 0b11 => .cqo, - }; - return emit.encode(mnemonic, .{}); -} - -fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .lea); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - return emit.encode(.lea, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ - .base = src_reg, - .disp = disp, - }) }, - }); - }, - 0b01 => { - const start_offset = emit.code.items.len; - try emit.encode(.lea, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, - }); - const end_offset = emit.code.items.len; - // Backpatch the displacement - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm = emit.mir.extraData(Mir.Imm64, payload).data.decode(); - const disp = @intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset)); - mem.writeIntLittle(i32, emit.code.items[end_offset - 4 ..][0..4], disp); - }, - 0b10 => { - const payload = emit.mir.instructions.items(.data)[inst].payload; - const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); - const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; - const scale_index = Memory.ScaleIndex{ - .scale = 1, - .index = index_reg_disp.index, - }; - return emit.encode(.lea, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ - .base = src_reg, - .scale_index = scale_index, - .disp = index_reg_disp.disp, - }) }, - }); - }, - 0b11 => return emit.fail("TODO unused LEA variant 0b11", .{}), - } -} - -fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .lea_pic); - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - const relocation = emit.mir.instructions.items(.data)[inst].relocation; - - switch (ops.flags) { - 0b00, 0b01, 0b10 => {}, - else => return emit.fail("TODO unused LEA PIC variant 0b11", .{}), - } - - try emit.encode(.lea, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, - }); - - const end_offset = emit.code.items.len; - - if (emit.bin_file.cast(link.File.MachO)) |macho_file| { - const reloc_type = switch (ops.flags) { - 0b00 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), - 0b01 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), - else => unreachable, - }; - const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; - try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ - .type = reloc_type, - .target = .{ .sym_index = relocation.sym_index, .file = null }, - .offset = @intCast(u32, end_offset - 4), - .addend = 0, - .pcrel = true, - .length = 2, - }); - } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { - const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; - try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ - .type = switch (ops.flags) { - 0b00 => .got, - 0b01 => .direct, - 0b10 => .import, - else => unreachable, - }, - .target = switch (ops.flags) { - 0b00, 0b01 => .{ .sym_index = relocation.sym_index, .file = null }, - 0b10 => coff_file.getGlobalByIndex(relocation.sym_index), - else => unreachable, - }, - .offset = @intCast(u32, end_offset - 4), - .addend = 0, - .pcrel = true, - .length = 2, - }); - } else { - return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{}); - } -} - -// SSE/AVX instructions - -fn mirMovFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg2.bitSize()), .{ - .base = ops.reg2, - .disp = disp, - }) }, - }); - }, - 0b01 => { - const disp = emit.mir.instructions.items(.data)[inst].disp; - return emit.encode(mnemonic, .{ - .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ - .base = ops.reg1, - .disp = disp, - }) }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - 0b10 => { - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }), - } -} - -fn mirAddFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }), - } -} - -fn mirCmpFloat(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst].decode(); - switch (ops.flags) { - 0b00 => { - return emit.encode(mnemonic, .{ - .op1 = .{ .reg = ops.reg1 }, - .op2 = .{ .reg = ops.reg2 }, - }); - }, - else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, mnemonic }), - } -} - -// Pseudo-instructions - -fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .call_extern); - const relocation = emit.mir.instructions.items(.data)[inst].relocation; - - const offset = blk: { - // callq - try emit.encode(.call, .{ - .op1 = .{ .imm = Immediate.s(0) }, - }); - break :blk @intCast(u32, emit.code.items.len) - 4; - }; - - if (emit.bin_file.cast(link.File.MachO)) |macho_file| { - // Add relocation to the decl. - const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; - const target = macho_file.getGlobalByIndex(relocation.sym_index); - try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ - .type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), - .target = target, - .offset = offset, - .addend = 0, - .pcrel = true, - .length = 2, - }); - } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { - // Add relocation to the decl. - const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; - const target = coff_file.getGlobalByIndex(relocation.sym_index); - try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ - .type = .direct, - .target = target, - .offset = offset, - .addend = 0, - .pcrel = true, - .length = 2, - }); - } else { - return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{}); - } -} +// if (emit.bin_file.cast(link.File.MachO)) |macho_file| { +// // Add relocation to the decl. +// const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; +// const target = macho_file.getGlobalByIndex(relocation.sym_index); +// try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ +// .type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), +// .target = target, +// .offset = offset, +// .addend = 0, +// .pcrel = true, +// .length = 2, +// }); +// } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { +// // Add relocation to the decl. +// const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; +// const target = coff_file.getGlobalByIndex(relocation.sym_index); +// try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ +// .type = .direct, +// .target = target, +// .offset = offset, +// .addend = 0, +// .pcrel = true, +// .length = 2, +// }); +// } else { +// return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{}); +// } +// } fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .dbg_line); const payload = emit.mir.instructions.items(.data)[inst].payload; const dbg_line_column = emit.mir.extraData(Mir.DbgLineColumn, payload).data; log.debug("mirDbgLine", .{}); @@ -1230,8 +635,7 @@ fn dbgAdvancePCAndLine(emit: *Emit, line: u32, column: u32) InnerError!void { } fn mirDbgPrologueEnd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .dbg_prologue_end); + _ = inst; switch (emit.debug_output) { .dwarf => |dw| { try dw.setPrologueEnd(); @@ -1247,8 +651,7 @@ fn mirDbgPrologueEnd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } fn mirDbgEpilogueBegin(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .dbg_epilogue_begin); + _ = inst; switch (emit.debug_output) { .dwarf => |dw| { try dw.setEpilogueBegin(); diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 43c24216a8..5f4ed05deb 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -12,6 +12,8 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const bits = @import("bits.zig"); +const encoder = @import("encoder.zig"); + const Air = @import("../../Air.zig"); const CodeGen = @import("CodeGen.zig"); const IntegerBitSet = std.bit_set.IntegerBitSet; @@ -21,421 +23,239 @@ instructions: std.MultiArrayList(Inst).Slice, /// The meaning of this data is determined by `Inst.Tag` value. extra: []const u32, +pub const Mnemonic = encoder.Instruction.Mnemonic; +pub const Operand = encoder.Instruction.Operand; + pub const Inst = struct { tag: Tag, ops: Ops, - /// The meaning of this depends on `tag` and `ops`. data: Data, - pub const Tag = enum(u16) { - /// ops flags: form: - /// 0b00 reg1, reg2 - /// 0b00 reg1, imm32 - /// 0b01 reg1, [reg2 + imm32] - /// 0b01 reg1, [ds:imm32] - /// 0b10 [reg1 + imm32], reg2 - /// 0b11 reg1, imm_s - /// Notes: - /// * If reg2 is `none` then it means Data field `imm` is used as the immediate. - /// * When two imm32 values are required, Data field `payload` points at `ImmPair`. - adc, - - /// ops flags: form: - /// 0b00 byte ptr [reg1 + imm32], imm8 - /// 0b01 word ptr [reg1 + imm32], imm16 - /// 0b10 dword ptr [reg1 + imm32], imm32 - /// 0b11 qword ptr [reg1 + imm32], imm32 (sign-extended to imm64) - /// Notes: - /// * Uses `ImmPair` as payload - adc_mem_imm, - - /// form: reg1, [reg2 + scale*index + imm32] - /// ops flags scale - /// 0b00 1 - /// 0b01 2 - /// 0b10 4 - /// 0b11 8 - /// Notes: - /// * Uses `IndexRegisterDisp` as payload - adc_scale_src, - - /// form: [reg1 + scale*index + imm32], reg2 - /// ops flags scale - /// 0b00 1 - /// 0b01 2 - /// 0b10 4 - /// 0b11 8 - /// Notes: - /// * Uses `IndexRegisterDisp` payload. - adc_scale_dst, - - /// form: [reg1 + scale*rax + imm32], imm32 - /// ops flags scale - /// 0b00 1 - /// 0b01 2 - /// 0b10 4 - /// 0b11 8 - /// Notes: - /// * Uses `IndexRegisterDispImm` payload. - adc_scale_imm, - - /// ops flags: form: - /// 0b00 byte ptr [reg1 + index + imm32], imm8 - /// 0b01 word ptr [reg1 + index + imm32], imm16 - /// 0b10 dword ptr [reg1 + index + imm32], imm32 - /// 0b11 qword ptr [reg1 + index + imm32], imm32 (sign-extended to imm64) - /// Notes: - /// * Uses `IndexRegisterDispImm` payload. - adc_mem_index_imm, - - // The following instructions all have the same encoding as `adc`. - - add, - add_mem_imm, - add_scale_src, - add_scale_dst, - add_scale_imm, - add_mem_index_imm, - sub, - sub_mem_imm, - sub_scale_src, - sub_scale_dst, - sub_scale_imm, - sub_mem_index_imm, - xor, - xor_mem_imm, - xor_scale_src, - xor_scale_dst, - xor_scale_imm, - xor_mem_index_imm, - @"and", - and_mem_imm, - and_scale_src, - and_scale_dst, - and_scale_imm, - and_mem_index_imm, - @"or", - or_mem_imm, - or_scale_src, - or_scale_dst, - or_scale_imm, - or_mem_index_imm, - rol, - rol_mem_imm, - rol_scale_src, - rol_scale_dst, - rol_scale_imm, - rol_mem_index_imm, - ror, - ror_mem_imm, - ror_scale_src, - ror_scale_dst, - ror_scale_imm, - ror_mem_index_imm, - rcl, - rcl_mem_imm, - rcl_scale_src, - rcl_scale_dst, - rcl_scale_imm, - rcl_mem_index_imm, - rcr, - rcr_mem_imm, - rcr_scale_src, - rcr_scale_dst, - rcr_scale_imm, - rcr_mem_index_imm, - sbb, - sbb_mem_imm, - sbb_scale_src, - sbb_scale_dst, - sbb_scale_imm, - sbb_mem_index_imm, - cmp, - cmp_mem_imm, - cmp_scale_src, - cmp_scale_dst, - cmp_scale_imm, - cmp_mem_index_imm, - mov, - mov_mem_imm, - mov_scale_src, - mov_scale_dst, - mov_scale_imm, - mov_mem_index_imm, - - /// ops flags: form: - /// 0b00 reg1, reg2, - /// 0b01 reg1, byte ptr [reg2 + imm32] - /// 0b10 reg1, word ptr [reg2 + imm32] - /// 0b11 reg1, dword ptr [reg2 + imm32] - mov_sign_extend, - - /// ops flags: form: - /// 0b00 reg1, reg2 - /// 0b01 reg1, byte ptr [reg2 + imm32] - /// 0b10 reg1, word ptr [reg2 + imm32] - mov_zero_extend, - - /// ops flags: form: - /// 0b00 reg1, [reg2 + imm32] - /// 0b00 reg1, [ds:imm32] - /// 0b01 reg1, [rip + imm32] - /// 0b10 reg1, [reg2 + index + imm32] - /// Notes: - /// * 0b10 uses `IndexRegisterDisp` payload - lea, - - /// ops flags: form: - /// 0b00 reg1, [rip + reloc] // via GOT PIC - /// 0b01 reg1, [rip + reloc] // direct load PIC - /// 0b10 reg1, [rip + reloc] // via imports table PIC - /// Notes: - /// * `Data` contains `relocation` - lea_pic, - - /// ops flags: form: - /// 0b00 reg1, 1 - /// 0b01 reg1, .cl - /// 0b10 reg1, imm8 - /// Notes: - /// * If flags == 0b10, uses `imm`. - shl, - shl_mem_imm, - shl_scale_src, - shl_scale_dst, - shl_scale_imm, - shl_mem_index_imm, - sal, - sal_mem_imm, - sal_scale_src, - sal_scale_dst, - sal_scale_imm, - sal_mem_index_imm, - shr, - shr_mem_imm, - shr_scale_src, - shr_scale_dst, - shr_scale_imm, - shr_mem_index_imm, - sar, - sar_mem_imm, - sar_scale_src, - sar_scale_dst, - sar_scale_imm, - sar_mem_index_imm, - - /// ops flags: form: - /// 0b00 reg1 - /// 0b00 byte ptr [reg2 + imm32] - /// 0b01 word ptr [reg2 + imm32] - /// 0b10 dword ptr [reg2 + imm32] - /// 0b11 qword ptr [reg2 + imm32] - imul, - idiv, - mul, - div, - - /// ops flags: form: - /// 0b00 AX <- AL - /// 0b01 DX:AX <- AX - /// 0b10 EDX:EAX <- EAX - /// 0b11 RDX:RAX <- RAX - cwd, - - /// ops flags: form: - /// 0b00 reg1, reg2 - /// 0b01 reg1, [reg2 + imm32] - /// 0b01 reg1, [imm32] if reg2 is none - /// 0b10 reg1, reg2, imm32 - /// 0b11 reg1, [reg2 + imm32], imm32 - imul_complex, - - /// ops flags: form: - /// 0b00 reg1, imm64 - /// 0b01 rax, moffs64 - /// Notes: - /// * If reg1 is 64-bit, the immediate is 64-bit and stored - /// within extra data `Imm64`. - /// * For 0b01, reg1 (or reg2) need to be - /// a version of rax. If reg1 == .none, then reg2 == .rax, - /// or vice versa. - movabs, - - /// ops flags: form: - /// 0b00 word ptr [reg1 + imm32] - /// 0b01 dword ptr [reg1 + imm32] - /// 0b10 qword ptr [reg1 + imm32] - /// Notes: - /// * source is always ST(0) - /// * only supports memory operands as destination - fisttp, - - /// ops flags: form: - /// 0b01 dword ptr [reg1 + imm32] - /// 0b10 qword ptr [reg1 + imm32] - fld, - - /// ops flags: form: - /// 0b00 inst - /// 0b01 reg1 - /// 0b01 [imm32] if reg1 is none - /// 0b10 [reg1 + imm32] - jmp, - call, - - /// ops flags: - /// unused - /// Notes: - /// * uses `inst_cc` in Data. - cond_jmp, - - /// ops flags: - /// 0b00 reg1 - /// Notes: - /// * uses condition code (CC) stored as part of data - cond_set_byte, - - /// ops flags: - /// 0b00 reg1, reg2, - /// 0b01 reg1, word ptr [reg2 + imm] - /// 0b10 reg1, dword ptr [reg2 + imm] - /// 0b11 reg1, qword ptr [reg2 + imm] - /// Notes: - /// * uses condition code (CC) stored as part of data - cond_mov, - - /// ops flags: form: - /// 0b00 reg1 - /// 0b01 [reg1 + imm32] - /// 0b10 imm32 - /// Notes: - /// * If 0b10 is specified and the tag is push, pushes immediate onto the stack - /// using the mnemonic PUSH imm32. - push, - pop, - - /// ops flags: form: - /// 0b00 retf imm16 - /// 0b01 retf - /// 0b10 retn imm16 - /// 0b11 retn - ret, - - /// Fast system call - syscall, - - /// ops flags: form: - /// 0b00 reg1, imm32 if reg2 == .none - /// 0b00 reg1, reg2 - /// TODO handle more cases - @"test", - - /// Undefined Instruction - ud, - - /// Breakpoint form: - /// 0b00 int3 - interrupt, - - /// Nop - nop, - - /// SSE/AVX instructions - /// ops flags: form: - /// 0b00 reg1, qword ptr [reg2 + imm32] - /// 0b01 qword ptr [reg1 + imm32], reg2 - /// 0b10 reg1, reg2 - mov_f64, - mov_f32, - - /// ops flags: form: - /// 0b00 reg1, reg2 - add_f64, - add_f32, - - /// ops flags: form: - /// 0b00 reg1, reg2 - cmp_f64, - cmp_f32, - - /// Pseudo-instructions - /// call extern function - /// Notes: - /// * target of the call is stored as `relocation` in `Data` union. - call_extern, - - /// end of prologue - dbg_prologue_end, - - /// start of epilogue - dbg_epilogue_begin, - - /// update debug line - dbg_line, - - /// push registers - /// Uses `payload` field with `SaveRegisterList` as payload. - push_regs, - - /// pop registers - /// Uses `payload` field with `SaveRegisterList` as payload. - pop_regs, - }; - /// The position of an MIR instruction within the `Mir` instructions array. pub const Index = u32; - pub const Ops = packed struct { - reg1: u7, - reg2: u7, - flags: u2, + pub const Tag = enum(u8) { + /// Add with carry + adc, + /// Add + add, + /// Logical and + @"and", + /// Call + call, + /// Convert byte to word + cbw, + /// Convert word to doubleword + cwde, + /// Convert doubleword to quadword + cdqe, + /// Convert word to doubleword + cwd, + /// Convert doubleword to quadword + cdq, + /// Convert doubleword to quadword + cqo, + /// Logical compare + cmp, + /// Conditional move + cmovcc, + /// Unsigned division + div, + /// Store integer with truncation + fisttp, + /// Load floating-point value + fld, + /// Signed division + idiv, + /// Signed multiplication + imul, + /// + int3, + /// Conditional jump + jcc, + /// Jump + jmp, + /// Load effective address + lea, + /// Move + mov, + /// Move with sign extension + movsx, + /// Move with zero extension + movzx, + /// Multiply + mul, + /// No-op + nop, + /// Logical or + @"or", + /// Pop + pop, + /// Push + push, + /// Return + ret, + /// Arithmetic shift left + sal, + /// Arithmetic shift right + sar, + /// Integer subtraction with borrow + sbb, + /// Set byte on condition + setcc, + /// Logical shift left + shl, + /// Logical shift right + shr, + /// Subtract + sub, + /// Syscall + syscall, + /// Test condition + @"test", + /// Undefined instruction + ud2, + /// Logical exclusive-or + xor, - pub fn encode(vals: struct { - reg1: Register = .none, - reg2: Register = .none, - flags: u2 = 0b00, - }) Ops { - return .{ - .reg1 = @enumToInt(vals.reg1), - .reg2 = @enumToInt(vals.reg2), - .flags = vals.flags, - }; - } + /// Add single precision floating point + addss, + /// Compare scalar single-precision floating-point values + cmpss, + /// Move scalar single-precision floating-point value + movss, + /// Unordered compare scalar single-precision floating-point values + ucomiss, + /// Add double precision floating point + addsd, + /// Compare scalar double-precision floating-point values + cmpsd, + /// Move scalar double-precision floating-point value + movsd, + /// Unordered compare scalar double-precision floating-point values + ucomisd, - pub fn decode(ops: Ops) struct { - reg1: Register, - reg2: Register, - flags: u2, - } { - return .{ - .reg1 = @intToEnum(Register, ops.reg1), - .reg2 = @intToEnum(Register, ops.reg2), - .flags = ops.flags, - }; - } + /// End of prologue + dbg_prologue_end, + /// Start of epilogue + dbg_epilogue_begin, + /// Update debug line + /// Uses `payload` payload with data of type `DbgLineColumn`. + dbg_line, + /// Push registers + /// Uses `payload` payload with data of type `SaveRegisterList`. + push_regs, + /// Pop registers + /// Uses `payload` payload with data of type `SaveRegisterList`. + pop_regs, + }; + + pub const Ops = enum(u8) { + /// No data associated with this instruction (only mnemonic is used). + none, + /// Single register operand. + /// Uses `r` payload. + r, + /// Register, register operands. + /// Uses `rr` payload. + rr, + /// Register, register, register operands. + /// Uses `rrr` payload. + rrr, + /// Register, immediate (sign-extended) operands. + /// Uses `ri_s` payload. + ri_s, + /// Register, immediate (unsigned) operands. + /// Uses `ri_u` payload. + ri_u, + /// Register, 64-bit unsigned immediate operands. + /// Uses `rx` payload with payload type `Imm64`. + ri64, + /// Immediate (sign-extended) operand. + /// Uses `imm_s` payload. + imm_s, + /// Immediate (unsigned) operand. + /// Uses `imm_u` payload. + imm_u, + /// Relative displacement operand. + /// Uses `rel` payload. + rel, + /// Register, memory operands. + /// Uses `rx` payload. + rm, + /// Register, memory, immediate (unsigned) operands + /// Uses `rx` payload. + rmi_u, + /// Register, memory, immediate (sign-extended) operands + /// Uses `rx` payload. + rmi_s, + /// Memory, immediate (unsigned) operands. + /// Uses `payload` payload. + mi_u, + /// Memory, immediate (sign-extend) operands. + /// Uses `payload` payload. + mi_s, + /// Memory, register operands. + /// Uses `payload` payload. + mr, + /// Lea into register with linker relocation. + /// Uses `payload` payload with data of type `LeaRegisterReloc`. + lea_r_reloc, + /// References another Mir instruction directly. + /// Uses `inst` payload. + inst, + /// References another Mir instruction directly with condition code (CC). + /// Uses `inst_cc` payload. + inst_cc, + /// Uses `payload` payload with data of type `MemoryConditionCode`. + m_cc, + /// Uses `rx` payload with extra data of type `MemoryConditionCode`. + rm_cc, + /// Uses `reloc` payload. + reloc, }; - /// 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 { - /// Another instruction. + /// References another Mir instruction. inst: Index, - /// A 32-bit immediate value. - imm: u32, - /// A 32-bit signed immediate value. - imm_s: i32, - /// A 32-bit signed displacement value. - disp: i32, - /// A condition code for use with EFLAGS register. - cc: bits.Condition, - /// Another instruction with condition code. - /// Used by `cond_jmp`. + /// Another instruction with condition code (CC). + /// Used by `jcc`. inst_cc: struct { /// Another instruction. inst: Index, /// A condition code for use with EFLAGS register. cc: bits.Condition, }, + /// A 32-bit signed immediate value. + imm_s: i32, + /// A 32-bit unsigned immediate value. + imm_u: u32, + /// A 32-bit signed relative offset value. + rel: i32, + r: Register, + rr: struct { + r1: Register, + r2: Register, + }, + rrr: struct { + r1: Register, + r2: Register, + r3: Register, + }, + /// Register, signed immediate. + ri_s: struct { + r1: Register, + imm: i32, + }, + /// Register, unsigned immediate. + ri_u: struct { + r1: Register, + imm: u32, + }, + /// Register, followed by custom payload found in extra. + rx: struct { + r1: Register, + payload: u32, + }, /// Relocation for the linker where: /// * `atom_index` is the index of the source /// * `sym_index` is the index of the target @@ -458,62 +278,19 @@ pub const Inst = struct { } }; -pub const IndexRegisterDisp = struct { - /// Index register to use with SIB-based encoding - index: u32, - - /// Displacement value - disp: i32, - - pub fn encode(index: Register, disp: i32) IndexRegisterDisp { - return .{ - .index = @enumToInt(index), - .disp = disp, - }; - } - - pub fn decode(this: IndexRegisterDisp) struct { - index: Register, - disp: i32, - } { - return .{ - .index = @intToEnum(Register, this.index), - .disp = this.disp, - }; - } -}; - -/// TODO: would it be worth making `IndexRegisterDisp` and `IndexRegisterDispImm` a variable length list -/// instead of having two structs, one a superset of the other one? -pub const IndexRegisterDispImm = struct { - /// Index register to use with SIB-based encoding - index: u32, - - /// Displacement value - disp: i32, - - /// Immediate - imm: u32, - - pub fn encode(index: Register, disp: i32, imm: u32) IndexRegisterDispImm { - return .{ - .index = @enumToInt(index), - .disp = disp, - .imm = imm, - }; - } - - pub fn decode(this: IndexRegisterDispImm) struct { - index: Register, - disp: i32, - imm: u32, - } { - return .{ - .index = @intToEnum(Register, this.index), - .disp = this.disp, - .imm = this.imm, - }; - } +pub const LeaRegisterReloc = struct { + /// Destination register. + reg: Register, + /// Type of the load. + load_type: enum(u2) { + got, + direct, + import, + }, + /// Index of the containing atom. + atom_index: u32, + /// Index into the linker's symbol table. + sym_index: u32, }; /// Used in conjunction with `SaveRegisterList` payload to transfer a list of used registers @@ -557,16 +334,13 @@ pub const RegisterList = struct { }; pub const SaveRegisterList = struct { + /// Base register + base_reg: u32, /// Use `RegisterList` to populate. register_list: u32, stack_end: u32, }; -pub const ImmPair = struct { - dest_off: i32, - operand: u32, -}; - pub const Imm64 = struct { msb: u32, lsb: u32, diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index 925e3fe181..690f777c28 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -4,10 +4,6 @@ const math = std.math; const bits = @import("bits.zig"); const Encoding = @import("Encoding.zig"); -const Immediate = bits.Immediate; -const Memory = bits.Memory; -const Moffs = bits.Moffs; -const PtrSize = bits.PtrSize; const Register = bits.Register; pub const Instruction = struct { @@ -25,6 +21,9 @@ pub const Instruction = struct { mem: Memory, imm: Immediate, + pub const Memory = bits.Memory; + pub const Immediate = bits.Immediate; + /// Returns the bitsize of the operand. pub fn bitSize(op: Operand) u64 { return switch (op) { @@ -296,7 +295,7 @@ pub const Instruction = struct { try encoder.opcode_1byte(prefix); } - fn encodeMemory(encoding: Encoding, mem: Memory, operand: Operand, encoder: anytype) !void { + fn encodeMemory(encoding: Encoding, mem: Operand.Memory, operand: Operand, encoder: anytype) !void { const operand_enc = switch (operand) { .reg => |reg| reg.lowEnc(), .none => encoding.modRmExt(), @@ -379,7 +378,7 @@ pub const Instruction = struct { } } - fn encodeImm(imm: Immediate, kind: Encoding.Op, encoder: anytype) !void { + fn encodeImm(imm: Operand.Immediate, kind: Encoding.Op, encoder: anytype) !void { const raw = imm.asUnsigned(kind.bitSize()); switch (kind.bitSize()) { 8 => try encoder.imm8(@intCast(u8, raw)), From aa8fda799e64c02d44fe80d1297b5ec8ae6b7677 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 09:02:06 +0100 Subject: [PATCH 09/31] x86_64: split up assemble() into more declarative single-purpose helpers --- src/arch/x86_64/CodeGen.zig | 126 ++++++++++++++++++------------------ src/arch/x86_64/encoder.zig | 9 ++- 2 files changed, 66 insertions(+), 69 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index be8d07a2f6..6dacbadd8f 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -33,9 +33,11 @@ const errUnionPayloadOffset = codegen.errUnionPayloadOffset; const errUnionErrorOffset = codegen.errUnionErrorOffset; const Condition = bits.Condition; +const Immediate = bits.Immediate; +const Memory = bits.Memory; +const Register = bits.Register; const RegisterManager = abi.RegisterManager; const RegisterLock = RegisterManager.RegisterLock; -const Register = bits.Register; const gp = abi.RegisterClass.gp; const sse = abi.RegisterClass.sse; @@ -398,47 +400,58 @@ fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { return result; } -fn assemble(self: *Self, tag: Mir.Inst.Tag, args: struct { - op1: Mir.Operand = .none, - op2: Mir.Operand = .none, - op3: Mir.Operand = .none, - op4: Mir.Operand = .none, -}) !void { - const ops: Mir.Inst.Ops = blk: { - if (args.op1 == .none and args.op2 == .none and args.op3 == .none and args.op4 == .none) - break :blk .none; +fn asmNone(self: *Self, tag: Mir.Inst.Tag) !void { + _ = try self.addInst(.{ + .tag = tag, + .ops = .none, + .data = undefined, + }); +} - if (args.op1 == .reg and args.op2 == .reg) - break :blk .rr; - if (args.op1 == .reg and args.op2 == .imm) switch (args.op2.imm) { - .signed => break :blk .ri_s, - .unsigned => break :blk .ri_u, - }; - if (args.op1 == .reg) - break :blk .r; - if (args.op1 == .imm) switch (args.op1.imm) { - .signed => break :blk .imm_s, - .unsigned => break :blk .imm_u, // TODO 64bits - }; +fn asmRegister(self: *Self, tag: Mir.Inst.Tag, reg: Register) !void { + _ = try self.addInst(.{ + .tag = tag, + .ops = .r, + .data = .{ .r = reg }, + }); +} - unreachable; - }; +fn asmImmediate(self: *Self, tag: Mir.Inst.Tag, imm: Immediate) !void { + // TODO imm64 + const ops: Mir.Inst.Ops = if (imm == .signed) .imm_s else .imm_u; const data: Mir.Inst.Data = switch (ops) { - .none => undefined, - .imm_s => .{ .imm_s = args.op1.imm.signed }, - .imm_u => .{ .imm_u = @intCast(u32, args.op1.imm.unsigned) }, - .r => .{ .r = args.op1.reg }, - .rr => .{ .rr = .{ - .r1 = args.op1.reg, - .r2 = args.op2.reg, + .imm_s => .{ .imm_s = imm.signed }, + .imm_u => .{ .imm_u = @intCast(u32, imm.unsigned) }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + +fn asmRegisterRegister(self: *Self, tag: Mir.Inst.Tag, reg1: Register, reg2: Register) !void { + _ = try self.addInst(.{ + .tag = tag, + .ops = .rr, + .data = .{ .rr = .{ + .r1 = reg1, + .r2 = reg2, } }, + }); +} + +fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.Tag, reg: Register, imm: Immediate) !void { + const ops: Mir.Inst.Ops = if (imm == .signed) .ri_s else .ri_u; + const data: Mir.Inst.Data = switch (ops) { .ri_s => .{ .ri_s = .{ - .r1 = args.op1.reg, - .imm = args.op2.imm.signed, + .r1 = reg, + .imm = imm.signed, } }, .ri_u => .{ .ri_u = .{ - .r1 = args.op1.reg, - .imm = @intCast(u32, args.op2.imm.unsigned), + .r1 = reg, + .imm = @intCast(u32, imm.unsigned), } }, else => unreachable, }; @@ -452,13 +465,8 @@ fn assemble(self: *Self, tag: Mir.Inst.Tag, args: struct { fn gen(self: *Self) InnerError!void { const cc = self.fn_type.fnCallingConvention(); if (cc != .Naked) { - try self.assemble(.push, .{ - .op1 = .{ .reg = .rbp }, - }); - try self.assemble(.mov, .{ - .op1 = .{ .reg = .rbp }, - .op2 = .{ .reg = .rsp }, - }); + try self.asmRegister(.push, .rbp); + try self.asmRegisterRegister(.mov, .rbp, .rsp); // We want to subtract the aligned stack frame size from rsp here, but we don't // yet know how big it will be, so we leave room for a 4-byte stack size. @@ -541,8 +549,8 @@ fn gen(self: *Self) InnerError!void { .data = undefined, }); - try self.assemble(.pop, .{ .op1 = .{ .reg = .rbp } }); - try self.assemble(.ret, .{}); + try self.asmRegister(.pop, .rbp); + try self.asmNone(.ret); // Adjust the stack if (self.max_end_stack > math.maxInt(i32)) { @@ -5313,23 +5321,19 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { var iter = std.mem.tokenize(u8, asm_source, "\n\r"); while (iter.next()) |ins| { if (mem.eql(u8, ins, "syscall")) { - try self.assemble(.syscall, .{}); + try self.asmNone(.syscall); } else if (mem.indexOf(u8, ins, "push")) |_| { const arg = ins[4..]; if (mem.indexOf(u8, arg, "$")) |l| { const n = std.fmt.parseInt(u8, ins[4 + l + 1 ..], 10) catch { return self.fail("TODO implement more inline asm int parsing", .{}); }; - try self.assemble(.push, .{ - .op1 = .{ .imm = Mir.Operand.Immediate.u(n) }, - }); + try self.asmImmediate(.push, Immediate.u(n)); } else if (mem.indexOf(u8, arg, "%%")) |l| { const reg_name = ins[4 + l + 2 ..]; const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - try self.assemble(.push, .{ - .op1 = .{ .reg = reg }, - }); + try self.asmRegister(.push, reg); } else return self.fail("TODO more push operands", .{}); } else if (mem.indexOf(u8, ins, "pop")) |_| { const arg = ins[3..]; @@ -5337,9 +5341,7 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { const reg_name = ins[3 + l + 2 ..]; const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - try self.assemble(.pop, .{ - .op1 = .{ .reg = reg }, - }); + try self.asmRegister(.pop, reg); } else return self.fail("TODO more pop operands", .{}); } else { return self.fail("TODO implement support for more x86 assembly instructions", .{}); @@ -6119,19 +6121,15 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit // register is the fastest way to zero a register. if (x == 0) { - try self.assemble(.xor, .{ - .op1 = .{ .reg = reg.to32() }, - .op2 = .{ .reg = reg.to32() }, - }); - return; + return self.asmRegisterRegister(.xor, reg.to32(), reg.to32()); } if (x <= math.maxInt(i32)) { // Next best case: if we set the lower four bytes, the upper four will be zeroed. - try self.assemble(.mov, .{ - .op1 = .{ .reg = registerAlias(reg, abi_size) }, - .op2 = .{ .imm = Mir.Operand.Immediate.u(@intCast(u32, x)) }, - }); - return; + return self.asmRegisterImmediate( + .mov, + registerAlias(reg, abi_size), + Immediate.u(@intCast(u32, x)), + ); } // Worst case: we need to load the 64-bit register with the IMM. GNU's assemblers calls // this `movabs`, though this is officially just a different variant of the plain `mov` diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index 690f777c28..292b61ee21 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -4,6 +4,8 @@ const math = std.math; const bits = @import("bits.zig"); const Encoding = @import("Encoding.zig"); +const Immediate = bits.Immediate; +const Memory = bits.Memory; const Register = bits.Register; pub const Instruction = struct { @@ -21,9 +23,6 @@ pub const Instruction = struct { mem: Memory, imm: Immediate, - pub const Memory = bits.Memory; - pub const Immediate = bits.Immediate; - /// Returns the bitsize of the operand. pub fn bitSize(op: Operand) u64 { return switch (op) { @@ -295,7 +294,7 @@ pub const Instruction = struct { try encoder.opcode_1byte(prefix); } - fn encodeMemory(encoding: Encoding, mem: Operand.Memory, operand: Operand, encoder: anytype) !void { + fn encodeMemory(encoding: Encoding, mem: Memory, operand: Operand, encoder: anytype) !void { const operand_enc = switch (operand) { .reg => |reg| reg.lowEnc(), .none => encoding.modRmExt(), @@ -378,7 +377,7 @@ pub const Instruction = struct { } } - fn encodeImm(imm: Operand.Immediate, kind: Encoding.Op, encoder: anytype) !void { + fn encodeImm(imm: Immediate, kind: Encoding.Op, encoder: anytype) !void { const raw = imm.asUnsigned(kind.bitSize()); switch (kind.bitSize()) { 8 => try encoder.imm8(@intCast(u8, raw)), From f61a70e812b0301f4e54e38ff4ce2b041f395e8d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 09:38:59 +0100 Subject: [PATCH 10/31] x86_64: handle encoding and decoding Imm64 unsigned --- src/arch/x86_64/CodeGen.zig | 37 +++++++++++++++++-------------------- src/arch/x86_64/Emit.zig | 8 +++++++- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 6dacbadd8f..d6dd63ee57 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -417,7 +417,6 @@ fn asmRegister(self: *Self, tag: Mir.Inst.Tag, reg: Register) !void { } fn asmImmediate(self: *Self, tag: Mir.Inst.Tag, imm: Immediate) !void { - // TODO imm64 const ops: Mir.Inst.Ops = if (imm == .signed) .imm_s else .imm_u; const data: Mir.Inst.Data = switch (ops) { .imm_s => .{ .imm_s = imm.signed }, @@ -443,7 +442,10 @@ fn asmRegisterRegister(self: *Self, tag: Mir.Inst.Tag, reg1: Register, reg2: Reg } fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.Tag, reg: Register, imm: Immediate) !void { - const ops: Mir.Inst.Ops = if (imm == .signed) .ri_s else .ri_u; + const ops: Mir.Inst.Ops = switch (imm) { + .signed => .ri_s, + .unsigned => |x| if (x <= math.maxInt(u32)) .ri_u else .ri64, + }; const data: Mir.Inst.Data = switch (ops) { .ri_s => .{ .ri_s = .{ .r1 = reg, @@ -453,6 +455,10 @@ fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.Tag, reg: Register, imm: Imme .r1 = reg, .imm = @intCast(u32, imm.unsigned), } }, + .ri64 => .{ .rx = .{ + .r1 = reg, + .payload = try self.addExtra(Mir.Imm64.encode(imm.unsigned)), + } }, else => unreachable, }; _ = try self.addInst(.{ @@ -6118,32 +6124,23 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void // }); }, .immediate => |x| { - // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit - // register is the fastest way to zero a register. if (x == 0) { + // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit + // register is the fastest way to zero a register. return self.asmRegisterRegister(.xor, reg.to32(), reg.to32()); } - if (x <= math.maxInt(i32)) { - // Next best case: if we set the lower four bytes, the upper four will be zeroed. + if (ty.isSignedInt() and x <= math.maxInt(i32)) { return self.asmRegisterImmediate( .mov, registerAlias(reg, abi_size), - Immediate.u(@intCast(u32, x)), + Immediate.s(@intCast(i32, @bitCast(i64, x))), ); } - // Worst case: we need to load the 64-bit register with the IMM. GNU's assemblers calls - // this `movabs`, though this is officially just a different variant of the plain `mov` - // instruction. - // - // This encoding is, in fact, the *same* as the one used for 32-bit loads. The only - // difference is that we set REX.W before the instruction, which extends the load to - // 64-bit and uses the full bit-width of the register. - // const payload = try self.addExtra(Mir.Imm64.encode(x)); - // _ = try self.addInst(.{ - // .tag = .movabs, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg.to64() }), - // .data = .{ .payload = payload }, - // }); + return self.asmRegisterImmediate( + .mov, + registerAlias(reg, abi_size), + Immediate.u(x), + ); }, .register => |src_reg| { // If the registers are the same, nothing to do. diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 15f41a943c..35b8b59846 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -178,9 +178,10 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE if (mem.eql(u8, field.name, @tagName(tag))) break @field(Instruction.Mnemonic, field.name); } else unreachable; - var operands = [4]Instruction.Operand{ .none, .none, .none, .none }; const ops = emit.mir.instructions.items(.ops)[inst]; const data = emit.mir.instructions.items(.data)[inst]; + + var operands = [4]Instruction.Operand{ .none, .none, .none, .none }; switch (ops) { .none => {}, .imm_s => operands[0] = .{ .imm = Immediate.s(data.imm_s) }, @@ -198,6 +199,11 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE .{ .reg = data.ri_u.r1 }, .{ .imm = Immediate.u(data.ri_u.imm) }, }, + .ri64 => { + operands[0] = .{ .reg = data.rx.r1 }; + const imm64 = emit.mir.extraData(Mir.Imm64, data.rx.payload).data; + operands[1] = .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }; + }, else => unreachable, } From 7221cd8ec90b5f206cf0b2979ba165719e5a2f23 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 11:48:23 +0100 Subject: [PATCH 11/31] x86_64: add helpers for CMOVcc and SETcc at the MIR level --- src/arch/x86_64/CodeGen.zig | 91 ++++++++-------------- src/arch/x86_64/Emit.zig | 146 +++++++++++------------------------- src/arch/x86_64/Mir.zig | 22 +++++- 3 files changed, 94 insertions(+), 165 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index d6dd63ee57..829d6b83fd 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -400,6 +400,29 @@ fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { return result; } +fn asmSetCCRegister(self: *Self, reg: Register, cc: bits.Condition) !void { + _ = try self.addInst(.{ + .tag = .setcc, + .ops = .r_c, + .data = .{ .r_c = .{ + .r1 = reg, + .cc = cc, + } }, + }); +} + +fn asmCmovCCRegisterRegister(self: *Self, reg1: Register, reg2: Register, cc: bits.Condition) !void { + _ = try self.addInst(.{ + .tag = .cmovcc, + .ops = .rr_c, + .data = .{ .rr_c = .{ + .r1 = reg1, + .r2 = reg2, + .cc = cc, + } }, + }); +} + fn asmNone(self: *Self, tag: Mir.Inst.Tag) !void { _ = try self.addInst(.{ .tag = tag, @@ -1346,15 +1369,7 @@ fn airMin(self: *Self, inst: Air.Inst.Index) !void { .unsigned => .b, .signed => .l, }; - _ = cc; - // _ = try self.addInst(.{ - // .tag = .cond_mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_mcv.register, - // .reg2 = lhs_reg, - // }), - // .data = .{ .cc = cc }, - // }); + try self.asmCmovCCRegisterRegister(dst_mcv.register, lhs_reg, cc); break :result dst_mcv; }; @@ -1554,14 +1569,7 @@ fn genSetStackTruncatedOverflowCompare( .signed => .o, .unsigned => .c, }; - _ = cc; - // _ = try self.addInst(.{ - // .tag = .cond_set_byte, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = overflow_reg.to8(), - // }), - // .data = .{ .cc = cc }, - // }); + try self.asmSetCCRegister(overflow_reg.to8(), cc); const scratch_reg = temp_regs[1]; try self.genSetReg(extended_ty, scratch_reg, .{ .register = reg }); @@ -1574,12 +1582,7 @@ fn genSetStackTruncatedOverflowCompare( ); const eq_reg = temp_regs[2]; - // _ = try self.addInst(.{ - // .tag = .cond_set_byte, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = eq_reg.to8() }), - // .data = .{ .cc = .ne }, - // }); - + try self.asmSetCCRegister(eq_reg.to8(), .ne); try self.genBinOpMir( .@"or", Type.u8, @@ -1829,14 +1832,7 @@ fn genInlineIntDivFloor(self: *Self, ty: Type, lhs: MCValue, rhs: MCValue) !MCVa // }), // .data = undefined, // }); - // _ = try self.addInst(.{ - // .tag = .cond_mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = divisor.to64(), - // .reg2 = .rdx, - // }), - // .data = .{ .cc = .e }, - // }); + try self.asmCmovCCRegisterRegister(divisor.to64(), .rdx, .e); try self.genBinOpMir(.add, Type.isize, .{ .register = divisor }, .{ .register = .rax }); return MCValue{ .register = divisor }; } @@ -2881,13 +2877,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type const overflow_bit_ty = value_ty.structFieldType(1); const overflow_bit_offset = value_ty.structFieldOffset(1, self.target.*); const tmp_reg = try self.register_manager.allocReg(null, gp); - // _ = try self.addInst(.{ - // .tag = .cond_set_byte, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = tmp_reg.to8(), - // }), - // .data = .{ .cc = ro.eflags }, - // }); + try self.asmSetCCRegister(tmp_reg.to8(), ro.eflags); try self.genInlineMemcpyRegisterRegister( overflow_bit_ty, reg, @@ -3185,13 +3175,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { defer self.register_manager.unlockReg(reg_lock); const dst_reg = try self.register_manager.allocReg(inst, gp); - // _ = try self.addInst(.{ - // .tag = .cond_set_byte, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_reg.to8(), - // }), - // .data = .{ .cc = ro.eflags }, - // }); + try self.asmSetCCRegister(dst_reg.to8(), ro.eflags); break :result MCValue{ .register = dst_reg.to8() }; }, else => unreachable, @@ -5577,13 +5561,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl const overflow_bit_ty = ty.structFieldType(1); const overflow_bit_offset = ty.structFieldOffset(1, self.target.*); const tmp_reg = try self.register_manager.allocReg(null, gp); - // _ = try self.addInst(.{ - // .tag = .cond_set_byte, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = tmp_reg.to8(), - // }), - // .data = .{ .cc = ro.eflags }, - // }); + try self.asmSetCCRegister(tmp_reg.to8(), ro.eflags); return self.genSetStack( overflow_bit_ty, @@ -6114,14 +6092,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } }, .eflags => |cc| { - _ = cc; - // _ = try self.addInst(.{ - // .tag = .cond_set_byte, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to8(), - // }), - // .data = .{ .cc = cc }, - // }); + return self.asmSetCCRegister(reg.to8(), cc); }, .immediate => |x| { if (x == 0) { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 35b8b59846..2b49a6051c 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -118,6 +118,9 @@ pub fn lowerMir(emit: *Emit) InnerError!void { => try emit.mirEncodeGeneric(tag, inst), // Pseudo-instructions + .cmovcc => try emit.mirCmovCC(inst), + .setcc => try emit.mirSetCC(inst), + .dbg_line => try emit.mirDbgLine(inst), .dbg_prologue_end => try emit.mirDbgPrologueEnd(inst), .dbg_epilogue_begin => try emit.mirDbgEpilogueBegin(inst), @@ -200,9 +203,11 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE .{ .imm = Immediate.u(data.ri_u.imm) }, }, .ri64 => { - operands[0] = .{ .reg = data.rx.r1 }; const imm64 = emit.mir.extraData(Mir.Imm64, data.rx.payload).data; - operands[1] = .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }; + operands[0..2].* = .{ + .{ .reg = data.rx.r1 }, + .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }, + }; }, else => unreachable, } @@ -215,6 +220,42 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE }); } +fn mnemonicFromCC(comptime basename: []const u8, cc: bits.Condition) Instruction.Mnemonic { + inline for (@typeInfo(bits.Condition).Enum.fields) |field| { + if (mem.eql(u8, field.name, @tagName(cc))) + return @field(Instruction.Mnemonic, basename ++ field.name); + } else unreachable; +} + +fn mirCmovCC(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const ops = emit.mir.instructions.items(.ops)[inst]; + switch (ops) { + .rr_c => { + const data = emit.mir.instructions.items(.data)[inst].rr_c; + const mnemonic = mnemonicFromCC("cmov", data.cc); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = data.r1 }, + .op2 = .{ .reg = data.r2 }, + }); + }, + else => unreachable, // TODO + } +} + +fn mirSetCC(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const ops = emit.mir.instructions.items(.ops)[inst]; + switch (ops) { + .r_c => { + const data = emit.mir.instructions.items(.data)[inst].r_c; + const mnemonic = mnemonicFromCC("set", data.cc); + return emit.encode(mnemonic, .{ + .op1 = .{ .reg = data.r1 }, + }); + }, + else => unreachable, // TODO + } +} + fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; const save_reg_list = emit.mir.extraData(Mir.SaveRegisterList, payload).data; @@ -333,107 +374,6 @@ fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) // }); // } -// fn mirCondSetByte(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .cond_set_byte); -// const ops = emit.mir.instructions.items(.ops)[inst].decode(); -// const cc = emit.mir.instructions.items(.data)[inst].cc; -// const mnemonic: Instruction.Mnemonic = switch (cc) { -// .a => .seta, -// .ae => .setae, -// .b => .setb, -// .be => .setbe, -// .c => .setc, -// .e => .sete, -// .g => .setg, -// .ge => .setge, -// .l => .setl, -// .le => .setle, -// .na => .setna, -// .nae => .setnae, -// .nb => .setnb, -// .nbe => .setnbe, -// .nc => .setnc, -// .ne => .setne, -// .ng => .setng, -// .nge => .setnge, -// .nl => .setnl, -// .nle => .setnle, -// .no => .setno, -// .np => .setnp, -// .ns => .setns, -// .nz => .setnz, -// .o => .seto, -// .p => .setp, -// .pe => .setpe, -// .po => .setpo, -// .s => .sets, -// .z => .setz, -// }; -// return emit.encode(mnemonic, .{ .op1 = .{ .reg = ops.reg1 } }); -// } - -// fn mirCondMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .cond_mov); -// const ops = emit.mir.instructions.items(.ops)[inst].decode(); -// const cc = emit.mir.instructions.items(.data)[inst].cc; -// const mnemonic: Instruction.Mnemonic = switch (cc) { -// .a => .cmova, -// .ae => .cmovae, -// .b => .cmovb, -// .be => .cmovbe, -// .c => .cmovc, -// .e => .cmove, -// .g => .cmovg, -// .ge => .cmovge, -// .l => .cmovl, -// .le => .cmovle, -// .na => .cmovna, -// .nae => .cmovnae, -// .nb => .cmovnb, -// .nbe => .cmovnbe, -// .nc => .cmovnc, -// .ne => .cmovne, -// .ng => .cmovng, -// .nge => .cmovnge, -// .nl => .cmovnl, -// .nle => .cmovnle, -// .no => .cmovno, -// .np => .cmovnp, -// .ns => .cmovns, -// .nz => .cmovnz, -// .o => .cmovo, -// .p => .cmovp, -// .pe => .cmovpe, -// .po => .cmovpo, -// .s => .cmovs, -// .z => .cmovz, -// }; -// const op1: Instruction.Operand = .{ .reg = ops.reg1 }; - -// if (ops.flags == 0b00) { -// return emit.encode(mnemonic, .{ -// .op1 = op1, -// .op2 = .{ .reg = ops.reg2 }, -// }); -// } -// const disp = emit.mir.instructions.items(.data)[inst].disp; -// const ptr_size: Memory.PtrSize = switch (ops.flags) { -// 0b00 => unreachable, -// 0b01 => .word, -// 0b10 => .dword, -// 0b11 => .qword, -// }; -// return emit.encode(mnemonic, .{ -// .op1 = op1, -// .op2 = .{ .mem = Memory.sib(ptr_size, .{ -// .base = ops.reg2, -// .disp = disp, -// }) }, -// }); -// } - // fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { // const tag = emit.mir.instructions.items(.tag)[inst]; // assert(tag == .lea); diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 5f4ed05deb..02bc70614d 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -56,8 +56,6 @@ pub const Inst = struct { cqo, /// Logical compare cmp, - /// Conditional move - cmovcc, /// Unsigned division div, /// Store integer with truncation @@ -134,6 +132,9 @@ pub const Inst = struct { /// Unordered compare scalar double-precision floating-point values ucomisd, + /// Conditional move + cmovcc, + /// End of prologue dbg_prologue_end, /// Start of epilogue @@ -161,6 +162,12 @@ pub const Inst = struct { /// Register, register, register operands. /// Uses `rrr` payload. rrr, + /// Register with condition code (CC). + /// Uses `r_c` payload. + r_c, + /// Register, register with condition code (CC). + /// Uses `rr_c` payload. + rr_c, /// Register, immediate (sign-extended) operands. /// Uses `ri_s` payload. ri_s, @@ -241,6 +248,17 @@ pub const Inst = struct { r2: Register, r3: Register, }, + /// Register with condition code (CC). + r_c: struct { + r1: Register, + cc: bits.Condition, + }, + /// Register, register with condition code (CC). + rr_c: struct { + r1: Register, + r2: Register, + cc: bits.Condition, + }, /// Register, signed immediate. ri_s: struct { r1: Register, From 1bde522c2c6cae52c581458774ad1dfa479d8426 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 15:03:41 +0100 Subject: [PATCH 12/31] x86_64: add helper for Jcc instruction --- src/arch/x86_64/CodeGen.zig | 247 ++++++++++++++---------------------- src/arch/x86_64/Emit.zig | 85 +++++-------- 2 files changed, 126 insertions(+), 206 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 829d6b83fd..711dc29724 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -4511,35 +4511,29 @@ fn genCondBrMir(self: *Self, ty: Type, mcv: MCValue) !u32 { const abi_size = ty.abiSize(self.target.*); switch (mcv) { .eflags => |cc| { - _ = cc; - // return self.addInst(.{ - // .tag = .cond_jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ - // .inst_cc = .{ - // .inst = undefined, - // // Here we map the opposites since the jump is to the false branch. - // .cc = cc.negate(), - // }, - // }, - // }); + return self.addInst(.{ + .tag = .jcc, + .ops = .inst_cc, + .data = .{ + .inst_cc = .{ + .inst = undefined, + // Here we map the opposites since the jump is to the false branch. + .cc = cc.negate(), + }, + }, + }); }, .register => |reg| { - _ = reg; try self.spillEflagsIfOccupied(); - // _ = try self.addInst(.{ - // .tag = .@"test", - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), - // .data = .{ .imm = 1 }, - // }); - // return self.addInst(.{ - // .tag = .cond_jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst_cc = .{ - // .inst = undefined, - // .cc = .e, - // } }, - // }); + try self.asmRegisterImmediate(.@"test", reg, Immediate.u(1)); + return self.addInst(.{ + .tag = .jcc, + .ops = .inst_cc, + .data = .{ .inst_cc = .{ + .inst = undefined, + .cc = .e, + } }, + }); }, .immediate, .stack_offset, @@ -4961,22 +4955,17 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u }, } - // _ = try self.addInst(.{ - // .tag = .@"test", - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(cond_reg, abi_size), - // .reg2 = registerAlias(cond_reg, abi_size), - // }), - // .data = undefined, - // }); - // return self.addInst(.{ - // .tag = .cond_jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst_cc = .{ - // .inst = undefined, - // .cc = .ne, - // } }, - // }); + const aliased_reg = registerAlias(cond_reg, abi_size); + try self.asmRegisterRegister(.@"test", aliased_reg, aliased_reg); + + return self.addInst(.{ + .tag = .jcc, + .ops = .inst_cc, + .data = .{ .inst_cc = .{ + .inst = undefined, + .cc = .ne, + } }, + }); }, .stack_offset => { try self.spillEflagsIfOccupied(); @@ -5189,9 +5178,9 @@ fn canonicaliseBranches(self: *Self, parent_branch: *Branch, canon_branch: *Bran fn performReloc(self: *Self, reloc: Mir.Inst.Index) !void { const next_inst = @intCast(u32, self.mir_instructions.len); switch (self.mir_instructions.items(.tag)[reloc]) { - // .cond_jmp => { - // self.mir_instructions.items(.data)[reloc].inst_cc.inst = next_inst; - // }, + .jcc => { + self.mir_instructions.items(.data)[reloc].inst_cc.inst = next_inst; + }, .jmp => { self.mir_instructions.items(.data)[reloc].inst = next_inst; }, @@ -5806,7 +5795,6 @@ fn genInlineMemcpy( const index_reg = regs[2].to64(); const count_reg = regs[3].to64(); const tmp_reg = regs[4].to8(); - _ = index_reg; _ = tmp_reg; switch (dst_ptr) { @@ -5825,15 +5813,11 @@ fn genInlineMemcpy( // }); }, .register => |reg| { - _ = reg; - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), - // .reg2 = reg, - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister( + .mov, + registerAlias(dst_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), + reg, + ); }, else => { return self.fail("TODO implement memcpy for setting stack when dest is {}", .{dst_ptr}); @@ -5856,15 +5840,11 @@ fn genInlineMemcpy( // }); }, .register => |reg| { - _ = reg; - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(src_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), - // .reg2 = reg, - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister( + .mov, + registerAlias(src_addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), + reg, + ); }, else => { return self.fail("TODO implement memcpy for setting stack when src is {}", .{src_ptr}); @@ -5873,30 +5853,24 @@ fn genInlineMemcpy( try self.genSetReg(Type.usize, count_reg, len); - // mov index_reg, 0 - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - // .data = .{ .imm = 0 }, - // }); + try self.asmRegisterImmediate(.mov, index_reg, Immediate.u(0)); - // loop: - // cmp count, 0 - // const loop_start = try self.addInst(.{ - // .tag = .cmp, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = count_reg }), - // .data = .{ .imm = 0 }, - // }); - - // je end - // const loop_reloc = try self.addInst(.{ - // .tag = .cond_jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst_cc = .{ - // .inst = undefined, - // .cc = .e, - // } }, - // }); + const loop_start = try self.addInst(.{ + .tag = .cmp, + .ops = .ri_u, + .data = .{ .ri_u = .{ + .r1 = count_reg, + .imm = 0, + } }, + }); + const loop_reloc = try self.addInst(.{ + .tag = .jcc, + .ops = .inst_cc, + .data = .{ .inst_cc = .{ + .inst = undefined, + .cc = .e, + } }, + }); // mov tmp, [addr + index_reg] // _ = try self.addInst(.{ @@ -5918,29 +5892,16 @@ fn genInlineMemcpy( // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, // }); - // add index_reg, 1 - // _ = try self.addInst(.{ - // .tag = .add, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - // .data = .{ .imm = 1 }, - // }); + try self.asmRegisterImmediate(.add, index_reg, Immediate.u(1)); + try self.asmRegisterImmediate(.sub, count_reg, Immediate.u(1)); - // sub count, 1 - // _ = try self.addInst(.{ - // .tag = .sub, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = count_reg }), - // .data = .{ .imm = 1 }, - // }); + _ = try self.addInst(.{ + .tag = .jmp, + .ops = .inst, + .data = .{ .inst = loop_start }, + }); - // jmp loop - // _ = try self.addInst(.{ - // .tag = .jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst = loop_start }, - // }); - - // end: - // try self.performReloc(loop_reloc); + try self.performReloc(loop_reloc); } fn genInlineMemset( @@ -5982,15 +5943,11 @@ fn genInlineMemset( // }); }, .register => |reg| { - _ = reg; - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), - // .reg2 = reg, - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister( + .mov, + registerAlias(addr_reg, @intCast(u32, @divExact(reg.bitSize(), 8))), + reg, + ); }, else => { return self.fail("TODO implement memcpy for setting stack when dest is {}", .{dst_ptr}); @@ -6000,26 +5957,23 @@ fn genInlineMemset( try self.genSetReg(Type.usize, index_reg, len); try self.genBinOpMir(.sub, Type.usize, .{ .register = index_reg }, .{ .immediate = 1 }); - // loop: - // cmp index_reg, -1 - // const loop_start = try self.addInst(.{ - // .tag = .cmp, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = index_reg, - // .flags = 0b11, - // }), - // .data = .{ .imm_s = -1 }, - // }); + const loop_start = try self.addInst(.{ + .tag = .cmp, + .ops = .ri_s, + .data = .{ .ri_s = .{ + .r1 = index_reg, + .imm = -1, + } }, + }); - // je end - // const loop_reloc = try self.addInst(.{ - // .tag = .cond_jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst_cc = .{ - // .inst = undefined, - // .cc = .e, - // } }, - // }); + const loop_reloc = try self.addInst(.{ + .tag = .jcc, + .ops = .inst_cc, + .data = .{ .inst_cc = .{ + .inst = undefined, + .cc = .e, + } }, + }); switch (value) { .immediate => |x| { @@ -6042,22 +5996,15 @@ fn genInlineMemset( else => return self.fail("TODO inline memset for value of type {}", .{value}), } - // sub index_reg, 1 - // _ = try self.addInst(.{ - // .tag = .sub, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = index_reg }), - // .data = .{ .imm = 1 }, - // }); + try self.asmRegisterImmediate(.sub, index_reg, Immediate.u(1)); - // jmp loop - // _ = try self.addInst(.{ - // .tag = .jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst = loop_start }, - // }); + _ = try self.addInst(.{ + .tag = .jmp, + .ops = .inst, + .data = .{ .inst = loop_start }, + }); - // end: - // try self.performReloc(loop_reloc); + try self.performReloc(loop_reloc); } fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 2b49a6051c..607fc00e41 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -1,4 +1,3 @@ -//! //! This file contains the functionality for lowering x86_64 MIR into //! machine code @@ -118,8 +117,9 @@ pub fn lowerMir(emit: *Emit) InnerError!void { => try emit.mirEncodeGeneric(tag, inst), // Pseudo-instructions - .cmovcc => try emit.mirCmovCC(inst), - .setcc => try emit.mirSetCC(inst), + .cmovcc => try emit.mirCmovcc(inst), + .setcc => try emit.mirSetcc(inst), + .jcc => try emit.mirJcc(inst), .dbg_line => try emit.mirDbgLine(inst), .dbg_prologue_end => try emit.mirDbgPrologueEnd(inst), @@ -220,19 +220,19 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE }); } -fn mnemonicFromCC(comptime basename: []const u8, cc: bits.Condition) Instruction.Mnemonic { +fn mnemonicFromConditionCode(comptime basename: []const u8, cc: bits.Condition) Instruction.Mnemonic { inline for (@typeInfo(bits.Condition).Enum.fields) |field| { if (mem.eql(u8, field.name, @tagName(cc))) return @field(Instruction.Mnemonic, basename ++ field.name); } else unreachable; } -fn mirCmovCC(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +fn mirCmovcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst]; switch (ops) { .rr_c => { const data = emit.mir.instructions.items(.data)[inst].rr_c; - const mnemonic = mnemonicFromCC("cmov", data.cc); + const mnemonic = mnemonicFromConditionCode("cmov", data.cc); return emit.encode(mnemonic, .{ .op1 = .{ .reg = data.r1 }, .op2 = .{ .reg = data.r2 }, @@ -242,12 +242,12 @@ fn mirCmovCC(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } -fn mirSetCC(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +fn mirSetcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst]; switch (ops) { .r_c => { const data = emit.mir.instructions.items(.data)[inst].r_c; - const mnemonic = mnemonicFromCC("set", data.cc); + const mnemonic = mnemonicFromConditionCode("set", data.cc); return emit.encode(mnemonic, .{ .op1 = .{ .reg = data.r1 }, }); @@ -256,6 +256,27 @@ fn mirSetCC(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } +fn mirJcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const ops = emit.mir.instructions.items(.ops)[inst]; + switch (ops) { + .inst_cc => { + const data = emit.mir.instructions.items(.data)[inst].inst_cc; + const mnemonic = mnemonicFromConditionCode("j", data.cc); + const source = emit.code.items.len; + try emit.encode(mnemonic, .{ + .op1 = .{ .imm = Immediate.s(0) }, + }); + try emit.relocs.append(emit.bin_file.allocator, .{ + .source = source, + .target = data.inst, + .offset = emit.code.items.len - 4, + .length = 6, + }); + }, + else => unreachable, // TODO + } +} + fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; const save_reg_list = emit.mir.extraData(Mir.SaveRegisterList, payload).data; @@ -326,54 +347,6 @@ fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) // } // } -// fn mirCondJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .cond_jmp); -// const inst_cc = emit.mir.instructions.items(.data)[inst].inst_cc; -// const mnemonic: Instruction.Mnemonic = switch (inst_cc.cc) { -// .a => .ja, -// .ae => .jae, -// .b => .jb, -// .be => .jbe, -// .c => .jc, -// .e => .je, -// .g => .jg, -// .ge => .jge, -// .l => .jl, -// .le => .jle, -// .na => .jna, -// .nae => .jnae, -// .nb => .jnb, -// .nbe => .jnbe, -// .nc => .jnc, -// .ne => .jne, -// .ng => .jng, -// .nge => .jnge, -// .nl => .jnl, -// .nle => .jnle, -// .no => .jno, -// .np => .jnp, -// .ns => .jns, -// .nz => .jnz, -// .o => .jo, -// .p => .jp, -// .pe => .jpe, -// .po => .jpo, -// .s => .js, -// .z => .jz, -// }; -// const source = emit.code.items.len; -// try emit.encode(mnemonic, .{ -// .op1 = .{ .imm = Immediate.s(0) }, -// }); -// try emit.relocs.append(emit.bin_file.allocator, .{ -// .source = source, -// .target = inst_cc.inst, -// .offset = emit.code.items.len - 4, -// .length = 6, -// }); -// } - // fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { // const tag = emit.mir.instructions.items(.tag)[inst]; // assert(tag == .lea); From 9658ab676643ef5c2457ac4908c180e05dc7f729 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 18:38:54 +0100 Subject: [PATCH 13/31] x86_64: handle all instructions without introducing Memory operand --- src/arch/x86_64/CodeGen.zig | 463 ++++++++++++------------------------ src/arch/x86_64/Emit.zig | 111 ++++++++- 2 files changed, 256 insertions(+), 318 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 711dc29724..f5ee4d99eb 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -400,7 +400,7 @@ fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 { return result; } -fn asmSetCCRegister(self: *Self, reg: Register, cc: bits.Condition) !void { +fn asmSetccRegister(self: *Self, reg: Register, cc: bits.Condition) !void { _ = try self.addInst(.{ .tag = .setcc, .ops = .r_c, @@ -411,7 +411,7 @@ fn asmSetCCRegister(self: *Self, reg: Register, cc: bits.Condition) !void { }); } -fn asmCmovCCRegisterRegister(self: *Self, reg1: Register, reg2: Register, cc: bits.Condition) !void { +fn asmCmovccRegisterRegister(self: *Self, reg1: Register, reg2: Register, cc: bits.Condition) !void { _ = try self.addInst(.{ .tag = .cmovcc, .ops = .rr_c, @@ -1369,7 +1369,7 @@ fn airMin(self: *Self, inst: Air.Inst.Index) !void { .unsigned => .b, .signed => .l, }; - try self.asmCmovCCRegisterRegister(dst_mcv.register, lhs_reg, cc); + try self.asmCmovccRegisterRegister(dst_mcv.register, lhs_reg, cc); break :result dst_mcv; }; @@ -1569,7 +1569,7 @@ fn genSetStackTruncatedOverflowCompare( .signed => .o, .unsigned => .c, }; - try self.asmSetCCRegister(overflow_reg.to8(), cc); + try self.asmSetccRegister(overflow_reg.to8(), cc); const scratch_reg = temp_regs[1]; try self.genSetReg(extended_ty, scratch_reg, .{ .register = reg }); @@ -1582,7 +1582,7 @@ fn genSetStackTruncatedOverflowCompare( ); const eq_reg = temp_regs[2]; - try self.asmSetCCRegister(eq_reg.to8(), .ne); + try self.asmSetccRegister(eq_reg.to8(), .ne); try self.genBinOpMir( .@"or", Type.u8, @@ -1725,26 +1725,10 @@ fn genIntMulDivOpMir( try self.genSetReg(ty, .rax, lhs); } - _ = signedness; - // switch (signedness) { - // .signed => { - // _ = try self.addInst(.{ - // .tag = .cwd, - // .ops = Mir.Inst.Ops.encode(.{ .flags = 0b11 }), - // .data = undefined, - // }); - // }, - // .unsigned => { - // _ = try self.addInst(.{ - // .tag = .xor, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rdx, - // .reg2 = .rdx, - // }), - // .data = undefined, - // }); - // }, - // } + switch (signedness) { + .signed => try self.asmNone(.cqo), + .unsigned => try self.asmRegisterRegister(.xor, .rdx, .rdx), + } const factor = switch (rhs) { .register => rhs, @@ -1754,35 +1738,28 @@ fn genIntMulDivOpMir( break :blk MCValue{ .register = reg }; }, }; - _ = factor; - _ = tag; - // switch (factor) { - // .register => |reg| { - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }), - // .data = undefined, - // }); - // }, - // .stack_offset => |off| { - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg2 = .rbp, - // .flags = switch (abi_size) { - // 1 => 0b00, - // 2 => 0b01, - // 4 => 0b10, - // 8 => 0b11, - // else => unreachable, - // }, - // }), - // .data = .{ .disp = -off }, - // }); - // }, - // else => unreachable, - // } + switch (factor) { + .register => |reg| try self.asmRegister(tag, reg), + .stack_offset => |off| { + _ = off; + // _ = try self.addInst(.{ + // .tag = tag, + // .ops = Mir.Inst.Ops.encode(.{ + // .reg2 = .rbp, + // .flags = switch (abi_size) { + // 1 => 0b00, + // 2 => 0b01, + // 4 => 0b10, + // 8 => 0b11, + // else => unreachable, + // }, + // }), + // .data = .{ .disp = -off }, + // }); + }, + else => unreachable, + } } /// Always returns a register. @@ -1808,31 +1785,10 @@ fn genInlineIntDivFloor(self: *Self, ty: Type, lhs: MCValue, rhs: MCValue) !MCVa .unsigned => .div, }, Type.isize, signedness, .{ .register = dividend }, .{ .register = divisor }); - // _ = try self.addInst(.{ - // .tag = .xor, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = divisor.to64(), - // .reg2 = dividend.to64(), - // }), - // .data = undefined, - // }); - // _ = try self.addInst(.{ - // .tag = .sar, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = divisor.to64(), - // .flags = 0b10, - // }), - // .data = .{ .imm = 63 }, - // }); - // _ = try self.addInst(.{ - // .tag = .@"test", - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rdx, - // .reg2 = .rdx, - // }), - // .data = undefined, - // }); - try self.asmCmovCCRegisterRegister(divisor.to64(), .rdx, .e); + try self.asmRegisterRegister(.xor, divisor.to64(), dividend.to64()); + try self.asmRegisterImmediate(.sar, divisor.to64(), Immediate.u(63)); + try self.asmRegisterRegister(.@"test", .rdx, .rdx); + try self.asmCmovccRegisterRegister(divisor.to64(), .rdx, .e); try self.genBinOpMir(.add, Type.isize, .{ .register = divisor }, .{ .register = .rax }); return MCValue{ .register = divisor }; } @@ -2877,7 +2833,7 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type const overflow_bit_ty = value_ty.structFieldType(1); const overflow_bit_offset = value_ty.structFieldOffset(1, self.target.*); const tmp_reg = try self.register_manager.allocReg(null, gp); - try self.asmSetCCRegister(tmp_reg.to8(), ro.eflags); + try self.asmSetccRegister(tmp_reg.to8(), ro.eflags); try self.genInlineMemcpyRegisterRegister( overflow_bit_ty, reg, @@ -3151,14 +3107,11 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { }; const field_size = @intCast(u32, struct_field_ty.abiSize(self.target.*)); if (signedness == .signed and field_size < 8) { - // _ = try self.addInst(.{ - // .tag = .mov_sign_extend, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_mcv.register, - // .reg2 = registerAlias(dst_mcv.register, field_size), - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister( + .movsx, + dst_mcv.register, + registerAlias(dst_mcv.register, field_size), + ); } break :result dst_mcv; @@ -3175,7 +3128,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { defer self.register_manager.unlockReg(reg_lock); const dst_reg = try self.register_manager.allocReg(inst, gp); - try self.asmSetCCRegister(dst_reg.to8(), ro.eflags); + try self.asmSetccRegister(dst_reg.to8(), ro.eflags); break :result MCValue{ .register = dst_reg.to8() }; }, else => unreachable, @@ -3211,25 +3164,7 @@ fn genShiftBinOpMir(self: *Self, tag: Mir.Inst.Tag, ty: Type, reg: Register, shi switch (shift) { .immediate => |imm| switch (imm) { 0 => return, - 1 => { - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }), - // .data = undefined, - // }); - return; - }, - else => { - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .flags = 0b10, - // }), - // .data = .{ .imm = @intCast(u8, imm) }, - // }); - return; - }, + else => return self.asmRegisterImmediate(tag, registerAlias(reg, abi_size), Immediate.u(imm)), }, .register => |shift_reg| { if (shift_reg == .rcx) break :blk; @@ -3240,16 +3175,8 @@ fn genShiftBinOpMir(self: *Self, tag: Mir.Inst.Tag, ty: Type, reg: Register, shi try self.register_manager.getReg(.rcx, null); try self.genSetReg(Type.u8, .rcx, shift); } - _ = abi_size; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .flags = 0b01, - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister(tag, registerAlias(reg, abi_size), .cl); } /// Result is always a register. @@ -3620,43 +3547,38 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .register => |src_reg| switch (dst_ty.zigTypeTag()) { .Float => { if (intrinsicsAllowed(self.target.*, dst_ty)) { - // const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) { - // .f32 => switch (mir_tag) { - // .add => Mir.Inst.Tag.add_f32, - // .cmp => Mir.Inst.Tag.cmp_f32, - // else => return self.fail("TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}), - // }, - // .f64 => switch (mir_tag) { - // .add => Mir.Inst.Tag.add_f64, - // .cmp => Mir.Inst.Tag.cmp_f64, - // else => return self.fail("TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}), - // }, - // else => return self.fail("TODO genBinOpMir for float register-register and type {}", .{dst_ty.fmtDebug()}), - // }; - // _ = try self.addInst(.{ - // .tag = actual_tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_reg.to128(), - // .reg2 = src_reg.to128(), - // }), - // .data = undefined, - // }); - return; + const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) { + .f32 => switch (mir_tag) { + .add => .addss, + .cmp => .cmpss, + else => return self.fail( + "TODO genBinOpMir for f32 register-register with MIR tag {}", + .{mir_tag}, + ), + }, + .f64 => switch (mir_tag) { + .add => .addsd, + .cmp => .cmpsd, + else => return self.fail( + "TODO genBinOpMir for f64 register-register with MIR tag {}", + .{mir_tag}, + ), + }, + else => return self.fail( + "TODO genBinOpMir for float register-register and type {}", + .{dst_ty.fmtDebug()}, + ), + }; + try self.asmRegisterRegister(actual_tag, dst_reg.to128(), src_reg.to128()); } return self.fail("TODO genBinOpMir for float register-register and no intrinsics", .{}); }, - else => { - _ = src_reg; - // _ = try self.addInst(.{ - // .tag = mir_tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_reg, abi_size), - // .reg2 = registerAlias(src_reg, abi_size), - // }), - // .data = undefined, - // }); - }, + else => try self.asmRegisterRegister( + mir_tag, + registerAlias(dst_reg, abi_size), + registerAlias(src_reg, abi_size), + ), }, .immediate => |imm| { _ = imm; @@ -3777,7 +3699,6 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu /// Does not support byte-size operands. fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: MCValue) InnerError!void { const abi_size = @intCast(u32, dst_ty.abiSize(self.target.*)); - _ = abi_size; switch (dst_mcv) { .none => unreachable, .undef => unreachable, @@ -3792,18 +3713,11 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M .dead, .unreach => unreachable, .ptr_stack_offset => unreachable, .register_overflow => unreachable, - .register => |src_reg| { - _ = src_reg; - // register, register - // _ = try self.addInst(.{ - // .tag = .imul_complex, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_reg, abi_size), - // .reg2 = registerAlias(src_reg, abi_size), - // }), - // .data = undefined, - // }); - }, + .register => |src_reg| try self.asmRegisterRegister( + .imul, + registerAlias(dst_reg, abi_size), + registerAlias(src_reg, abi_size), + ), .immediate => |imm| { // TODO take into account the type's ABI size when selecting the register alias // register, immediate @@ -3992,20 +3906,12 @@ fn genVarDbgInfo( } fn airTrap(self: *Self) !void { - // _ = try self.addInst(.{ - // .tag = .ud, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = undefined, - // }); + try self.asmNone(.ud2); return self.finishAirBookkeeping(); } fn airBreakpoint(self: *Self) !void { - // _ = try self.addInst(.{ - // .tag = .interrupt, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = undefined, - // }); + try self.asmNone(.int3); return self.finishAirBookkeeping(); } @@ -4117,13 +4023,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier if (self.bin_file.cast(link.File.Elf)) |elf_file| { const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl); const atom = elf_file.getAtom(atom_index); - const got_addr = @intCast(i32, atom.getOffsetTableAddress(elf_file)); - _ = got_addr; - // _ = try self.addInst(.{ - // .tag = .call, - // .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), - // .data = .{ .disp = got_addr }, - // }); + const got_addr = atom.getOffsetTableAddress(elf_file); + try self.asmImmediate(.call, Immediate.s(@intCast(i32, got_addr))); } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { const atom_index = try coff_file.getOrCreateAtomForDecl(func.owner_decl); const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?; @@ -4133,14 +4034,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .sym_index = sym_index, }, }); - // _ = try self.addInst(.{ - // .tag = .call, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rax, - // .flags = 0b01, - // }), - // .data = undefined, - // }); + try self.asmRegister(.call, .rax); } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { const atom_index = try macho_file.getOrCreateAtomForDecl(func.owner_decl); const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?; @@ -4150,14 +4044,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .sym_index = sym_index, }, }); - // _ = try self.addInst(.{ - // .tag = .call, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rax, - // .flags = 0b01, - // }), - // .data = undefined, - // }); + try self.asmRegister(.call, .rax); } else if (self.bin_file.cast(link.File.Plan9)) |p9| { const decl_block_index = try p9.seeDecl(func.owner_decl); const decl_block = p9.getDeclBlock(decl_block_index); @@ -4166,12 +4053,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const got_addr = p9.bases.data; const got_index = decl_block.got_index.?; const fn_got_addr = got_addr + got_index * ptr_bytes; - _ = fn_got_addr; - // _ = try self.addInst(.{ - // .tag = .call, - // .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), - // .data = .{ .disp = @intCast(i32, fn_got_addr) }, - // }); + try self.asmImmediate(.call, Immediate.s(@intCast(i32, fn_got_addr))); } else unreachable; } else if (func_value.castTag(.extern_fn)) |func_payload| { const extern_fn = func_payload.data; @@ -4191,14 +4073,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .sym_index = sym_index, }, }); - // _ = try self.addInst(.{ - // .tag = .call, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rax, - // .flags = 0b01, - // }), - // .data = undefined, - // }); + try self.asmRegister(.call, .rax); } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { const sym_index = try macho_file.getGlobalSymbol(mem.sliceTo(decl_name, 0)); const atom = try macho_file.getOrCreateAtomForDecl(self.mod_fn.owner_decl); @@ -4223,23 +4098,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier assert(ty.zigTypeTag() == .Pointer); const mcv = try self.resolveInst(callee); try self.genSetReg(Type.initTag(.usize), .rax, mcv); - // _ = try self.addInst(.{ - // .tag = .call, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rax, - // .flags = 0b01, - // }), - // .data = undefined, - // }); + try self.asmRegister(.call, .rax); } if (info.stack_byte_count > 0) { // Readjust the stack - // _ = try self.addInst(.{ - // .tag = .add, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), - // .data = .{ .imm = info.stack_byte_count }, - // }); + try self.asmRegisterImmediate(.add, .rsp, Immediate.u(info.stack_byte_count)); } const result: MCValue = result: { @@ -4296,12 +4160,12 @@ fn airRet(self: *Self, inst: Air.Inst.Index) !void { // TODO when implementing defer, this will need to jump to the appropriate defer expression. // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. - // const jmp_reloc = try self.addInst(.{ - // .tag = .jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst = undefined }, - // }); - // try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); + const jmp_reloc = try self.addInst(.{ + .tag = .jmp, + .ops = .inst, + .data = .{ .inst = undefined }, + }); + try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -4332,12 +4196,12 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { // TODO when implementing defer, this will need to jump to the appropriate defer expression. // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. - // const jmp_reloc = try self.addInst(.{ - // .tag = .jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst = undefined }, - // }); - // try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); + const jmp_reloc = try self.addInst(.{ + .tag = .jmp, + .ops = .inst, + .data = .{ .inst = undefined }, + }); + try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -4872,13 +4736,12 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void { const loop = self.air.extraData(Air.Block, ty_pl.payload); const body = self.air.extra[loop.end..][0..loop.data.body_len]; const jmp_target = @intCast(u32, self.mir_instructions.len); - _ = jmp_target; try self.genBody(body); - // _ = try self.addInst(.{ - // .tag = .jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst = jmp_target }, - // }); + _ = try self.addInst(.{ + .tag = .jmp, + .ops = .inst, + .data = .{ .inst = jmp_target }, + }); return self.finishAirBookkeeping(); } @@ -4923,25 +4786,16 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u .none => unreachable, .undef => unreachable, .dead, .unreach => unreachable, - .immediate => |imm| { - _ = imm; - // _ = try self.addInst(.{ - // .tag = .xor, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(cond_reg, abi_size) }), - // .data = .{ .imm = @intCast(u32, imm) }, - // }); - }, - .register => |reg| { - _ = reg; - // _ = try self.addInst(.{ - // .tag = .xor, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(cond_reg, abi_size), - // .reg2 = registerAlias(reg, abi_size), - // }), - // .data = undefined, - // }); - }, + .immediate => |imm| try self.asmRegisterImmediate( + .xor, + registerAlias(cond_reg, abi_size), + Immediate.u(imm), + ), + .register => |reg| try self.asmRegisterRegister( + .xor, + registerAlias(cond_reg, abi_size), + registerAlias(reg, abi_size), + ), .stack_offset => { if (abi_size <= 8) { const reg = try self.copyToTmpRegister(ty, case); @@ -5223,12 +5077,12 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { // Emit a jump with a relocation. It will be patched up after the block ends. try block_data.relocs.ensureUnusedCapacity(self.gpa, 1); // Leave the jump offset undefined - // const jmp_reloc = try self.addInst(.{ - // .tag = .jmp, - // .ops = Mir.Inst.Ops.encode(.{}), - // .data = .{ .inst = undefined }, - // }); - // block_data.relocs.appendAssumeCapacity(jmp_reloc); + const jmp_reloc = try self.addInst(.{ + .tag = .jmp, + .ops = .inst, + .data = .{ .inst = undefined }, + }); + block_data.relocs.appendAssumeCapacity(jmp_reloc); } fn airAsm(self: *Self, inst: Air.Inst.Index) !void { @@ -5463,11 +5317,15 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE switch (ty.zigTypeTag()) { .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - // const tag: Mir.Inst.Tag = switch (ty.tag()) { - // .f32 => Mir.Inst.Tag.mov_f32, - // .f64 => Mir.Inst.Tag.mov_f64, - // else => return self.fail("TODO genSetStackArg for register for type {}", .{ty.fmtDebug()}), - // }; + const tag: Mir.Inst.Tag = switch (ty.tag()) { + .f32 => .movss, + .f64 => .movsd, + else => return self.fail( + "TODO genSetStackArg for register for type {}", + .{ty.fmtDebug()}, + ), + }; + _ = tag; _ = reg; // _ = try self.addInst(.{ // .tag = tag, @@ -5550,7 +5408,7 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl const overflow_bit_ty = ty.structFieldType(1); const overflow_bit_offset = ty.structFieldOffset(1, self.target.*); const tmp_reg = try self.register_manager.allocReg(null, gp); - try self.asmSetCCRegister(tmp_reg.to8(), ro.eflags); + try self.asmSetccRegister(tmp_reg.to8(), ro.eflags); return self.genSetStack( overflow_bit_ty, @@ -6039,7 +5897,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } }, .eflags => |cc| { - return self.asmSetCCRegister(reg.to8(), cc); + return self.asmSetccRegister(reg.to8(), cc); }, .immediate => |x| { if (x == 0) { @@ -6069,63 +5927,38 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .Int => switch (ty.intInfo(self.target.*).signedness) { .signed => { if (abi_size <= 4) { - // _ = try self.addInst(.{ - // .tag = .mov_sign_extend, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to64(), - // .reg2 = registerAlias(src_reg, abi_size), - // }), - // .data = undefined, - // }); - return; + return self.asmRegisterRegister( + .movsx, + reg.to64(), + registerAlias(src_reg, abi_size), + ); } }, .unsigned => { if (abi_size <= 2) { - // _ = try self.addInst(.{ - // .tag = .mov_zero_extend, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to64(), - // .reg2 = registerAlias(src_reg, abi_size), - // }), - // .data = undefined, - // }); - return; + return self.asmRegisterRegister( + .movzx, + reg.to64(), + registerAlias(src_reg, abi_size), + ); } }, }, .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - // const tag: Mir.Inst.Tag = switch (ty.tag()) { - // .f32 => Mir.Inst.Tag.mov_f32, - // .f64 => Mir.Inst.Tag.mov_f64, - // else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}), - // }; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to128(), - // .reg2 = src_reg.to128(), - // .flags = 0b10, - // }), - // .data = undefined, - // }); - return; + const tag: Mir.Inst.Tag = switch (ty.tag()) { + .f32 => .movss, + .f64 => .movsd, + else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}), + }; + return self.asmRegisterRegister(tag, reg.to128(), src_reg.to128()); } - return self.fail("TODO genSetReg from register for float with no intrinsics", .{}); }, else => {}, } - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .reg2 = registerAlias(src_reg, abi_size), - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister(.mov, registerAlias(reg, abi_size), registerAlias(src_reg, abi_size)); }, .linker_load => { switch (ty.zigTypeTag()) { @@ -6214,7 +6047,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } else { // If this is RAX, we can use a direct load. // Otherwise, we need to load the address, then indirectly load the value. - if (reg.id() == 0) { + if (reg.to64() == .rax) { // movabs rax, ds:moffs64 // const payload = try self.addExtra(Mir.Imm64.encode(x)); // _ = try self.addInst(.{ diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 607fc00e41..4eff2d11a0 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -87,7 +87,6 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .imul, .int3, .mov, - .movsx, .movzx, .mul, .nop, @@ -116,7 +115,11 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .ucomisd, => try emit.mirEncodeGeneric(tag, inst), - // Pseudo-instructions + .call, + .jmp, + => try emit.mirCallJmp(inst), + + .movsx => try emit.mirMovsx(inst), .cmovcc => try emit.mirCmovcc(inst), .setcc => try emit.mirSetcc(inst), .jcc => try emit.mirJcc(inst), @@ -209,7 +212,7 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }, }; }, - else => unreachable, + else => unreachable, // TODO } return emit.encode(mnemonic, .{ @@ -220,6 +223,28 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE }); } +fn mirMovsx(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const ops = emit.mir.instructions.items(.ops)[inst]; + const data = emit.mir.instructions.items(.data)[inst]; + + var op1: Instruction.Operand = .none; + var op2: Instruction.Operand = .none; + switch (ops) { + .rr => { + op1 = .{ .reg = data.rr.r1 }; + op2 = .{ .reg = data.rr.r2 }; + }, + else => unreachable, // TODO + } + + const mnemonic: Instruction.Mnemonic = if (op1.bitSize() == 64 and op2.bitSize() == 32) .movsxd else .movsx; + + return emit.encode(mnemonic, .{ + .op1 = op1, + .op2 = op2, + }); +} + fn mnemonicFromConditionCode(comptime basename: []const u8, cc: bits.Condition) Instruction.Mnemonic { inline for (@typeInfo(bits.Condition).Enum.fields) |field| { if (mem.eql(u8, field.name, @tagName(cc))) @@ -277,6 +302,86 @@ fn mirJcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } +fn mirCallJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const tag = emit.mir.instructions.items(.tag)[inst]; + const mnemonic: Instruction.Mnemonic = switch (tag) { + .call => .call, + .jmp => .jmp, + else => unreachable, + }; + const ops = emit.mir.instructions.items(.ops)[inst]; + switch (ops) { + .inst => { + const target = emit.mir.instructions.items(.data)[inst].inst; + const source = emit.code.items.len; + try emit.encode(mnemonic, .{ + .op1 = .{ .imm = Immediate.s(0) }, + }); + try emit.relocs.append(emit.bin_file.allocator, .{ + .source = source, + .target = target, + .offset = emit.code.items.len - 4, + .length = 5, + }); + }, + .r => { + const reg = emit.mir.instructions.items(.data)[inst].r; + try emit.encode(mnemonic, .{ + .op1 = .{ .reg = reg }, + }); + }, + .imm_s => { + const imm = emit.mir.instructions.items(.data)[inst].imm_s; + try emit.encode(mnemonic, .{ + .op1 = .{ .imm = Immediate.s(imm) }, + }); + }, + else => unreachable, // TODO + } +} + +// fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { +// const tag = emit.mir.instructions.items(.tag)[inst]; +// assert(tag == .call_extern); +// const relocation = emit.mir.instructions.items(.data)[inst].relocation; + +// const offset = blk: { +// // callq +// try emit.encode(.call, .{ +// .op1 = .{ .imm = Immediate.s(0) }, +// }); +// break :blk @intCast(u32, emit.code.items.len) - 4; +// }; + +// if (emit.bin_file.cast(link.File.MachO)) |macho_file| { +// // Add relocation to the decl. +// const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; +// const target = macho_file.getGlobalByIndex(relocation.sym_index); +// try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ +// .type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), +// .target = target, +// .offset = offset, +// .addend = 0, +// .pcrel = true, +// .length = 2, +// }); +// } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { +// // Add relocation to the decl. +// const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; +// const target = coff_file.getGlobalByIndex(relocation.sym_index); +// try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ +// .type = .direct, +// .target = target, +// .offset = offset, +// .addend = 0, +// .pcrel = true, +// .length = 2, +// }); +// } else { +// return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{}); +// } +// } + fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; const save_reg_list = emit.mir.extraData(Mir.SaveRegisterList, payload).data; From 32708dd6e2f16b4b688b2deed22253ea36233c91 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 20:32:07 +0100 Subject: [PATCH 14/31] x86_64: add RM and MR helpers to codegen --- src/arch/x86_64/CodeGen.zig | 419 ++++++++++++++++-------------------- src/arch/x86_64/Emit.zig | 86 +++++--- src/arch/x86_64/Mir.zig | 147 ++++++++++++- src/arch/x86_64/bits.zig | 11 + 4 files changed, 397 insertions(+), 266 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index f5ee4d99eb..4441e63aba 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -491,6 +491,72 @@ fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.Tag, reg: Register, imm: Imme }); } +fn asmMemory(self: *Self, tag: Mir.Inst.Tag, m: Memory) !void { + const ops: Mir.Inst.Ops = switch (m) { + .sib => .m_sib, + .rip => .m_rip, + else => unreachable, + }; + const data: Mir.Inst.Data = switch (ops) { + .m_sib => .{ .payload = try self.addExtra(Mir.MemorySib.encode(m)) }, + .m_rip => .{ .payload = try self.addExtra(Mir.MemoryRip.encode(m)) }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + +fn asmRegisterMemory(self: *Self, tag: Mir.Inst.Tag, reg: Register, m: Memory) !void { + const ops: Mir.Inst.Ops = switch (m) { + .sib => .rm_sib, + .rip => .rm_rip, + else => unreachable, + }; + const data: Mir.Inst.Data = switch (ops) { + .rm_sib => .{ .rx = .{ + .r1 = reg, + .payload = try self.addExtra(Mir.MemorySib.encode(m)), + } }, + .rm_rip => .{ .rx = .{ + .r1 = reg, + .payload = try self.addExtra(Mir.MemoryRip.encode(m)), + } }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + +fn asmMemoryRegister(self: *Self, tag: Mir.Inst.Tag, m: Memory, reg: Register) !void { + const ops: Mir.Inst.Ops = switch (m) { + .sib => .mr_sib, + .rip => .mr_rip, + else => unreachable, + }; + const data: Mir.Inst.Data = switch (ops) { + .mr_sib => .{ .rx = .{ + .r1 = reg, + .payload = try self.addExtra(Mir.MemorySib.encode(m)), + } }, + .mr_rip => .{ .rx = .{ + .r1 = reg, + .payload = try self.addExtra(Mir.MemoryRip.encode(m)), + } }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + fn gen(self: *Self) InnerError!void { const cc = self.fn_type.fnCallingConvention(); if (cc != .Naked) { @@ -1741,23 +1807,10 @@ fn genIntMulDivOpMir( switch (factor) { .register => |reg| try self.asmRegister(tag, reg), - .stack_offset => |off| { - _ = off; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg2 = .rbp, - // .flags = switch (abi_size) { - // 1 => 0b00, - // 2 => 0b01, - // 4 => 0b10, - // 8 => 0b11, - // else => unreachable, - // }, - // }), - // .data = .{ .disp = -off }, - // }); - }, + .stack_offset => |off| try self.asmMemory(tag, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rbp, + .disp = -off, + })), else => unreachable, } } @@ -2222,19 +2275,10 @@ fn genSliceElemPtr(self: *Self, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref) !MCValue { const addr_reg = try self.register_manager.allocReg(null, gp); switch (slice_mcv) { - .stack_offset => |off| { - _ = off; - // mov reg, [rbp - 8] - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg.to64(), - // .reg2 = .rbp, - // .flags = 0b01, - // }), - // .data = .{ .disp = -@intCast(i32, off) }, - // }); - }, + .stack_offset => |off| try self.asmRegisterMemory(.mov, addr_reg.to64(), Memory.sib(.qword, .{ + .base = .rbp, + .disp = -off, + })), else => return self.fail("TODO implement slice_elem_ptr when slice is {}", .{slice_mcv}), } // TODO we could allocate register here, but need to expect addr register and potentially @@ -2309,27 +2353,16 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void { array_ty.abiAlignment(self.target.*), )); try self.genSetStack(array_ty, off, array, .{}); - // lea reg, [rbp] - // _ = try self.addInst(.{ - // .tag = .lea, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg.to64(), - // .reg2 = .rbp, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory(.lea, addr_reg.to64(), Memory.sib(.qword, .{ + .base = .rbp, + .disp = -off, + })); }, .stack_offset => |off| { - _ = off; - // lea reg, [rbp] - // _ = try self.addInst(.{ - // .tag = .lea, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg.to64(), - // .reg2 = .rbp, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory(.lea, addr_reg.to64(), Memory.sib(.qword, .{ + .base = .rbp, + .disp = -off, + })); }, .memory, .linker_load => { try self.loadMemPtrIntoRegister(addr_reg, Type.usize, array); @@ -2366,7 +2399,7 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { defer if (ptr_lock) |lock| self.register_manager.unlockReg(lock); const elem_ty = ptr_ty.elemType2(); - const elem_abi_size = elem_ty.abiSize(self.target.*); + const elem_abi_size = @intCast(u32, elem_ty.abiSize(self.target.*)); const index_ty = self.air.typeOf(bin_op.rhs); const index = try self.resolveInst(bin_op.rhs); const index_lock: ?RegisterLock = switch (index) { @@ -2386,16 +2419,14 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { if (elem_abi_size > 8) { return self.fail("TODO copy value with size {} from pointer", .{elem_abi_size}); } else { - // mov dst_mcv, [dst_mcv] - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)), - // .reg2 = dst_mcv.register, - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); + try self.asmRegisterMemory( + .mov, + registerAlias(dst_mcv.register, elem_abi_size), + Memory.sib(Memory.PtrSize.fromSize(elem_abi_size), .{ + .base = dst_mcv.register, + .disp = 0, + }), + ); break :result .{ .register = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)) }; } }; @@ -2622,7 +2653,7 @@ fn reuseOperand( fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!void { const elem_ty = ptr_ty.elemType(); - const abi_size = elem_ty.abiSize(self.target.*); + const abi_size = @intCast(u32, elem_ty.abiSize(self.target.*)); switch (ptr) { .none => unreachable, .undef => unreachable, @@ -2649,17 +2680,11 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo .undef => unreachable, .eflags => unreachable, .register => |dst_reg| { - _ = dst_reg; - // mov dst_reg, [reg] - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_reg, @intCast(u32, abi_size)), - // .reg2 = reg, - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); + try self.asmRegisterMemory( + .mov, + registerAlias(dst_reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = reg, .disp = 0 }), + ); }, .stack_offset => |off| { if (abi_size <= 8) { @@ -2874,17 +2899,11 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type try self.loadMemPtrIntoRegister(addr_reg, ptr_ty, ptr); - // to get the actual address of the value we want to modify we have to go through the GOT - // mov reg, [reg] - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg.to64(), - // .reg2 = addr_reg.to64(), - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); + // To get the actual address of the value we want to modify we have to go through the GOT + try self.asmRegisterMemory(.mov, addr_reg.to64(), Memory.sib(.qword, .{ + .base = addr_reg.to64(), + .disp = 0, + })); const new_ptr = MCValue{ .register = addr_reg.to64() }; @@ -2936,16 +2955,11 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type defer self.register_manager.unlockReg(tmp_reg_lock); try self.loadMemPtrIntoRegister(tmp_reg, value_ty, value); + try self.asmRegisterMemory(.mov, tmp_reg, Memory.sib(.qword, .{ + .base = tmp_reg, + .disp = 0, + })); - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = tmp_reg, - // .reg2 = tmp_reg, - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); return self.store(new_ptr, .{ .register = tmp_reg }, ptr_ty, value_ty); } @@ -3603,15 +3617,11 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu if (off > math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } - // _ = try self.addInst(.{ - // .tag = mir_tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_reg, abi_size), - // .reg2 = .rbp, - // .flags = 0b01, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory( + mir_tag, + registerAlias(dst_reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = .rbp, .disp = -off }), + ); }, } }, @@ -3629,16 +3639,10 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .dead, .unreach => unreachable, .register_overflow => unreachable, .register => |src_reg| { - _ = src_reg; - // _ = try self.addInst(.{ - // .tag = mir_tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rbp, - // .reg2 = registerAlias(src_reg, abi_size), - // .flags = 0b10, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmMemoryRegister(mir_tag, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rbp, + .disp = -off, + }), registerAlias(src_reg, abi_size)); }, .immediate => |imm| { _ = imm; @@ -3738,16 +3742,11 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M } }, .stack_offset => |off| { - _ = off; - // _ = try self.addInst(.{ - // .tag = .imul_complex, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_reg, abi_size), - // .reg2 = .rbp, - // .flags = 0b01, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory( + .imul, + registerAlias(dst_reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = .rbp, .disp = -off }), + ); }, .memory => { return self.fail("TODO implement x86 multiply source memory", .{}); @@ -3770,17 +3769,11 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M .register => |src_reg| { // copy dst to a register const dst_reg = try self.copyToTmpRegister(dst_ty, dst_mcv); - _ = src_reg; - // multiply into dst_reg - // register, register - // _ = try self.addInst(.{ - // .tag = .imul_complex, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(dst_reg, abi_size), - // .reg2 = registerAlias(src_reg, abi_size), - // }), - // .data = undefined, - // }); + try self.asmRegisterRegister( + .imul, + registerAlias(dst_reg, abi_size), + registerAlias(src_reg, abi_size), + ); // copy dst_reg back out return self.genSetStack(dst_ty, off, .{ .register = dst_reg }, .{}); }, @@ -4006,11 +3999,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier if (info.stack_byte_count > 0) { // Adjust the stack - // _ = try self.addInst(.{ - // .tag = .sub, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = .rsp }), - // .data = .{ .imm = info.stack_byte_count }, - // }); + try self.asmRegisterImmediate(.sub, .rsp, Immediate.u(info.stack_byte_count)); } // Due to incremental compilation, how function calls are generated depends @@ -4161,7 +4150,7 @@ fn airRet(self: *Self, inst: Air.Inst.Index) !void { // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. const jmp_reloc = try self.addInst(.{ - .tag = .jmp, + .tag = .jmp_reloc, .ops = .inst, .data = .{ .inst = undefined }, }); @@ -4197,7 +4186,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. const jmp_reloc = try self.addInst(.{ - .tag = .jmp, + .tag = .jmp_reloc, .ops = .inst, .data = .{ .inst = undefined }, }); @@ -4738,7 +4727,7 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void { const jmp_target = @intCast(u32, self.mir_instructions.len); try self.genBody(body); _ = try self.addInst(.{ - .tag = .jmp, + .tag = .jmp_reloc, .ops = .inst, .data = .{ .inst = jmp_target }, }); @@ -5035,7 +5024,7 @@ fn performReloc(self: *Self, reloc: Mir.Inst.Index) !void { .jcc => { self.mir_instructions.items(.data)[reloc].inst_cc.inst = next_inst; }, - .jmp => { + .jmp_reloc => { self.mir_instructions.items(.data)[reloc].inst = next_inst; }, else => unreachable, @@ -5078,7 +5067,7 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { try block_data.relocs.ensureUnusedCapacity(self.gpa, 1); // Leave the jump offset undefined const jmp_reloc = try self.addInst(.{ - .tag = .jmp, + .tag = .jmp_reloc, .ops = .inst, .data = .{ .inst = undefined }, }); @@ -5247,7 +5236,7 @@ fn setRegOrMem(self: *Self, ty: Type, loc: MCValue, val: MCValue) !void { } fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerError!void { - const abi_size = ty.abiSize(self.target.*); + const abi_size = @intCast(u32, ty.abiSize(self.target.*)); switch (mcv) { .dead => unreachable, .unreach, .none => return, @@ -5325,36 +5314,25 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE .{ty.fmtDebug()}, ), }; - _ = tag; - _ = reg; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = switch (ty.tag()) { - // .f32 => .esp, - // .f64 => .rsp, - // else => unreachable, - // }, - // .reg2 = reg.to128(), - // .flags = 0b01, - // }), - // .data = .{ .disp = -stack_offset }, - // }); - return; + // TODO verify this + const ptr_size: Memory.PtrSize = switch (ty.tag()) { + .f32 => .dword, + .f64 => .qword, + else => unreachable, + }; + return self.asmMemoryRegister(tag, Memory.sib(ptr_size, .{ + .base = .rsp, + .disp = -stack_offset, + }), reg.to128()); } return self.fail("TODO genSetStackArg for register with no intrinsics", .{}); }, else => { - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rsp, - // .reg2 = registerAlias(reg, @intCast(u32, abi_size)), - // .flags = 0b10, - // }), - // .data = .{ .disp = -stack_offset }, - // }); + try self.asmMemoryRegister(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rsp, + .disp = -stack_offset, + }), registerAlias(reg, abi_size)); }, } }, @@ -5507,25 +5485,23 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl switch (ty.zigTypeTag()) { .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - // const tag: Mir.Inst.Tag = switch (ty.tag()) { - // .f32 => Mir.Inst.Tag.mov_f32, - // .f64 => Mir.Inst.Tag.mov_f64, - // else => return self.fail("TODO genSetStack for register for type {}", .{ty.fmtDebug()}), - // }; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = switch (ty.tag()) { - // .f32 => base_reg.to32(), - // .f64 => base_reg.to64(), - // else => unreachable, - // }, - // .reg2 = reg.to128(), - // .flags = 0b01, - // }), - // .data = .{ .disp = -stack_offset }, - // }); - return; + const tag: Mir.Inst.Tag = switch (ty.tag()) { + .f32 => .movss, + .f64 => .movsd, + else => return self.fail( + "TODO genSetStack for register for type {}", + .{ty.fmtDebug()}, + ), + }; + const ptr_size: Memory.PtrSize = switch (ty.tag()) { + .f32 => .dword, + .f64 => .qword, + else => unreachable, + }; + return self.asmMemoryRegister(tag, Memory.sib(ptr_size, .{ + .base = base_reg.to64(), + .disp = -stack_offset, + }), reg.to128()); } return self.fail("TODO genSetStack for register for type float with no intrinsics", .{}); @@ -5590,16 +5566,10 @@ fn genInlineMemcpyRegisterRegister( var remainder = abi_size; while (remainder > 0) { const nearest_power_of_two = @as(u6, 1) << math.log2_int(u3, @intCast(u3, remainder)); - - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_reg, - // .reg2 = registerAlias(tmp_reg, nearest_power_of_two), - // .flags = 0b10, - // }), - // .data = .{ .disp = -next_offset }, - // }); + try self.asmMemoryRegister(.mov, Memory.sib(Memory.PtrSize.fromSize(nearest_power_of_two), .{ + .base = dst_reg, + .disp = -next_offset, + }), registerAlias(tmp_reg, nearest_power_of_two)); if (nearest_power_of_two > 1) { try self.genShiftBinOpMir(.shr, ty, tmp_reg, .{ @@ -5611,15 +5581,10 @@ fn genInlineMemcpyRegisterRegister( next_offset -= nearest_power_of_two; } } else { - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_reg, - // .reg2 = registerAlias(src_reg, @intCast(u32, abi_size)), - // .flags = 0b10, - // }), - // .data = .{ .disp = -offset }, - // }); + try self.asmMemoryRegister(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = dst_reg, + .disp = -offset, + }), registerAlias(src_reg, abi_size)); } } @@ -5660,15 +5625,10 @@ fn genInlineMemcpy( try self.loadMemPtrIntoRegister(dst_addr_reg, Type.usize, dst_ptr); }, .ptr_stack_offset, .stack_offset => |off| { - _ = off; - // _ = try self.addInst(.{ - // .tag = .lea, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_addr_reg.to64(), - // .reg2 = opts.dest_stack_base orelse .rbp, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory(.lea, dst_addr_reg.to64(), Memory.sib(.qword, .{ + .base = opts.dest_stack_base orelse .rbp, + .disp = -off, + })); }, .register => |reg| { try self.asmRegisterRegister( @@ -5754,7 +5714,7 @@ fn genInlineMemcpy( try self.asmRegisterImmediate(.sub, count_reg, Immediate.u(1)); _ = try self.addInst(.{ - .tag = .jmp, + .tag = .jmp_reloc, .ops = .inst, .data = .{ .inst = loop_start }, }); @@ -5857,7 +5817,7 @@ fn genInlineMemset( try self.asmRegisterImmediate(.sub, index_reg, Immediate.u(1)); _ = try self.addInst(.{ - .tag = .jmp, + .tag = .jmp_reloc, .ops = .inst, .data = .{ .inst = loop_start }, }); @@ -6045,19 +6005,20 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void // .data = .{ .disp = @intCast(i32, x) }, // }); } else { - // If this is RAX, we can use a direct load. - // Otherwise, we need to load the address, then indirectly load the value. if (reg.to64() == .rax) { - // movabs rax, ds:moffs64 - // const payload = try self.addExtra(Mir.Imm64.encode(x)); - // _ = try self.addInst(.{ - // .tag = .movabs, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rax, - // .flags = 0b01, // imm64 will become moffs64 - // }), - // .data = .{ .payload = payload }, - // }); + // If this is RAX, we can use a direct load. + // Otherwise, we need to load the address, then indirectly load the value. + var moffs: Mir.MemoryMoffs = .{ + .seg = @enumToInt(Register.ds), + .msb = undefined, + .lsb = undefined, + }; + moffs.encodeOffset(x); + _ = try self.addInst(.{ + .tag = .mov_moffs, + .ops = .rax_moffs, + .data = .{ .payload = try self.addExtra(moffs) }, + }); } else { // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue. try self.genSetReg(ty, reg, MCValue{ .immediate = x }); diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 4eff2d11a0..fc1e345a5a 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -73,6 +73,7 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .adc, .add, .@"and", + .call, .cbw, .cwde, .cdqe, @@ -86,6 +87,8 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .idiv, .imul, .int3, + .jmp, + .lea, .mov, .movzx, .mul, @@ -115,9 +118,9 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .ucomisd, => try emit.mirEncodeGeneric(tag, inst), - .call, - .jmp, - => try emit.mirCallJmp(inst), + .jmp_reloc => try emit.mirJmpReloc(inst), + + .mov_moffs => try emit.mirMovMoffs(inst), .movsx => try emit.mirMovsx(inst), .cmovcc => try emit.mirCmovcc(inst), @@ -130,8 +133,6 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .push_regs => try emit.mirPushPopRegisterList(.push, inst), .pop_regs => try emit.mirPushPopRegisterList(.pop, inst), - - else => return emit.fail("Implement MIR->Emit lowering for x86_64 for pseudo-inst: {}", .{tag}), } } @@ -212,6 +213,34 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }, }; }, + .m_sib => { + const msib = emit.mir.extraData(Mir.MemorySib, data.payload).data; + operands[0] = .{ .mem = Mir.MemorySib.decode(msib) }; + }, + .m_rip => { + const mrip = emit.mir.extraData(Mir.MemoryRip, data.payload).data; + operands[0] = .{ .mem = Mir.MemoryRip.decode(mrip) }; + }, + .rm_sib, .mr_sib => { + const msib = emit.mir.extraData(Mir.MemorySib, data.rx.payload).data; + const op1 = .{ .reg = data.rx.r1 }; + const op2 = .{ .mem = Mir.MemorySib.decode(msib) }; + switch (ops) { + .rm_sib => operands[0..2].* = .{ op1, op2 }, + .mr_sib => operands[0..2].* = .{ op2, op1 }, + else => unreachable, + } + }, + .rm_rip, .mr_rip => { + const mrip = emit.mir.extraData(Mir.MemoryRip, data.rx.payload).data; + const op1 = .{ .reg = data.rx.r1 }; + const op2 = .{ .mem = Mir.MemoryRip.decode(mrip) }; + switch (ops) { + .rm_rip => operands[0..2].* = .{ op1, op2 }, + .mr_rip => operands[0..2].* = .{ op2, op1 }, + else => unreachable, + } + }, else => unreachable, // TODO } @@ -223,6 +252,29 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE }); } +fn mirMovMoffs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const ops = emit.mir.instructions.items(.ops)[inst]; + const payload = emit.mir.instructions.items(.data)[inst].payload; + const moffs = emit.mir.extraData(Mir.MemoryMoffs, payload).data; + const seg = @intToEnum(Register, moffs.seg); + const offset = moffs.decodeOffset(); + switch (ops) { + .rax_moffs => { + try emit.encode(.mov, .{ + .op1 = .{ .reg = .rax }, + .op2 = .{ .mem = Memory.moffs(seg, offset) }, + }); + }, + .moffs_rax => { + try emit.encode(.mov, .{ + .op1 = .{ .mem = Memory.moffs(seg, offset) }, + .op2 = .{ .reg = .rax }, + }); + }, + else => unreachable, + } +} + fn mirMovsx(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst]; const data = emit.mir.instructions.items(.data)[inst]; @@ -302,19 +354,13 @@ fn mirJcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } -fn mirCallJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - const mnemonic: Instruction.Mnemonic = switch (tag) { - .call => .call, - .jmp => .jmp, - else => unreachable, - }; +fn mirJmpReloc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = emit.mir.instructions.items(.ops)[inst]; switch (ops) { .inst => { const target = emit.mir.instructions.items(.data)[inst].inst; const source = emit.code.items.len; - try emit.encode(mnemonic, .{ + try emit.encode(.jmp, .{ .op1 = .{ .imm = Immediate.s(0) }, }); try emit.relocs.append(emit.bin_file.allocator, .{ @@ -324,19 +370,7 @@ fn mirCallJmp(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { .length = 5, }); }, - .r => { - const reg = emit.mir.instructions.items(.data)[inst].r; - try emit.encode(mnemonic, .{ - .op1 = .{ .reg = reg }, - }); - }, - .imm_s => { - const imm = emit.mir.instructions.items(.data)[inst].imm_s; - try emit.encode(mnemonic, .{ - .op1 = .{ .imm = Immediate.s(imm) }, - }); - }, - else => unreachable, // TODO + else => unreachable, } } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 02bc70614d..40fd1953de 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -17,6 +17,7 @@ const encoder = @import("encoder.zig"); const Air = @import("../../Air.zig"); const CodeGen = @import("CodeGen.zig"); const IntegerBitSet = std.bit_set.IntegerBitSet; +const Memory = bits.Memory; const Register = bits.Register; instructions: std.MultiArrayList(Inst).Slice, @@ -135,6 +136,12 @@ pub const Inst = struct { /// Conditional move cmovcc, + /// Mov absolute to/from memory wrt segment register to/from rax + mov_moffs, + + /// Jump with relocation to another local MIR instruction + jmp_reloc, + /// End of prologue dbg_prologue_end, /// Start of epilogue @@ -186,24 +193,48 @@ pub const Inst = struct { /// Relative displacement operand. /// Uses `rel` payload. rel, - /// Register, memory operands. + /// Register, memory (SIB) operands. /// Uses `rx` payload. - rm, + rm_sib, + /// Register, memory (RIP) operands. + /// Uses `rx` payload. + rm_rip, /// Register, memory, immediate (unsigned) operands /// Uses `rx` payload. rmi_u, /// Register, memory, immediate (sign-extended) operands /// Uses `rx` payload. rmi_s, - /// Memory, immediate (unsigned) operands. - /// Uses `payload` payload. - mi_u, - /// Memory, immediate (sign-extend) operands. - /// Uses `payload` payload. - mi_s, - /// Memory, register operands. - /// Uses `payload` payload. - mr, + /// Single memory (SIB) operand. + /// Uses `payload` with extra data of type `MemorySib`. + m_sib, + /// Single memory (RIP) operand. + /// Uses `payload` with extra data of type `MemoryRip`. + m_rip, + /// Memory (SIB), immediate (unsigned) operands. + /// Uses `xi_u` payload with extra data of type `MemorySib`. + mi_u_sib, + /// Memory (RIP), immediate (unsigned) operands. + /// Uses `xi_u` payload with extra data of type `MemoryRip`. + mi_u_rip, + /// Memory (SIB), immediate (sign-extend) operands. + /// Uses `xi_s` payload with extra data of type `MemorySib`. + mi_s_sib, + /// Memory (RIP), immediate (sign-extend) operands. + /// Uses `xi_s` payload with extra data of type `MemoryRip`. + mi_s_rip, + /// Memory (SIB), register operands. + /// Uses `rx` payload with extra data of type `MemorySib`. + mr_sib, + /// Memory (RIP), register operands. + /// Uses `rx` payload with extra data of type `MemoryRip`. + mr_rip, + /// Rax, Memory moffs. + /// Uses `payload` with extra data of type `MemoryMoffs`. + rax_moffs, + /// Memory moffs, rax. + /// Uses `payload` with extra data of type `MemoryMoffs`. + moffs_rax, /// Lea into register with linker relocation. /// Uses `payload` payload with data of type `LeaRegisterReloc`. lea_r_reloc, @@ -274,6 +305,16 @@ pub const Inst = struct { r1: Register, payload: u32, }, + /// Custom payload followed by an unsigned immediate. + xi_u: struct { + payload: u32, + imm: u32, + }, + /// Custom payload followed by a signed immediate. + xi_s: struct { + payload: u32, + imm: i32, + }, /// Relocation for the linker where: /// * `atom_index` is the index of the source /// * `sym_index` is the index of the target @@ -378,6 +419,90 @@ pub const Imm64 = struct { } }; +// TODO this can be further compacted using packed struct +pub const MemorySib = struct { + /// Size of the pointer. + ptr_size: u32, + /// Base register. -1 means null, or no base register. + base: i32, + /// Scale for index register. -1 means null, or no scale. + /// This has to be in sync with `index` field. + scale: i32, + /// Index register. -1 means null, or no index register. + /// This has to be in sync with `scale` field. + index: i32, + /// Displacement value. + disp: i32, + + pub fn encode(mem: Memory) MemorySib { + const sib = mem.sib; + return .{ + .ptr_size = @enumToInt(sib.ptr_size), + .base = if (sib.base) |r| @enumToInt(r) else -1, + .scale = if (sib.scale_index) |si| si.scale else -1, + .index = if (sib.scale_index) |si| @enumToInt(si.index) else -1, + .disp = sib.disp, + }; + } + + pub fn decode(msib: MemorySib) Memory { + const base: ?Register = if (msib.base == -1) null else @intToEnum(Register, msib.base); + const scale_index: ?Memory.ScaleIndex = if (msib.index == -1) null else .{ + .scale = @intCast(u4, msib.scale), + .index = @intToEnum(Register, msib.index), + }; + const mem: Memory = .{ .sib = .{ + .ptr_size = @intToEnum(Memory.PtrSize, msib.ptr_size), + .base = base, + .scale_index = scale_index, + .disp = msib.disp, + } }; + return mem; + } +}; + +pub const MemoryRip = struct { + /// Size of the pointer. + ptr_size: u32, + /// Displacement value. + disp: i32, + + pub fn encode(mem: Memory) MemoryRip { + return .{ + .ptr_size = @enumToInt(mem.rip.ptr_size), + .disp = mem.rip.disp, + }; + } + + pub fn decode(mrip: MemoryRip) Memory { + return .{ .rip = .{ + .ptr_size = @intToEnum(Memory.PtrSize, mrip.ptr_size), + .disp = mrip.disp, + } }; + } +}; + +pub const MemoryMoffs = struct { + /// Segment register. + seg: u32, + /// Absolute offset wrt to the segment register split between MSB and LSB parts much like + /// `Imm64` payload. + msb: u32, + lsb: u32, + + pub fn encodeOffset(moffs: *MemoryMoffs, v: u64) void { + moffs.msb = @truncate(u32, v >> 32); + moffs.lsb = @truncate(u32, v); + } + + pub fn decodeOffset(moffs: *const MemoryMoffs) u64 { + var res: u64 = 0; + res |= (@intCast(u64, moffs.msb) << 32); + res |= @intCast(u64, moffs.lsb); + return res; + } +}; + pub const DbgLineColumn = struct { line: u32, column: u32, diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index ad9a6f7f23..b6ac9ec5a8 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -417,6 +417,17 @@ pub const Memory = union(enum) { qword, tbyte, + pub fn fromSize(size: u32) PtrSize { + return switch (size) { + 1 => .byte, + 2 => .word, + 4 => .dword, + 8 => .qword, + 10 => .tbyte, + else => unreachable, + }; + } + pub fn fromBitSize(bit_size: u64) PtrSize { return switch (bit_size) { 8 => .byte, From 4af8313f362e393f51af1bcefd0b91c3b1ce5611 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 21:47:11 +0100 Subject: [PATCH 15/31] x86_64: plug up all RM/MR references --- src/arch/x86_64/CodeGen.zig | 284 ++++++++++++++---------------------- 1 file changed, 112 insertions(+), 172 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 4441e63aba..f3f425d549 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -5834,14 +5834,11 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (off < std.math.minInt(i32) or off > std.math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } - // _ = try self.addInst(.{ - // .tag = .lea, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .reg2 = .rbp, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory( + .lea, + registerAlias(reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = .rbp, .disp = -off }), + ); }, .unreach, .none => return, // Nothing to do. .undef => { @@ -5927,40 +5924,31 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void try self.loadMemPtrIntoRegister(base_reg, Type.usize, mcv); if (intrinsicsAllowed(self.target.*, ty)) { - // const tag: Mir.Inst.Tag = switch (ty.tag()) { - // .f32 => Mir.Inst.Tag.mov_f32, - // .f64 => Mir.Inst.Tag.mov_f64, - // else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), - // }; - - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to128(), - // .reg2 = switch (ty.tag()) { - // .f32 => base_reg.to32(), - // .f64 => base_reg.to64(), - // else => unreachable, - // }, - // }), - // .data = .{ .disp = 0 }, - // }); - return; + const tag: Mir.Inst.Tag = switch (ty.tag()) { + .f32 => .movss, + .f64 => .movsx, + else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), + }; + const ptr_size: Memory.PtrSize = switch (ty.tag()) { + .f32 => .dword, + .f64 => .qword, + else => unreachable, + }; + return self.asmRegisterMemory(tag, reg.to128(), Memory.sib(ptr_size, .{ + .base = base_reg.to64(), + .disp = 0, + })); } return self.fail("TODO genSetReg from memory for float with no intrinsics", .{}); }, else => { try self.loadMemPtrIntoRegister(reg, Type.usize, mcv); - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .reg2 = reg.to64(), - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); + try self.asmRegisterMemory( + .mov, + registerAlias(reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = reg.to64(), .disp = 0 }), + ); }, } }, @@ -5970,40 +5958,34 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void try self.loadMemPtrIntoRegister(base_reg, Type.usize, mcv); if (intrinsicsAllowed(self.target.*, ty)) { - // const tag: Mir.Inst.Tag = switch (ty.tag()) { - // .f32 => Mir.Inst.Tag.mov_f32, - // .f64 => Mir.Inst.Tag.mov_f64, - // else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), - // }; - - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to128(), - // .reg2 = switch (ty.tag()) { - // .f32 => base_reg.to32(), - // .f64 => base_reg.to64(), - // else => unreachable, - // }, - // }), - // .data = .{ .disp = 0 }, - // }); - return; + const tag: Mir.Inst.Tag = switch (ty.tag()) { + .f32 => .movss, + .f64 => .movsd, + else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), + }; + const ptr_size: Memory.PtrSize = switch (ty.tag()) { + .f32 => .dword, + .f64 => .qword, + else => unreachable, + }; + return self.asmRegisterMemory(tag, reg.to128(), Memory.sib(ptr_size, .{ + .base = base_reg.to64(), + .disp = 0, + })); } return self.fail("TODO genSetReg from memory for float with no intrinsics", .{}); }, else => { if (x <= math.maxInt(i32)) { - // mov reg, [ds:imm32] - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .flags = 0b01, - // }), - // .data = .{ .disp = @intCast(i32, x) }, - // }); + try self.asmRegisterMemory( + .mov, + registerAlias(reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .ds, + .disp = @intCast(i32, x), + }), + ); } else { if (reg.to64() == .rax) { // If this is RAX, we can use a direct load. @@ -6022,17 +6004,11 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } else { // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue. try self.genSetReg(ty, reg, MCValue{ .immediate = x }); - - // mov reg, [reg + 0x0] - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .reg2 = reg.to64(), - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); + try self.asmRegisterMemory( + .mov, + registerAlias(reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = reg.to64(), .disp = 0 }), + ); } } }, @@ -6046,81 +6022,59 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void .Int => switch (ty.intInfo(self.target.*).signedness) { .signed => { if (abi_size <= 4) { - const flags: u2 = switch (abi_size) { - 1 => 0b01, - 2 => 0b10, - 4 => 0b11, - else => unreachable, - }; - _ = flags; - // _ = try self.addInst(.{ - // .tag = .mov_sign_extend, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to64(), - // .reg2 = .rbp, - // .flags = flags, - // }), - // .data = .{ .disp = -off }, - // }); - return; + return self.asmRegisterMemory( + .movsx, + reg.to64(), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rbp, + .disp = -off, + }), + ); } }, .unsigned => { if (abi_size <= 2) { - const flags: u2 = switch (abi_size) { - 1 => 0b01, - 2 => 0b10, - else => unreachable, - }; - _ = flags; - // _ = try self.addInst(.{ - // .tag = .mov_zero_extend, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to64(), - // .reg2 = .rbp, - // .flags = flags, - // }), - // .data = .{ .disp = -off }, - // }); - return; + return self.asmRegisterMemory( + .movzx, + reg.to64(), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rbp, + .disp = -off, + }), + ); } }, }, .Float => { if (intrinsicsAllowed(self.target.*, ty)) { - // const tag: Mir.Inst.Tag = switch (ty.tag()) { - // .f32 => Mir.Inst.Tag.mov_f32, - // .f64 => Mir.Inst.Tag.mov_f64, - // else => return self.fail("TODO genSetReg from stack offset for {}", .{ty.fmtDebug()}), - // }; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to128(), - // .reg2 = switch (ty.tag()) { - // .f32 => .ebp, - // .f64 => .rbp, - // else => unreachable, - // }, - // }), - // .data = .{ .disp = -off }, - // }); - return; + const tag: Mir.Inst.Tag = switch (ty.tag()) { + .f32 => .movss, + .f64 => .movsd, + else => return self.fail( + "TODO genSetReg from stack offset for {}", + .{ty.fmtDebug()}, + ), + }; + const ptr_size: Memory.PtrSize = switch (ty.tag()) { + .f32 => .dword, + .f64 => .qword, + else => unreachable, + }; + return self.asmRegisterMemory(tag, reg.to128(), Memory.sib(ptr_size, .{ + .base = .rbp, + .disp = -off, + })); } return self.fail("TODO genSetReg from stack offset for float with no intrinsics", .{}); }, else => {}, } - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .reg2 = .rbp, - // .flags = 0b01, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory( + .mov, + registerAlias(reg, abi_size), + Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = .rbp, .disp = -off }), + ); }, } } @@ -6184,7 +6138,16 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { const src_ty = self.air.typeOf(ty_op.operand); const dst_ty = self.air.typeOfIndex(inst); const operand = try self.resolveInst(ty_op.operand); - _ = dst_ty; + const src_abi_size = @intCast(u32, src_ty.abiSize(self.target.*)); + const dst_abi_size = @intCast(u32, dst_ty.abiSize(self.target.*)); + + switch (src_abi_size) { + 4, 8 => {}, + else => |size| return self.fail("TODO load ST(0) with abiSize={}", .{size}), + } + if (dst_abi_size > 8) { + return self.fail("TODO convert float with abiSize={}", .{dst_abi_size}); + } // move float src to ST(0) const stack_offset = switch (operand) { @@ -6192,42 +6155,24 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { else => blk: { const offset = @intCast(i32, try self.allocMem( inst, - @intCast(u32, src_ty.abiSize(self.target.*)), + src_abi_size, src_ty.abiAlignment(self.target.*), )); try self.genSetStack(src_ty, offset, operand, .{}); break :blk offset; }, }; - _ = stack_offset; - // _ = try self.addInst(.{ - // .tag = .fld, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rbp, - // .flags = switch (src_ty.abiSize(self.target.*)) { - // 4 => 0b01, - // 8 => 0b10, - // else => |size| return self.fail("TODO load ST(0) with abiSize={}", .{size}), - // }, - // }), - // .data = .{ .disp = -stack_offset }, - // }); + try self.asmMemory(.fld, Memory.sib(Memory.PtrSize.fromSize(src_abi_size), .{ + .base = .rbp, + .disp = -stack_offset, + })); // convert const stack_dst = try self.allocRegOrMem(inst, false); - // _ = try self.addInst(.{ - // .tag = .fisttp, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rbp, - // .flags = switch (dst_ty.abiSize(self.target.*)) { - // 1...2 => 0b00, - // 3...4 => 0b01, - // 5...8 => 0b10, - // else => |size| return self.fail("TODO convert float with abiSize={}", .{size}), - // }, - // }), - // .data = .{ .disp = -stack_dst.stack_offset }, - // }); + try self.asmMemory(.fisttp, Memory.sib(Memory.PtrSize.fromSize(dst_abi_size), .{ + .base = .rbp, + .disp = -stack_dst.stack_offset, + })); return self.finishAir(inst, stack_dst, .{ ty_op.operand, .none, .none }); } @@ -6318,15 +6263,10 @@ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void { .linker_load, .memory => { const reg = try self.register_manager.allocReg(null, gp); try self.loadMemPtrIntoRegister(reg, src_ty, src_ptr); - // _ = try self.addInst(.{ - // .tag = .mov, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg, - // .reg2 = reg, - // .flags = 0b01, - // }), - // .data = .{ .disp = 0 }, - // }); + try self.asmRegisterMemory(.mov, reg, Memory.sib(.qword, .{ + .base = reg, + .disp = 0, + })); break :blk MCValue{ .register = reg }; }, else => break :blk src_ptr, From 022b308d6a0a3d3cf178ddc6e887f24369f69deb Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 22:15:58 +0100 Subject: [PATCH 16/31] x86_64: start converting MI references --- src/arch/x86_64/CodeGen.zig | 251 +++++++++++------------------------- src/arch/x86_64/Emit.zig | 28 ++++ 2 files changed, 106 insertions(+), 173 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index f3f425d549..e9e7e1875b 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -509,6 +509,29 @@ fn asmMemory(self: *Self, tag: Mir.Inst.Tag, m: Memory) !void { }); } +fn asmMemoryImmediate(self: *Self, tag: Mir.Inst.Tag, m: Memory, imm: Immediate) !void { + const ops: Mir.Inst.Ops = switch (m) { + .sib => if (imm == .signed) .mi_s_sib else .mi_u_sib, + .rip => if (imm == .signed) .mi_s_rip else .mi_u_rip, + else => unreachable, + }; + const payload: u32 = switch (ops) { + .mi_s_sib, .mi_u_sib => try self.addExtra(Mir.MemorySib.encode(m)), + .mi_s_rip, .mi_u_rip => try self.addExtra(Mir.MemoryRip.encode(m)), + else => unreachable, + }; + const data: Mir.Inst.Data = switch (ops) { + .mi_s_sib, .mi_s_rip => .{ .xi_s = .{ .imm = imm.signed, .payload = payload } }, + .mi_u_sib, .mi_u_rip => .{ .xi_u = .{ .imm = @intCast(u32, imm.unsigned), .payload = payload } }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + fn asmRegisterMemory(self: *Self, tag: Mir.Inst.Tag, reg: Register, m: Memory) !void { const ops: Mir.Inst.Ops = switch (m) { .sib => .rm_sib, @@ -2776,7 +2799,7 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue } fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type) InnerError!void { - const abi_size = value_ty.abiSize(self.target.*); + const abi_size = @intCast(u32, value_ty.abiSize(self.target.*)); switch (ptr) { .none => unreachable, .undef => unreachable, @@ -2807,28 +2830,12 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type try self.genSetReg(value_ty, reg, value); }, .immediate => |imm| { - _ = imm; switch (abi_size) { 1, 2, 4 => { - // TODO this is wasteful! - // introduce new MIR tag specifically for mov [reg + 0], imm - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = 0, - // .operand = @truncate(u32, imm), - // }); - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = reg.to64(), - // .flags = switch (abi_size) { - // 1 => 0b00, - // 2 => 0b01, - // 4 => 0b10, - // else => unreachable, - // }, - // }), - // .data = .{ .payload = payload }, - // }); + try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = reg.to64(), + .disp = 0, + }), Immediate.u(@truncate(u32, imm))); }, 8 => { // TODO: optimization: if the imm is only using the lower @@ -2913,19 +2920,8 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type return self.fail("TODO saving imm to memory for abi_size {}", .{abi_size}); } - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = 0, - // // TODO check if this logic is correct - // .operand = @intCast(u32, imm), - // }); - const flags: u2 = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - 8 => 0b11, - else => unreachable, - }; - if (flags == 0b11) { + if (abi_size == 8) { + // TODO const top_bits: u32 = @intCast(u32, imm >> 32); const can_extend = if (value_ty.isUnsignedInt()) (top_bits == 0) and (imm & 0x8000_0000) == 0 @@ -2936,14 +2932,10 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type return self.fail("TODO imm64 would get incorrectly sign extended", .{}); } } - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg.to64(), - // .flags = flags, - // }), - // .data = .{ .payload = payload }, - // }); + try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = addr_reg.to64(), + .disp = 0, + }), Immediate.u(@intCast(u32, imm))); }, .register => { return self.store(new_ptr, value, ptr_ty, value_ty); @@ -3595,12 +3587,12 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu ), }, .immediate => |imm| { - _ = imm; - // _ = try self.addInst(.{ - // .tag = mir_tag, - // .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(dst_reg, abi_size) }), - // .data = .{ .imm = @intCast(u32, imm) }, - // }); + // TODO + try self.asmRegisterImmediate( + mir_tag, + registerAlias(dst_reg, abi_size), + Immediate.u(@intCast(u32, imm)), + ); }, .memory, .linker_load, @@ -3645,36 +3637,11 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu }), registerAlias(src_reg, abi_size)); }, .immediate => |imm| { - _ = imm; - // const tag: Mir.Inst.Tag = switch (mir_tag) { - // .add => .add_mem_imm, - // .@"or" => .or_mem_imm, - // .@"and" => .and_mem_imm, - // .sub => .sub_mem_imm, - // .xor => .xor_mem_imm, - // .cmp => .cmp_mem_imm, - // else => unreachable, - // }; - const flags: u2 = switch (abi_size) { - 1 => 0b00, - 2 => 0b01, - 4 => 0b10, - 8 => 0b11, - else => unreachable, - }; - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = -off, - // .operand = @intCast(u32, imm), - // }); - _ = flags; - // _ = try self.addInst(.{ - // .tag = tag, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rbp, - // .flags = flags, - // }), - // .data = .{ .payload = payload }, - // }); + // TODO + try self.asmMemoryImmediate(mir_tag, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rbp, + .disp = -off, + }), Immediate.u(@intCast(u32, imm))); }, .memory, .stack_offset, @@ -5258,33 +5225,18 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE return self.genSetStackArg(ty, stack_offset, .{ .register = reg }); }, .immediate => |imm| { - _ = imm; switch (abi_size) { - // TODO - // 1, 2, 4 => { - // // We have a positive stack offset value but we want a twos complement negative - // // offset from rbp, which is at the top of the stack frame. - // // mov [rbp+offset], immediate - // const flags: u2 = switch (abi_size) { - // 1 => 0b00, - // 2 => 0b01, - // 4 => 0b10, - // else => unreachable, - // }; - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = -stack_offset, - // .operand = @intCast(u32, imm), - // }); - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = .rsp, - // .flags = flags, - // }), - // .data = .{ .payload = payload }, - // }); - // }, - 1, 2, 4, 8 => { + 1, 2, 4 => { + // TODO + // We have a positive stack offset value but we want a twos complement negative + // offset from rbp, which is at the top of the stack frame. + // mov [rbp+offset], immediate + try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = .rsp, + .disp = -stack_offset, + }), Immediate.u(@intCast(u32, imm))); + }, + 8 => { const reg = try self.copyToTmpRegister(ty, mcv); return self.genSetStackArg(ty, stack_offset, MCValue{ .register = reg }); }, @@ -5355,7 +5307,7 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE } fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: InlineMemcpyOpts) InnerError!void { - const abi_size = ty.abiSize(self.target.*); + const abi_size = @intCast(u32, ty.abiSize(self.target.*)); switch (mcv) { .dead => unreachable, .unreach, .none => return, // Nothing to do. @@ -5400,75 +5352,33 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl return self.genSetStack(ty, stack_offset, .{ .register = reg }, opts); }, .immediate => |x_big| { - _ = x_big; const base_reg = opts.dest_stack_base orelse .rbp; - _ = base_reg; + // TODO switch (abi_size) { 0 => { assert(ty.isError()); - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = -stack_offset, - // .operand = @truncate(u32, x_big), - // }); - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = base_reg, - // .flags = 0b00, - // }), - // .data = .{ .payload = payload }, - // }); + try self.asmMemoryImmediate(.mov, Memory.sib(.byte, .{ + .base = base_reg, + .disp = -stack_offset, + }), Immediate.u(@truncate(u32, x_big))); }, 1, 2, 4 => { - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = -stack_offset, - // .operand = @truncate(u32, x_big), - // }); - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = base_reg, - // .flags = switch (abi_size) { - // 1 => 0b00, - // 2 => 0b01, - // 4 => 0b10, - // else => unreachable, - // }, - // }), - // .data = .{ .payload = payload }, - // }); + try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = base_reg, + .disp = -stack_offset, + }), Immediate.u(@truncate(u32, x_big))); }, 8 => { // 64 bit write to memory would take two mov's anyways so we // insted just use two 32 bit writes to avoid register allocation - { - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = -stack_offset + 4, - // .operand = @truncate(u32, x_big >> 32), - // }); - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = base_reg, - // .flags = 0b10, - // }), - // .data = .{ .payload = payload }, - // }); - } - { - // const payload = try self.addExtra(Mir.ImmPair{ - // .dest_off = -stack_offset, - // .operand = @truncate(u32, x_big), - // }); - // _ = try self.addInst(.{ - // .tag = .mov_mem_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = base_reg, - // .flags = 0b10, - // }), - // .data = .{ .payload = payload }, - // }); - } + try self.asmMemoryImmediate(.mov, Memory.sib(.dword, .{ + .base = base_reg, + .disp = -stack_offset + 4, + }), Immediate.u(@truncate(u32, x_big >> 32))); + try self.asmMemoryImmediate(.mov, Memory.sib(.dword, .{ + .base = base_reg, + .disp = -stack_offset, + }), Immediate.u(@truncate(u32, x_big))); }, else => { return self.fail("TODO implement set abi_size=large stack variable with immediate", .{}); @@ -5647,15 +5557,10 @@ fn genInlineMemcpy( try self.loadMemPtrIntoRegister(src_addr_reg, Type.usize, src_ptr); }, .ptr_stack_offset, .stack_offset => |off| { - _ = off; - // _ = try self.addInst(.{ - // .tag = .lea, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = src_addr_reg.to64(), - // .reg2 = opts.source_stack_base orelse .rbp, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory(.lea, src_addr_reg.to64(), Memory.sib(.qword, .{ + .base = opts.source_stack_base orelse .rbp, + .disp = -off, + })); }, .register => |reg| { try self.asmRegisterRegister( diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index fc1e345a5a..218e1c6f55 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -221,6 +221,34 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE const mrip = emit.mir.extraData(Mir.MemoryRip, data.payload).data; operands[0] = .{ .mem = Mir.MemoryRip.decode(mrip) }; }, + .mi_u_sib => { + const msib = emit.mir.extraData(Mir.MemorySib, data.xi_u.payload).data; + operands[0..2].* = .{ + .{ .mem = Mir.MemorySib.decode(msib) }, + .{ .imm = Immediate.u(data.xi_u.imm) }, + }; + }, + .mi_s_sib => { + const msib = emit.mir.extraData(Mir.MemorySib, data.xi_s.payload).data; + operands[0..2].* = .{ + .{ .mem = Mir.MemorySib.decode(msib) }, + .{ .imm = Immediate.s(data.xi_s.imm) }, + }; + }, + .mi_u_rip => { + const mrip = emit.mir.extraData(Mir.MemoryRip, data.xi_u.payload).data; + operands[0..2].* = .{ + .{ .mem = Mir.MemoryRip.decode(mrip) }, + .{ .imm = Immediate.u(data.xi_u.imm) }, + }; + }, + .mi_s_rip => { + const mrip = emit.mir.extraData(Mir.MemoryRip, data.xi_s.payload).data; + operands[0..2].* = .{ + .{ .mem = Mir.MemoryRip.decode(mrip) }, + .{ .imm = Immediate.s(data.xi_s.imm) }, + }; + }, .rm_sib, .mr_sib => { const msib = emit.mir.extraData(Mir.MemorySib, data.rx.payload).data; const op1 = .{ .reg = data.rx.r1 }; From d0e72125396c391172758d28c21a9b901dcecc68 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Mar 2023 23:56:55 +0100 Subject: [PATCH 17/31] x86_64: finish rolling out all MIR assembly helpers --- src/arch/x86_64/CodeGen.zig | 172 ++++++++++--------- src/arch/x86_64/Emit.zig | 328 ++++++++++++------------------------ src/arch/x86_64/Mir.zig | 46 +++-- 3 files changed, 225 insertions(+), 321 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index e9e7e1875b..9056b64dce 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -491,6 +491,37 @@ fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.Tag, reg: Register, imm: Imme }); } +fn asmRegisterRegisterImmediate( + self: *Self, + tag: Mir.Inst.Tag, + reg1: Register, + reg2: Register, + imm: Immediate, +) !void { + const ops: Mir.Inst.Ops = switch (imm) { + .signed => .rri_s, + .unsigned => .rri_u, + }; + const data: Mir.Inst.Data = switch (ops) { + .rri_s => .{ .rri_s = .{ + .r1 = reg1, + .r2 = reg2, + .imm = imm.signed, + } }, + .rri_u => .{ .rri_u = .{ + .r1 = reg1, + .r2 = reg2, + .imm = @intCast(u32, imm.unsigned), + } }, + else => unreachable, + }; + _ = try self.addInst(.{ + .tag = tag, + .ops = ops, + .data = data, + }); +} + fn asmMemory(self: *Self, tag: Mir.Inst.Tag, m: Memory) !void { const ops: Mir.Inst.Ops = switch (m) { .sib => .m_sib, @@ -2767,27 +2798,20 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue const atom = try coff_file.getOrCreateAtomForDecl(self.mod_fn.owner_decl); break :blk coff_file.getAtom(atom).getSymbolIndex().?; } else unreachable; - const flags: u2 = switch (load_struct.type) { - .got => 0b00, - .direct => 0b01, - .import => 0b10, + const ops: Mir.Inst.Ops = switch (load_struct.type) { + .got => .got_reloc, + .direct => .direct_reloc, + .import => .import_reloc, }; - _ = abi_size; - _ = atom_index; - _ = flags; - // _ = try self.addInst(.{ - // .tag = .lea_pic, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = registerAlias(reg, abi_size), - // .flags = flags, - // }), - // .data = .{ - // .relocation = .{ - // .atom_index = atom_index, - // .sym_index = load_struct.sym_index, - // }, - // }, - // }); + _ = try self.addInst(.{ + .tag = .lea_linker, + .ops = ops, + .data = .{ .payload = try self.addExtra(Mir.LeaRegisterReloc{ + .reg = @enumToInt(registerAlias(reg, abi_size)), + .atom_index = atom_index, + .sym_index = load_struct.sym_index, + }) }, + }); }, .memory => |addr| { // TODO: in case the address fits in an imm32 we can use [ds:imm32] @@ -3690,18 +3714,15 @@ fn genIntMulComplexOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: M registerAlias(src_reg, abi_size), ), .immediate => |imm| { - // TODO take into account the type's ABI size when selecting the register alias - // register, immediate if (math.minInt(i32) <= imm and imm <= math.maxInt(i32)) { - // _ = try self.addInst(.{ - // .tag = .imul_complex, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_reg.to32(), - // .reg2 = dst_reg.to32(), - // .flags = 0b10, - // }), - // .data = .{ .imm = @intCast(u32, imm) }, - // }); + // TODO take into account the type's ABI size when selecting the register alias + // register, immediate + try self.asmRegisterRegisterImmediate( + .imul, + dst_reg.to32(), + dst_reg.to32(), + Immediate.u(@intCast(u32, imm)), + ); } else { // TODO verify we don't spill and assign to the same register as dst_mcv const src_reg = try self.copyToTmpRegister(dst_ty, src_mcv); @@ -4034,16 +4055,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const sym_index = try macho_file.getGlobalSymbol(mem.sliceTo(decl_name, 0)); const atom = try macho_file.getOrCreateAtomForDecl(self.mod_fn.owner_decl); const atom_index = macho_file.getAtom(atom).getSymbolIndex().?; - _ = sym_index; - _ = atom_index; - // _ = try self.addInst(.{ - // .tag = .call_extern, - // .ops = undefined, - // .data = .{ .relocation = .{ - // .atom_index = atom_index, - // .sym_index = sym_index, - // } }, - // }); + _ = try self.addInst(.{ + .tag = .call_extern, + .ops = undefined, + .data = .{ .relocation = .{ + .atom_index = atom_index, + .sym_index = sym_index, + } }, + }); } else { return self.fail("TODO implement calling extern functions", .{}); } @@ -5528,7 +5547,6 @@ fn genInlineMemcpy( const index_reg = regs[2].to64(); const count_reg = regs[3].to64(); const tmp_reg = regs[4].to8(); - _ = tmp_reg; switch (dst_ptr) { .memory, .linker_load => { @@ -5575,7 +5593,6 @@ fn genInlineMemcpy( } try self.genSetReg(Type.usize, count_reg, len); - try self.asmRegisterImmediate(.mov, index_reg, Immediate.u(0)); const loop_start = try self.addInst(.{ @@ -5595,26 +5612,22 @@ fn genInlineMemcpy( } }, }); - // mov tmp, [addr + index_reg] - // _ = try self.addInst(.{ - // .tag = .mov_scale_src, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = tmp_reg.to8(), - // .reg2 = src_addr_reg, - // }), - // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, - // }); - - // mov [stack_offset + index_reg], tmp - // _ = try self.addInst(.{ - // .tag = .mov_scale_dst, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = dst_addr_reg, - // .reg2 = tmp_reg.to8(), - // }), - // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDisp.encode(index_reg, 0)) }, - // }); - + try self.asmRegisterMemory(.mov, tmp_reg.to8(), Memory.sib(.byte, .{ + .base = src_addr_reg, + .scale_index = .{ + .scale = 1, + .index = index_reg, + }, + .disp = 0, + })); + try self.asmMemoryRegister(.mov, Memory.sib(.byte, .{ + .base = dst_addr_reg, + .scale_index = .{ + .scale = 1, + .index = index_reg, + }, + .disp = 0, + }), tmp_reg.to8()); try self.asmRegisterImmediate(.add, index_reg, Immediate.u(1)); try self.asmRegisterImmediate(.sub, count_reg, Immediate.u(1)); @@ -5655,15 +5668,10 @@ fn genInlineMemset( try self.loadMemPtrIntoRegister(addr_reg, Type.usize, dst_ptr); }, .ptr_stack_offset, .stack_offset => |off| { - _ = off; - // _ = try self.addInst(.{ - // .tag = .lea, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg.to64(), - // .reg2 = opts.dest_stack_base orelse .rbp, - // }), - // .data = .{ .disp = -off }, - // }); + try self.asmRegisterMemory(.lea, addr_reg.to64(), Memory.sib(.qword, .{ + .base = opts.dest_stack_base orelse .rbp, + .disp = -off, + })); }, .register => |reg| { try self.asmRegisterRegister( @@ -5703,18 +5711,14 @@ fn genInlineMemset( if (x > math.maxInt(i32)) { return self.fail("TODO inline memset for value immediate larger than 32bits", .{}); } - // mov byte ptr [rbp + index_reg + stack_offset], imm - // _ = try self.addInst(.{ - // .tag = .mov_mem_index_imm, - // .ops = Mir.Inst.Ops.encode(.{ - // .reg1 = addr_reg, - // }), - // .data = .{ .payload = try self.addExtra(Mir.IndexRegisterDispImm.encode( - // index_reg, - // 0, - // @intCast(u32, x), - // )) }, - // }); + try self.asmMemoryImmediate(.mov, Memory.sib(.byte, .{ + .base = addr_reg, + .scale_index = .{ + .scale = 1, + .index = index_reg, + }, + .disp = 0, + }), Immediate.u(@intCast(u8, x))); }, else => return self.fail("TODO inline memset for value of type {}", .{value}), } diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 218e1c6f55..5b3b03e8ee 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -120,6 +120,10 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .jmp_reloc => try emit.mirJmpReloc(inst), + .call_extern => try emit.mirCallExtern(inst), + + .lea_linker => try emit.mirLeaLinker(inst), + .mov_moffs => try emit.mirMovMoffs(inst), .movsx => try emit.mirMovsx(inst), @@ -213,6 +217,16 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }, }; }, + .rri_s => operands[0..3].* = .{ + .{ .reg = data.rri_s.r1 }, + .{ .reg = data.rri_s.r2 }, + .{ .imm = Immediate.s(data.rri_s.imm) }, + }, + .rri_u => operands[0..3].* = .{ + .{ .reg = data.rri_u.r1 }, + .{ .reg = data.rri_u.r2 }, + .{ .imm = Immediate.u(data.rri_u.imm) }, + }, .m_sib => { const msib = emit.mir.extraData(Mir.MemorySib, data.payload).data; operands[0] = .{ .mem = Mir.MemorySib.decode(msib) }; @@ -402,47 +416,44 @@ fn mirJmpReloc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } -// fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .call_extern); -// const relocation = emit.mir.instructions.items(.data)[inst].relocation; +fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const relocation = emit.mir.instructions.items(.data)[inst].relocation; -// const offset = blk: { -// // callq -// try emit.encode(.call, .{ -// .op1 = .{ .imm = Immediate.s(0) }, -// }); -// break :blk @intCast(u32, emit.code.items.len) - 4; -// }; + const offset = blk: { + try emit.encode(.call, .{ + .op1 = .{ .imm = Immediate.s(0) }, + }); + break :blk @intCast(u32, emit.code.items.len) - 4; + }; -// if (emit.bin_file.cast(link.File.MachO)) |macho_file| { -// // Add relocation to the decl. -// const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; -// const target = macho_file.getGlobalByIndex(relocation.sym_index); -// try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ -// .type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), -// .target = target, -// .offset = offset, -// .addend = 0, -// .pcrel = true, -// .length = 2, -// }); -// } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { -// // Add relocation to the decl. -// const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; -// const target = coff_file.getGlobalByIndex(relocation.sym_index); -// try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ -// .type = .direct, -// .target = target, -// .offset = offset, -// .addend = 0, -// .pcrel = true, -// .length = 2, -// }); -// } else { -// return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{}); -// } -// } + if (emit.bin_file.cast(link.File.MachO)) |macho_file| { + // Add relocation to the decl. + const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; + const target = macho_file.getGlobalByIndex(relocation.sym_index); + try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ + .type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), + .target = target, + .offset = offset, + .addend = 0, + .pcrel = true, + .length = 2, + }); + } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { + // Add relocation to the decl. + const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; + const target = coff_file.getGlobalByIndex(relocation.sym_index); + try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ + .type = .direct, + .target = target, + .offset = offset, + .addend = 0, + .pcrel = true, + .length = 2, + }); + } else { + return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{}); + } +} fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; @@ -474,194 +485,63 @@ fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) } } -// fn mirJmpCall(emit: *Emit, mnemonic: Instruction.Mnemonic, inst: Mir.Inst.Index) InnerError!void { -// const ops = emit.mir.instructions.items(.ops)[inst].decode(); -// switch (ops.flags) { -// 0b00 => { -// const target = emit.mir.instructions.items(.data)[inst].inst; -// const source = emit.code.items.len; -// try emit.encode(mnemonic, .{ -// .op1 = .{ .imm = Immediate.s(0) }, -// }); -// try emit.relocs.append(emit.bin_file.allocator, .{ -// .source = source, -// .target = target, -// .offset = emit.code.items.len - 4, -// .length = 5, -// }); -// }, -// 0b01 => { -// if (ops.reg1 == .none) { -// const disp = emit.mir.instructions.items(.data)[inst].disp; -// return emit.encode(mnemonic, .{ -// .op1 = .{ .mem = Memory.sib(.qword, .{ .disp = disp }) }, -// }); -// } -// return emit.encode(mnemonic, .{ -// .op1 = .{ .reg = ops.reg1 }, -// }); -// }, -// 0b10 => { -// const disp = emit.mir.instructions.items(.data)[inst].disp; -// return emit.encode(mnemonic, .{ -// .op1 = .{ .mem = Memory.sib(.qword, .{ -// .base = ops.reg1, -// .disp = disp, -// }) }, -// }); -// }, -// 0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}), -// } -// } +fn mirLeaLinker(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + const ops = emit.mir.instructions.items(.ops)[inst]; + const payload = emit.mir.instructions.items(.data)[inst].payload; + const metadata = emit.mir.extraData(Mir.LeaRegisterReloc, payload).data; + const reg = @intToEnum(Register, metadata.reg); -// fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .lea); -// const ops = emit.mir.instructions.items(.ops)[inst].decode(); -// switch (ops.flags) { -// 0b00 => { -// const disp = emit.mir.instructions.items(.data)[inst].disp; -// const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; -// return emit.encode(.lea, .{ -// .op1 = .{ .reg = ops.reg1 }, -// .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ -// .base = src_reg, -// .disp = disp, -// }) }, -// }); -// }, -// 0b01 => { -// const start_offset = emit.code.items.len; -// try emit.encode(.lea, .{ -// .op1 = .{ .reg = ops.reg1 }, -// .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, -// }); -// const end_offset = emit.code.items.len; -// // Backpatch the displacement -// const payload = emit.mir.instructions.items(.data)[inst].payload; -// const imm = emit.mir.extraData(Mir.Imm64, payload).data.decode(); -// const disp = @intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset)); -// mem.writeIntLittle(i32, emit.code.items[end_offset - 4 ..][0..4], disp); -// }, -// 0b10 => { -// const payload = emit.mir.instructions.items(.data)[inst].payload; -// const index_reg_disp = emit.mir.extraData(Mir.IndexRegisterDisp, payload).data.decode(); -// const src_reg: ?Register = if (ops.reg2 != .none) ops.reg2 else null; -// const scale_index = Memory.ScaleIndex{ -// .scale = 1, -// .index = index_reg_disp.index, -// }; -// return emit.encode(.lea, .{ -// .op1 = .{ .reg = ops.reg1 }, -// .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), .{ -// .base = src_reg, -// .scale_index = scale_index, -// .disp = index_reg_disp.disp, -// }) }, -// }); -// }, -// 0b11 => return emit.fail("TODO unused LEA variant 0b11", .{}), -// } -// } + try emit.encode(.lea, .{ + .op1 = .{ .reg = reg }, + .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(reg.bitSize()), 0) }, + }); -// fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .lea_pic); -// const ops = emit.mir.instructions.items(.ops)[inst].decode(); -// const relocation = emit.mir.instructions.items(.data)[inst].relocation; + const end_offset = emit.code.items.len; -// switch (ops.flags) { -// 0b00, 0b01, 0b10 => {}, -// else => return emit.fail("TODO unused LEA PIC variant 0b11", .{}), -// } - -// try emit.encode(.lea, .{ -// .op1 = .{ .reg = ops.reg1 }, -// .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(ops.reg1.bitSize()), 0) }, -// }); - -// const end_offset = emit.code.items.len; - -// if (emit.bin_file.cast(link.File.MachO)) |macho_file| { -// const reloc_type = switch (ops.flags) { -// 0b00 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), -// 0b01 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), -// else => unreachable, -// }; -// const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; -// try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ -// .type = reloc_type, -// .target = .{ .sym_index = relocation.sym_index, .file = null }, -// .offset = @intCast(u32, end_offset - 4), -// .addend = 0, -// .pcrel = true, -// .length = 2, -// }); -// } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { -// const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; -// try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ -// .type = switch (ops.flags) { -// 0b00 => .got, -// 0b01 => .direct, -// 0b10 => .import, -// else => unreachable, -// }, -// .target = switch (ops.flags) { -// 0b00, 0b01 => .{ .sym_index = relocation.sym_index, .file = null }, -// 0b10 => coff_file.getGlobalByIndex(relocation.sym_index), -// else => unreachable, -// }, -// .offset = @intCast(u32, end_offset - 4), -// .addend = 0, -// .pcrel = true, -// .length = 2, -// }); -// } else { -// return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{}); -// } -// } - -// fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { -// const tag = emit.mir.instructions.items(.tag)[inst]; -// assert(tag == .call_extern); -// const relocation = emit.mir.instructions.items(.data)[inst].relocation; - -// const offset = blk: { -// // callq -// try emit.encode(.call, .{ -// .op1 = .{ .imm = Immediate.s(0) }, -// }); -// break :blk @intCast(u32, emit.code.items.len) - 4; -// }; - -// if (emit.bin_file.cast(link.File.MachO)) |macho_file| { -// // Add relocation to the decl. -// const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; -// const target = macho_file.getGlobalByIndex(relocation.sym_index); -// try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ -// .type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), -// .target = target, -// .offset = offset, -// .addend = 0, -// .pcrel = true, -// .length = 2, -// }); -// } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { -// // Add relocation to the decl. -// const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?; -// const target = coff_file.getGlobalByIndex(relocation.sym_index); -// try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ -// .type = .direct, -// .target = target, -// .offset = offset, -// .addend = 0, -// .pcrel = true, -// .length = 2, -// }); -// } else { -// return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{}); -// } -// } + if (emit.bin_file.cast(link.File.MachO)) |macho_file| { + const reloc_type = switch (ops) { + .got_reloc => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), + .direct_reloc => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), + else => unreachable, + }; + const atom_index = macho_file.getAtomIndexForSymbol(.{ + .sym_index = metadata.atom_index, + .file = null, + }).?; + try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{ + .type = reloc_type, + .target = .{ .sym_index = metadata.sym_index, .file = null }, + .offset = @intCast(u32, end_offset - 4), + .addend = 0, + .pcrel = true, + .length = 2, + }); + } else if (emit.bin_file.cast(link.File.Coff)) |coff_file| { + const atom_index = coff_file.getAtomIndexForSymbol(.{ + .sym_index = metadata.atom_index, + .file = null, + }).?; + try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{ + .type = switch (ops) { + .got_reloc => .got, + .direct_reloc => .direct, + .import_reloc => .import, + else => unreachable, + }, + .target = switch (ops) { + .got_reloc, .direct_reloc => .{ .sym_index = metadata.sym_index, .file = null }, + .import_reloc => coff_file.getGlobalByIndex(metadata.sym_index), + else => unreachable, + }, + .offset = @intCast(u32, end_offset - 4), + .addend = 0, + .pcrel = true, + .length = 2, + }); + } else { + return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{}); + } +} fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const payload = emit.mir.instructions.items(.data)[inst].payload; diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 40fd1953de..6f0d578662 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -142,6 +142,13 @@ pub const Inst = struct { /// Jump with relocation to another local MIR instruction jmp_reloc, + /// Call to an extern symbol via linker relocation. + /// Uses `relocation` payload. + call_extern, + + /// Load effective address of a symbol not yet allocated in VM. + lea_linker, + /// End of prologue dbg_prologue_end, /// Start of epilogue @@ -169,6 +176,12 @@ pub const Inst = struct { /// Register, register, register operands. /// Uses `rrr` payload. rrr, + /// Register, register, immediate (sign-extended) operands. + /// Uses `rri_s` payload. + rri_s, + /// Register, register, immediate (unsigned) operands. + /// Uses `rri_u` payload. + rri_u, /// Register with condition code (CC). /// Uses `r_c` payload. r_c, @@ -199,12 +212,6 @@ pub const Inst = struct { /// Register, memory (RIP) operands. /// Uses `rx` payload. rm_rip, - /// Register, memory, immediate (unsigned) operands - /// Uses `rx` payload. - rmi_u, - /// Register, memory, immediate (sign-extended) operands - /// Uses `rx` payload. - rmi_s, /// Single memory (SIB) operand. /// Uses `payload` with extra data of type `MemorySib`. m_sib, @@ -250,6 +257,15 @@ pub const Inst = struct { rm_cc, /// Uses `reloc` payload. reloc, + /// Linker relocation - GOT indirection. + /// Uses `payload` payload with extra data of type `LeaRegisterReloc`. + got_reloc, + /// Linker relocation - direct reference. + /// Uses `payload` payload with extra data of type `LeaRegisterReloc`. + direct_reloc, + /// Linker relocation - imports table indirection (binding). + /// Uses `payload` payload with extra data of type `LeaRegisterReloc`. + import_reloc, }; pub const Data = union { @@ -279,6 +295,16 @@ pub const Inst = struct { r2: Register, r3: Register, }, + rri_s: struct { + r1: Register, + r2: Register, + imm: i32, + }, + rri_u: struct { + r1: Register, + r2: Register, + imm: u32, + }, /// Register with condition code (CC). r_c: struct { r1: Register, @@ -339,13 +365,7 @@ pub const Inst = struct { pub const LeaRegisterReloc = struct { /// Destination register. - reg: Register, - /// Type of the load. - load_type: enum(u2) { - got, - direct, - import, - }, + reg: u32, /// Index of the containing atom. atom_index: u32, /// Index into the linker's symbol table. From fe1fab4a8ee8d908ab592c63bbc35bbbaa1ed0bf Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 10 Mar 2023 19:23:01 +0100 Subject: [PATCH 18/31] x86_64: fix CALL emits for ELF and Plan9 --- src/arch/x86_64/CodeGen.zig | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 9056b64dce..6ba81aecdd 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -4001,7 +4001,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl); const atom = elf_file.getAtom(atom_index); const got_addr = atom.getOffsetTableAddress(elf_file); - try self.asmImmediate(.call, Immediate.s(@intCast(i32, got_addr))); + try self.asmMemory(.call, Memory.sib(.qword, .{ + .base = .ds, + .disp = @intCast(i32, got_addr), + })); } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { const atom_index = try coff_file.getOrCreateAtomForDecl(func.owner_decl); const sym_index = coff_file.getAtom(atom_index).getSymbolIndex().?; @@ -4030,7 +4033,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const got_addr = p9.bases.data; const got_index = decl_block.got_index.?; const fn_got_addr = got_addr + got_index * ptr_bytes; - try self.asmImmediate(.call, Immediate.s(@intCast(i32, fn_got_addr))); + try self.asmMemory(.call, Memory.sib(.qword, .{ + .base = .ds, + .disp = @intCast(i32, fn_got_addr), + })); } else unreachable; } else if (func_value.castTag(.extern_fn)) |func_payload| { const extern_fn = func_payload.data; From e34e7d5ad1a61c66c145af612e11b7c6500caf79 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 10 Mar 2023 19:32:48 +0100 Subject: [PATCH 19/31] x86_64: add missing decodings for .movsx --- src/arch/x86_64/CodeGen.zig | 2 +- src/arch/x86_64/Emit.zig | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 6ba81aecdd..cd2b90051d 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -5841,7 +5841,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (intrinsicsAllowed(self.target.*, ty)) { const tag: Mir.Inst.Tag = switch (ty.tag()) { .f32 => .movss, - .f64 => .movsx, + .f64 => .movsd, else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}), }; const ptr_size: Memory.PtrSize = switch (ty.tag()) { diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 5b3b03e8ee..c490dea497 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -328,6 +328,16 @@ fn mirMovsx(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { op1 = .{ .reg = data.rr.r1 }; op2 = .{ .reg = data.rr.r2 }; }, + .rm_sib => { + const msib = emit.mir.extraData(Mir.MemorySib, data.rx.payload).data; + op1 = .{ .reg = data.rx.r1 }; + op2 = .{ .mem = Mir.MemorySib.decode(msib) }; + }, + .rm_rip => { + const mrip = emit.mir.extraData(Mir.MemoryRip, data.rx.payload).data; + op1 = .{ .reg = data.rx.r1 }; + op2 = .{ .mem = Mir.MemoryRip.decode(mrip) }; + }, else => unreachable, // TODO } From 6e1da365038856d9fbff690f187dc0a5c0933440 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 10 Mar 2023 19:38:15 +0100 Subject: [PATCH 20/31] x86_64: PtrSize.fromSize() should take into account nonexact sizes too --- src/arch/x86_64/bits.zig | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index b6ac9ec5a8..1828cdc08f 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -418,14 +418,18 @@ pub const Memory = union(enum) { tbyte, pub fn fromSize(size: u32) PtrSize { - return switch (size) { - 1 => .byte, - 2 => .word, - 4 => .dword, - 8 => .qword, - 10 => .tbyte, - else => unreachable, - }; + return if (size <= 1) + .byte + else if (size <= 2) + .word + else if (size <= 4) + .dword + else if (size <= 8) + .qword + else if (size == 10) + .tbyte + else + unreachable; } pub fn fromBitSize(bit_size: u64) PtrSize { From 21630ea17f1db8791c86ccb6b5e64c7390c52f61 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 00:09:24 +0100 Subject: [PATCH 21/31] x86_64: apply couple of tweaks and pass behavior tests --- src/arch/x86_64/CodeGen.zig | 59 ++++++++++++++++++++++++++----------- src/arch/x86_64/Emit.zig | 5 +++- src/arch/x86_64/bits.zig | 2 +- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index cd2b90051d..9ad89b48ba 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2856,10 +2856,14 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type .immediate => |imm| { switch (abi_size) { 1, 2, 4 => { + const immediate = if (value_ty.isSignedInt()) + Immediate.s(@intCast(i32, @bitCast(i64, imm))) + else + Immediate.u(@truncate(u32, imm)); try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = reg.to64(), .disp = 0, - }), Immediate.u(@truncate(u32, imm))); + }), immediate); }, 8 => { // TODO: optimization: if the imm is only using the lower @@ -3580,7 +3584,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) { .f32 => switch (mir_tag) { .add => .addss, - .cmp => .cmpss, + .cmp => .ucomiss, else => return self.fail( "TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}, @@ -3588,7 +3592,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu }, .f64 => switch (mir_tag) { .add => .addsd, - .cmp => .cmpsd, + .cmp => .ucomisd, else => return self.fail( "TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}, @@ -3599,7 +3603,7 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu .{dst_ty.fmtDebug()}, ), }; - try self.asmRegisterRegister(actual_tag, dst_reg.to128(), src_reg.to128()); + return self.asmRegisterRegister(actual_tag, dst_reg.to128(), src_reg.to128()); } return self.fail("TODO genBinOpMir for float register-register and no intrinsics", .{}); @@ -5255,11 +5259,14 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE // TODO // We have a positive stack offset value but we want a twos complement negative // offset from rbp, which is at the top of the stack frame. - // mov [rbp+offset], immediate + const immediate = if (ty.isSignedInt()) + Immediate.s(@intCast(i32, @bitCast(i64, imm))) + else + Immediate.u(@intCast(u32, imm)); try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = .rsp, .disp = -stack_offset, - }), Immediate.u(@intCast(u32, imm))); + }), immediate); }, 8 => { const reg = try self.copyToTmpRegister(ty, mcv); @@ -5340,10 +5347,19 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl if (!self.wantSafety()) return; // The already existing value will do just fine. // TODO Upgrade this to a memset call when we have that available. - switch (ty.abiSize(self.target.*)) { - 1 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaa }, opts), - 2 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaa }, opts), - 4 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }, opts), + switch (abi_size) { + 1, 2, 4 => { + const value: u64 = switch (abi_size) { + 1 => 0xaa, + 2 => 0xaaaa, + 4 => 0xaaaaaaaa, + else => unreachable, + }; + return self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ + .base = opts.dest_stack_base orelse .rbp, + .disp = -stack_offset, + }), Immediate.u(value)); + }, 8 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }, opts), else => |x| return self.genInlineMemset( .{ .stack_offset = stack_offset }, @@ -5385,13 +5401,17 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl try self.asmMemoryImmediate(.mov, Memory.sib(.byte, .{ .base = base_reg, .disp = -stack_offset, - }), Immediate.u(@truncate(u32, x_big))); + }), Immediate.u(@truncate(u8, x_big))); }, 1, 2, 4 => { + const immediate = if (ty.isSignedInt()) + Immediate.s(@truncate(i32, @bitCast(i64, x_big))) + else + Immediate.u(@intCast(u32, x_big)); try self.asmMemoryImmediate(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = base_reg, .disp = -stack_offset, - }), Immediate.u(@truncate(u32, x_big))); + }), immediate); }, 8 => { // 64 bit write to memory would take two mov's anyways so we @@ -5777,12 +5797,15 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void // register is the fastest way to zero a register. return self.asmRegisterRegister(.xor, reg.to32(), reg.to32()); } - if (ty.isSignedInt() and x <= math.maxInt(i32)) { - return self.asmRegisterImmediate( - .mov, - registerAlias(reg, abi_size), - Immediate.s(@intCast(i32, @bitCast(i64, x))), - ); + if (ty.isSignedInt()) { + const signed_x = @bitCast(i64, x); + if (math.minInt(i32) <= signed_x and signed_x <= math.maxInt(i32)) { + return self.asmRegisterImmediate( + .mov, + registerAlias(reg, abi_size), + Immediate.s(@intCast(i32, signed_x)), + ); + } } return self.asmRegisterImmediate( .mov, diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index c490dea497..a660e3f9ba 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -341,7 +341,10 @@ fn mirMovsx(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { else => unreachable, // TODO } - const mnemonic: Instruction.Mnemonic = if (op1.bitSize() == 64 and op2.bitSize() == 32) .movsxd else .movsx; + const mnemonic: Instruction.Mnemonic = switch (op1.bitSize()) { + 32, 64 => if (op2.bitSize() == 32) .movsxd else .movsx, + else => .movsx, + }; return emit.encode(mnemonic, .{ .op1 = op1, diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index 1828cdc08f..d974070e5d 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -542,7 +542,7 @@ pub const Immediate = union(enum) { .signed => |x| switch (bit_size) { 1, 8 => @bitCast(u8, @intCast(i8, x)), 16 => @bitCast(u16, @intCast(i16, x)), - 32 => @bitCast(u32, @intCast(i32, x)), + 32, 64 => @bitCast(u32, x), else => unreachable, }, .unsigned => |x| switch (bit_size) { From 621fc36b55a882c562d9378d4cf1fd8a5e1a907c Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 09:21:41 +0100 Subject: [PATCH 22/31] x86_64: add wrapper for .jmp_reloc --- src/arch/x86_64/CodeGen.zig | 48 +++++++++++-------------------------- src/arch/x86_64/Emit.zig | 28 +++++++++------------- src/arch/x86_64/Mir.zig | 4 +--- 3 files changed, 26 insertions(+), 54 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 9ad89b48ba..fabca1d848 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -423,6 +423,14 @@ fn asmCmovccRegisterRegister(self: *Self, reg1: Register, reg2: Register, cc: bi }); } +fn asmJmpReloc(self: *Self, target: Mir.Inst.Index) !Mir.Inst.Index { + return self.addInst(.{ + .tag = .jmp_reloc, + .ops = undefined, + .data = .{ .inst = target }, + }); +} + fn asmNone(self: *Self, tag: Mir.Inst.Tag) !void { _ = try self.addInst(.{ .tag = tag, @@ -4145,11 +4153,7 @@ fn airRet(self: *Self, inst: Air.Inst.Index) !void { // TODO when implementing defer, this will need to jump to the appropriate defer expression. // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. - const jmp_reloc = try self.addInst(.{ - .tag = .jmp_reloc, - .ops = .inst, - .data = .{ .inst = undefined }, - }); + const jmp_reloc = try self.asmJmpReloc(undefined); try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -4181,11 +4185,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void { // TODO when implementing defer, this will need to jump to the appropriate defer expression. // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction // which is available if the jump is 127 bytes or less forward. - const jmp_reloc = try self.addInst(.{ - .tag = .jmp_reloc, - .ops = .inst, - .data = .{ .inst = undefined }, - }); + const jmp_reloc = try self.asmJmpReloc(undefined); try self.exitlude_jump_relocs.append(self.gpa, jmp_reloc); return self.finishAir(inst, .dead, .{ un_op, .none, .none }); } @@ -4722,11 +4722,7 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void { const body = self.air.extra[loop.end..][0..loop.data.body_len]; const jmp_target = @intCast(u32, self.mir_instructions.len); try self.genBody(body); - _ = try self.addInst(.{ - .tag = .jmp_reloc, - .ops = .inst, - .data = .{ .inst = jmp_target }, - }); + _ = try self.asmJmpReloc(jmp_target); return self.finishAirBookkeeping(); } @@ -5062,11 +5058,7 @@ fn brVoid(self: *Self, block: Air.Inst.Index) !void { // Emit a jump with a relocation. It will be patched up after the block ends. try block_data.relocs.ensureUnusedCapacity(self.gpa, 1); // Leave the jump offset undefined - const jmp_reloc = try self.addInst(.{ - .tag = .jmp_reloc, - .ops = .inst, - .data = .{ .inst = undefined }, - }); + const jmp_reloc = try self.asmJmpReloc(undefined); block_data.relocs.appendAssumeCapacity(jmp_reloc); } @@ -5656,13 +5648,7 @@ fn genInlineMemcpy( }), tmp_reg.to8()); try self.asmRegisterImmediate(.add, index_reg, Immediate.u(1)); try self.asmRegisterImmediate(.sub, count_reg, Immediate.u(1)); - - _ = try self.addInst(.{ - .tag = .jmp_reloc, - .ops = .inst, - .data = .{ .inst = loop_start }, - }); - + _ = try self.asmJmpReloc(loop_start); try self.performReloc(loop_reloc); } @@ -5750,13 +5736,7 @@ fn genInlineMemset( } try self.asmRegisterImmediate(.sub, index_reg, Immediate.u(1)); - - _ = try self.addInst(.{ - .tag = .jmp_reloc, - .ops = .inst, - .data = .{ .inst = loop_start }, - }); - + _ = try self.asmJmpReloc(loop_start); try self.performReloc(loop_reloc); } diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index a660e3f9ba..6e92e81882 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -410,23 +410,17 @@ fn mirJcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } fn mirJmpReloc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const ops = emit.mir.instructions.items(.ops)[inst]; - switch (ops) { - .inst => { - const target = emit.mir.instructions.items(.data)[inst].inst; - const source = emit.code.items.len; - try emit.encode(.jmp, .{ - .op1 = .{ .imm = Immediate.s(0) }, - }); - try emit.relocs.append(emit.bin_file.allocator, .{ - .source = source, - .target = target, - .offset = emit.code.items.len - 4, - .length = 5, - }); - }, - else => unreachable, - } + const target = emit.mir.instructions.items(.data)[inst].inst; + const source = emit.code.items.len; + try emit.encode(.jmp, .{ + .op1 = .{ .imm = Immediate.s(0) }, + }); + try emit.relocs.append(emit.bin_file.allocator, .{ + .source = source, + .target = target, + .offset = emit.code.items.len - 4, + .length = 5, + }); } fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 6f0d578662..5de8ad1410 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -140,6 +140,7 @@ pub const Inst = struct { mov_moffs, /// Jump with relocation to another local MIR instruction + /// Uses `inst` payload. jmp_reloc, /// Call to an extern symbol via linker relocation. @@ -242,9 +243,6 @@ pub const Inst = struct { /// Memory moffs, rax. /// Uses `payload` with extra data of type `MemoryMoffs`. moffs_rax, - /// Lea into register with linker relocation. - /// Uses `payload` payload with data of type `LeaRegisterReloc`. - lea_r_reloc, /// References another Mir instruction directly. /// Uses `inst` payload. inst, From c9a153c7978b363a252f83878f75bd875fe6ae5e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 09:29:53 +0100 Subject: [PATCH 23/31] x86_64: add .dead pseudo-instruction to mark an unused MIR instruction --- src/arch/x86_64/CodeGen.zig | 16 ++++++++-------- src/arch/x86_64/Emit.zig | 2 ++ src/arch/x86_64/Mir.zig | 4 ++++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index fabca1d848..feb78ca7fc 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -630,8 +630,8 @@ fn gen(self: *Self) InnerError!void { // TODO During semantic analysis, check if there are no function calls. If there // are none, here we can omit the part where we subtract and then add rsp. const backpatch_stack_sub = try self.addInst(.{ - .tag = .nop, - .ops = .none, + .tag = .dead, + .ops = undefined, .data = undefined, }); @@ -657,8 +657,8 @@ fn gen(self: *Self) InnerError!void { // Push callee-preserved regs that were used actually in use. const backpatch_push_callee_preserved_regs = try self.addInst(.{ - .tag = .nop, - .ops = .none, + .tag = .dead, + .ops = undefined, .data = undefined, }); @@ -688,8 +688,8 @@ fn gen(self: *Self) InnerError!void { // Pop saved callee-preserved regs. const backpatch_pop_callee_preserved_regs = try self.addInst(.{ - .tag = .nop, - .ops = .none, + .tag = .dead, + .ops = undefined, .data = undefined, }); @@ -701,8 +701,8 @@ fn gen(self: *Self) InnerError!void { // Maybe add rsp, x if required. This is backpatched later. const backpatch_stack_add = try self.addInst(.{ - .tag = .nop, - .ops = .none, + .tag = .dead, + .ops = undefined, .data = undefined, }); diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 6e92e81882..161c436323 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -137,6 +137,8 @@ pub fn lowerMir(emit: *Emit) InnerError!void { .push_regs => try emit.mirPushPopRegisterList(.push, inst), .pop_regs => try emit.mirPushPopRegisterList(.pop, inst), + + .dead => {}, } } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 5de8ad1410..4cde4dd240 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -163,6 +163,10 @@ pub const Inst = struct { /// Pop registers /// Uses `payload` payload with data of type `SaveRegisterList`. pop_regs, + + /// Tombstone + /// Emitter should skip this instruction. + dead, }; pub const Ops = enum(u8) { From 0a8b5c20aa2402361a4e5698100902e47bd2c7c6 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 09:37:54 +0100 Subject: [PATCH 24/31] x86_64: add wrapper for .jcc with relocation --- src/arch/x86_64/CodeGen.zig | 64 ++++++++++--------------------------- 1 file changed, 17 insertions(+), 47 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index feb78ca7fc..21c7adec3c 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -431,6 +431,17 @@ fn asmJmpReloc(self: *Self, target: Mir.Inst.Index) !Mir.Inst.Index { }); } +fn asmJccReloc(self: *Self, target: Mir.Inst.Index, cc: bits.Condition) !Mir.Inst.Index { + return self.addInst(.{ + .tag = .jcc, + .ops = .inst_cc, + .data = .{ .inst_cc = .{ + .inst = target, + .cc = cc, + } }, + }); +} + fn asmNone(self: *Self, tag: Mir.Inst.Tag) !void { _ = try self.addInst(.{ .tag = tag, @@ -4360,29 +4371,13 @@ fn genCondBrMir(self: *Self, ty: Type, mcv: MCValue) !u32 { const abi_size = ty.abiSize(self.target.*); switch (mcv) { .eflags => |cc| { - return self.addInst(.{ - .tag = .jcc, - .ops = .inst_cc, - .data = .{ - .inst_cc = .{ - .inst = undefined, - // Here we map the opposites since the jump is to the false branch. - .cc = cc.negate(), - }, - }, - }); + // Here we map the opposites since the jump is to the false branch. + return self.asmJccReloc(undefined, cc.negate()); }, .register => |reg| { try self.spillEflagsIfOccupied(); try self.asmRegisterImmediate(.@"test", reg, Immediate.u(1)); - return self.addInst(.{ - .tag = .jcc, - .ops = .inst_cc, - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .e, - } }, - }); + return self.asmJccReloc(undefined, .e); }, .immediate, .stack_offset, @@ -4792,15 +4787,7 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u const aliased_reg = registerAlias(cond_reg, abi_size); try self.asmRegisterRegister(.@"test", aliased_reg, aliased_reg); - - return self.addInst(.{ - .tag = .jcc, - .ops = .inst_cc, - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .ne, - } }, - }); + return self.asmJccReloc(undefined, .ne); }, .stack_offset => { try self.spillEflagsIfOccupied(); @@ -5612,7 +5599,6 @@ fn genInlineMemcpy( try self.genSetReg(Type.usize, count_reg, len); try self.asmRegisterImmediate(.mov, index_reg, Immediate.u(0)); - const loop_start = try self.addInst(.{ .tag = .cmp, .ops = .ri_u, @@ -5621,15 +5607,7 @@ fn genInlineMemcpy( .imm = 0, } }, }); - const loop_reloc = try self.addInst(.{ - .tag = .jcc, - .ops = .inst_cc, - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .e, - } }, - }); - + const loop_reloc = try self.asmJccReloc(undefined, .e); try self.asmRegisterMemory(.mov, tmp_reg.to8(), Memory.sib(.byte, .{ .base = src_addr_reg, .scale_index = .{ @@ -5708,15 +5686,7 @@ fn genInlineMemset( .imm = -1, } }, }); - - const loop_reloc = try self.addInst(.{ - .tag = .jcc, - .ops = .inst_cc, - .data = .{ .inst_cc = .{ - .inst = undefined, - .cc = .e, - } }, - }); + const loop_reloc = try self.asmJccReloc(undefined, .e); switch (value) { .immediate => |x| { From fb38e3d6b29f71834c7ea4ef21e4d3f607c03777 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 16:32:30 +0100 Subject: [PATCH 25/31] x86_64: simplify immediate handling at MIR level --- src/arch/x86_64/CodeGen.zig | 77 +++++++++---------- src/arch/x86_64/Emit.zig | 142 +++++++++++++++++++----------------- src/arch/x86_64/Mir.zig | 55 +++++--------- 3 files changed, 128 insertions(+), 146 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 21c7adec3c..df06b90e4a 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -460,15 +460,13 @@ fn asmRegister(self: *Self, tag: Mir.Inst.Tag, reg: Register) !void { fn asmImmediate(self: *Self, tag: Mir.Inst.Tag, imm: Immediate) !void { const ops: Mir.Inst.Ops = if (imm == .signed) .imm_s else .imm_u; - const data: Mir.Inst.Data = switch (ops) { - .imm_s => .{ .imm_s = imm.signed }, - .imm_u => .{ .imm_u = @intCast(u32, imm.unsigned) }, - else => unreachable, - }; _ = try self.addInst(.{ .tag = tag, .ops = ops, - .data = data, + .data = .{ .imm = switch (imm) { + .signed => |x| @bitCast(u32, x), + .unsigned => |x| @intCast(u32, x), + } }, }); } @@ -489,11 +487,11 @@ fn asmRegisterImmediate(self: *Self, tag: Mir.Inst.Tag, reg: Register, imm: Imme .unsigned => |x| if (x <= math.maxInt(u32)) .ri_u else .ri64, }; const data: Mir.Inst.Data = switch (ops) { - .ri_s => .{ .ri_s = .{ + .ri_s => .{ .ri = .{ .r1 = reg, - .imm = imm.signed, + .imm = @bitCast(u32, imm.signed), } }, - .ri_u => .{ .ri_u = .{ + .ri_u => .{ .ri = .{ .r1 = reg, .imm = @intCast(u32, imm.unsigned), } }, @@ -522,12 +520,12 @@ fn asmRegisterRegisterImmediate( .unsigned => .rri_u, }; const data: Mir.Inst.Data = switch (ops) { - .rri_s => .{ .rri_s = .{ + .rri_s => .{ .rri = .{ .r1 = reg1, .r2 = reg2, - .imm = imm.signed, + .imm = @bitCast(u32, imm.signed), } }, - .rri_u => .{ .rri_u = .{ + .rri_u => .{ .rri = .{ .r1 = reg1, .r2 = reg2, .imm = @intCast(u32, imm.unsigned), @@ -547,11 +545,11 @@ fn asmMemory(self: *Self, tag: Mir.Inst.Tag, m: Memory) !void { .rip => .m_rip, else => unreachable, }; - const data: Mir.Inst.Data = switch (ops) { - .m_sib => .{ .payload = try self.addExtra(Mir.MemorySib.encode(m)) }, - .m_rip => .{ .payload = try self.addExtra(Mir.MemoryRip.encode(m)) }, + const data: Mir.Inst.Data = .{ .payload = switch (ops) { + .m_sib => try self.addExtra(Mir.MemorySib.encode(m)), + .m_rip => try self.addExtra(Mir.MemoryRip.encode(m)), else => unreachable, - }; + } }; _ = try self.addInst(.{ .tag = tag, .ops = ops, @@ -570,10 +568,11 @@ fn asmMemoryImmediate(self: *Self, tag: Mir.Inst.Tag, m: Memory, imm: Immediate) .mi_s_rip, .mi_u_rip => try self.addExtra(Mir.MemoryRip.encode(m)), else => unreachable, }; - const data: Mir.Inst.Data = switch (ops) { - .mi_s_sib, .mi_s_rip => .{ .xi_s = .{ .imm = imm.signed, .payload = payload } }, - .mi_u_sib, .mi_u_rip => .{ .xi_u = .{ .imm = @intCast(u32, imm.unsigned), .payload = payload } }, - else => unreachable, + const data: Mir.Inst.Data = .{ + .xi = .{ .payload = payload, .imm = switch (imm) { + .signed => |x| @bitCast(u32, x), + .unsigned => |x| @intCast(u32, x), + } }, }; _ = try self.addInst(.{ .tag = tag, @@ -588,16 +587,12 @@ fn asmRegisterMemory(self: *Self, tag: Mir.Inst.Tag, reg: Register, m: Memory) ! .rip => .rm_rip, else => unreachable, }; - const data: Mir.Inst.Data = switch (ops) { - .rm_sib => .{ .rx = .{ - .r1 = reg, - .payload = try self.addExtra(Mir.MemorySib.encode(m)), + const data: Mir.Inst.Data = .{ + .rx = .{ .r1 = reg, .payload = switch (ops) { + .rm_sib => try self.addExtra(Mir.MemorySib.encode(m)), + .rm_rip => try self.addExtra(Mir.MemoryRip.encode(m)), + else => unreachable, } }, - .rm_rip => .{ .rx = .{ - .r1 = reg, - .payload = try self.addExtra(Mir.MemoryRip.encode(m)), - } }, - else => unreachable, }; _ = try self.addInst(.{ .tag = tag, @@ -612,16 +607,12 @@ fn asmMemoryRegister(self: *Self, tag: Mir.Inst.Tag, m: Memory, reg: Register) ! .rip => .mr_rip, else => unreachable, }; - const data: Mir.Inst.Data = switch (ops) { - .mr_sib => .{ .rx = .{ - .r1 = reg, - .payload = try self.addExtra(Mir.MemorySib.encode(m)), + const data: Mir.Inst.Data = .{ + .rx = .{ .r1 = reg, .payload = switch (ops) { + .mr_sib => try self.addExtra(Mir.MemorySib.encode(m)), + .mr_rip => try self.addExtra(Mir.MemoryRip.encode(m)), + else => unreachable, } }, - .mr_rip => .{ .rx = .{ - .r1 = reg, - .payload = try self.addExtra(Mir.MemoryRip.encode(m)), - } }, - else => unreachable, }; _ = try self.addInst(.{ .tag = tag, @@ -733,7 +724,7 @@ fn gen(self: *Self) InnerError!void { self.mir_instructions.set(backpatch_stack_sub, .{ .tag = .sub, .ops = .ri_u, - .data = .{ .ri_u = .{ + .data = .{ .ri = .{ .r1 = .rsp, .imm = aligned_stack_end, } }, @@ -741,7 +732,7 @@ fn gen(self: *Self) InnerError!void { self.mir_instructions.set(backpatch_stack_add, .{ .tag = .add, .ops = .ri_u, - .data = .{ .ri_u = .{ + .data = .{ .ri = .{ .r1 = .rsp, .imm = aligned_stack_end, } }, @@ -5602,7 +5593,7 @@ fn genInlineMemcpy( const loop_start = try self.addInst(.{ .tag = .cmp, .ops = .ri_u, - .data = .{ .ri_u = .{ + .data = .{ .ri = .{ .r1 = count_reg, .imm = 0, } }, @@ -5681,9 +5672,9 @@ fn genInlineMemset( const loop_start = try self.addInst(.{ .tag = .cmp, .ops = .ri_s, - .data = .{ .ri_s = .{ + .data = .{ .ri = .{ .r1 = index_reg, - .imm = -1, + .imm = @bitCast(u32, @as(i32, -1)), } }, }); const loop_reloc = try self.asmJccReloc(undefined, .e); diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 161c436323..5a7f2ef224 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -194,105 +194,115 @@ fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE const ops = emit.mir.instructions.items(.ops)[inst]; const data = emit.mir.instructions.items(.data)[inst]; - var operands = [4]Instruction.Operand{ .none, .none, .none, .none }; + var op1: Instruction.Operand = .none; + var op2: Instruction.Operand = .none; + var op3: Instruction.Operand = .none; + var op4: Instruction.Operand = .none; + switch (ops) { .none => {}, - .imm_s => operands[0] = .{ .imm = Immediate.s(data.imm_s) }, - .imm_u => operands[0] = .{ .imm = Immediate.u(data.imm_u) }, - .r => operands[0] = .{ .reg = data.r }, - .rr => operands[0..2].* = .{ - .{ .reg = data.rr.r1 }, - .{ .reg = data.rr.r2 }, + .imm_s => op1 = .{ .imm = Immediate.s(@bitCast(i32, data.imm)) }, + .imm_u => op1 = .{ .imm = Immediate.u(data.imm) }, + .r => op1 = .{ .reg = data.r }, + .rr => { + op1 = .{ .reg = data.rr.r1 }; + op2 = .{ .reg = data.rr.r2 }; }, - .ri_s => operands[0..2].* = .{ - .{ .reg = data.ri_s.r1 }, - .{ .imm = Immediate.s(data.ri_s.imm) }, - }, - .ri_u => operands[0..2].* = .{ - .{ .reg = data.ri_u.r1 }, - .{ .imm = Immediate.u(data.ri_u.imm) }, + .ri_s, .ri_u => { + const imm = switch (ops) { + .ri_s => Immediate.s(@bitCast(i32, data.ri.imm)), + .ri_u => Immediate.u(data.ri.imm), + else => unreachable, + }; + op1 = .{ .reg = data.ri.r1 }; + op2 = .{ .imm = imm }; }, .ri64 => { const imm64 = emit.mir.extraData(Mir.Imm64, data.rx.payload).data; - operands[0..2].* = .{ - .{ .reg = data.rx.r1 }, - .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }, + op1 = .{ .reg = data.rx.r1 }; + op2 = .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) }; + }, + .rri_s, .rri_u => { + const imm = switch (ops) { + .rri_s => Immediate.s(@bitCast(i32, data.rri.imm)), + .rri_u => Immediate.u(data.rri.imm), + else => unreachable, }; - }, - .rri_s => operands[0..3].* = .{ - .{ .reg = data.rri_s.r1 }, - .{ .reg = data.rri_s.r2 }, - .{ .imm = Immediate.s(data.rri_s.imm) }, - }, - .rri_u => operands[0..3].* = .{ - .{ .reg = data.rri_u.r1 }, - .{ .reg = data.rri_u.r2 }, - .{ .imm = Immediate.u(data.rri_u.imm) }, + op1 = .{ .reg = data.rri.r1 }; + op2 = .{ .reg = data.rri.r2 }; + op3 = .{ .imm = imm }; }, .m_sib => { const msib = emit.mir.extraData(Mir.MemorySib, data.payload).data; - operands[0] = .{ .mem = Mir.MemorySib.decode(msib) }; + op1 = .{ .mem = Mir.MemorySib.decode(msib) }; }, .m_rip => { const mrip = emit.mir.extraData(Mir.MemoryRip, data.payload).data; - operands[0] = .{ .mem = Mir.MemoryRip.decode(mrip) }; + op1 = .{ .mem = Mir.MemoryRip.decode(mrip) }; }, - .mi_u_sib => { - const msib = emit.mir.extraData(Mir.MemorySib, data.xi_u.payload).data; - operands[0..2].* = .{ - .{ .mem = Mir.MemorySib.decode(msib) }, - .{ .imm = Immediate.u(data.xi_u.imm) }, + .mi_s_sib, .mi_u_sib => { + const msib = emit.mir.extraData(Mir.MemorySib, data.xi.payload).data; + const imm = switch (ops) { + .mi_s_sib => Immediate.s(@bitCast(i32, data.xi.imm)), + .mi_u_sib => Immediate.u(data.xi.imm), + else => unreachable, }; + op1 = .{ .mem = Mir.MemorySib.decode(msib) }; + op2 = .{ .imm = imm }; }, - .mi_s_sib => { - const msib = emit.mir.extraData(Mir.MemorySib, data.xi_s.payload).data; - operands[0..2].* = .{ - .{ .mem = Mir.MemorySib.decode(msib) }, - .{ .imm = Immediate.s(data.xi_s.imm) }, - }; - }, - .mi_u_rip => { - const mrip = emit.mir.extraData(Mir.MemoryRip, data.xi_u.payload).data; - operands[0..2].* = .{ - .{ .mem = Mir.MemoryRip.decode(mrip) }, - .{ .imm = Immediate.u(data.xi_u.imm) }, - }; - }, - .mi_s_rip => { - const mrip = emit.mir.extraData(Mir.MemoryRip, data.xi_s.payload).data; - operands[0..2].* = .{ - .{ .mem = Mir.MemoryRip.decode(mrip) }, - .{ .imm = Immediate.s(data.xi_s.imm) }, + .mi_u_rip, .mi_s_rip => { + const mrip = emit.mir.extraData(Mir.MemoryRip, data.xi.payload).data; + const imm = switch (ops) { + .mi_s_rip => Immediate.s(@bitCast(i32, data.xi.imm)), + .mi_u_rip => Immediate.u(data.xi.imm), + else => unreachable, }; + op1 = .{ .mem = Mir.MemoryRip.decode(mrip) }; + op2 = .{ .imm = imm }; }, .rm_sib, .mr_sib => { const msib = emit.mir.extraData(Mir.MemorySib, data.rx.payload).data; - const op1 = .{ .reg = data.rx.r1 }; - const op2 = .{ .mem = Mir.MemorySib.decode(msib) }; + const op_r = .{ .reg = data.rx.r1 }; + const op_m = .{ .mem = Mir.MemorySib.decode(msib) }; switch (ops) { - .rm_sib => operands[0..2].* = .{ op1, op2 }, - .mr_sib => operands[0..2].* = .{ op2, op1 }, + .rm_sib => { + op1 = op_r; + op2 = op_m; + }, + .mr_sib => { + op1 = op_m; + op2 = op_r; + }, else => unreachable, } }, .rm_rip, .mr_rip => { const mrip = emit.mir.extraData(Mir.MemoryRip, data.rx.payload).data; - const op1 = .{ .reg = data.rx.r1 }; - const op2 = .{ .mem = Mir.MemoryRip.decode(mrip) }; + const op_r = .{ .reg = data.rx.r1 }; + const op_m = .{ .mem = Mir.MemoryRip.decode(mrip) }; switch (ops) { - .rm_rip => operands[0..2].* = .{ op1, op2 }, - .mr_rip => operands[0..2].* = .{ op2, op1 }, + .rm_sib => { + op1 = op_r; + op2 = op_m; + }, + .mr_sib => { + op1 = op_m; + op2 = op_r; + }, else => unreachable, } }, - else => unreachable, // TODO + else => return emit.fail("TODO handle generic encoding: {s}, {s}", .{ + @tagName(mnemonic), + @tagName(ops), + }), } return emit.encode(mnemonic, .{ - .op1 = operands[0], - .op2 = operands[1], - .op3 = operands[2], - .op4 = operands[3], + .op1 = op1, + .op2 = op2, + .op3 = op3, + .op4 = op4, }); } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 4cde4dd240..2f611258fd 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -182,10 +182,10 @@ pub const Inst = struct { /// Uses `rrr` payload. rrr, /// Register, register, immediate (sign-extended) operands. - /// Uses `rri_s` payload. + /// Uses `rri` payload. rri_s, /// Register, register, immediate (unsigned) operands. - /// Uses `rri_u` payload. + /// Uses `rri` payload. rri_u, /// Register with condition code (CC). /// Uses `r_c` payload. @@ -194,22 +194,22 @@ pub const Inst = struct { /// Uses `rr_c` payload. rr_c, /// Register, immediate (sign-extended) operands. - /// Uses `ri_s` payload. + /// Uses `ri` payload. ri_s, /// Register, immediate (unsigned) operands. - /// Uses `ri_u` payload. + /// Uses `ri` payload. ri_u, /// Register, 64-bit unsigned immediate operands. /// Uses `rx` payload with payload type `Imm64`. ri64, /// Immediate (sign-extended) operand. - /// Uses `imm_s` payload. + /// Uses `imm` payload. imm_s, /// Immediate (unsigned) operand. - /// Uses `imm_u` payload. + /// Uses `imm` payload. imm_u, /// Relative displacement operand. - /// Uses `rel` payload. + /// Uses `imm` payload. rel, /// Register, memory (SIB) operands. /// Uses `rx` payload. @@ -224,16 +224,16 @@ pub const Inst = struct { /// Uses `payload` with extra data of type `MemoryRip`. m_rip, /// Memory (SIB), immediate (unsigned) operands. - /// Uses `xi_u` payload with extra data of type `MemorySib`. + /// Uses `xi` payload with extra data of type `MemorySib`. mi_u_sib, /// Memory (RIP), immediate (unsigned) operands. - /// Uses `xi_u` payload with extra data of type `MemoryRip`. + /// Uses `xi` payload with extra data of type `MemoryRip`. mi_u_rip, /// Memory (SIB), immediate (sign-extend) operands. - /// Uses `xi_s` payload with extra data of type `MemorySib`. + /// Uses `xi` payload with extra data of type `MemorySib`. mi_s_sib, /// Memory (RIP), immediate (sign-extend) operands. - /// Uses `xi_s` payload with extra data of type `MemoryRip`. + /// Uses `xi` payload with extra data of type `MemoryRip`. mi_s_rip, /// Memory (SIB), register operands. /// Uses `rx` payload with extra data of type `MemorySib`. @@ -281,12 +281,8 @@ pub const Inst = struct { /// A condition code for use with EFLAGS register. cc: bits.Condition, }, - /// A 32-bit signed immediate value. - imm_s: i32, - /// A 32-bit unsigned immediate value. - imm_u: u32, - /// A 32-bit signed relative offset value. - rel: i32, + /// A 32-bit immediate value. + imm: u32, r: Register, rr: struct { r1: Register, @@ -297,12 +293,7 @@ pub const Inst = struct { r2: Register, r3: Register, }, - rri_s: struct { - r1: Register, - r2: Register, - imm: i32, - }, - rri_u: struct { + rri: struct { r1: Register, r2: Register, imm: u32, @@ -318,13 +309,8 @@ pub const Inst = struct { r2: Register, cc: bits.Condition, }, - /// Register, signed immediate. - ri_s: struct { - r1: Register, - imm: i32, - }, - /// Register, unsigned immediate. - ri_u: struct { + /// Register, immediate. + ri: struct { r1: Register, imm: u32, }, @@ -333,16 +319,11 @@ pub const Inst = struct { r1: Register, payload: u32, }, - /// Custom payload followed by an unsigned immediate. - xi_u: struct { + /// Custom payload followed by an immediate. + xi: struct { payload: u32, imm: u32, }, - /// Custom payload followed by a signed immediate. - xi_s: struct { - payload: u32, - imm: i32, - }, /// Relocation for the linker where: /// * `atom_index` is the index of the source /// * `sym_index` is the index of the target From f279ccb8078db9df92f02e23f70486f1950b92ed Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 19:33:22 +0100 Subject: [PATCH 26/31] x86_64: rename asmNone to asmOpOnly --- src/arch/x86_64/CodeGen.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index df06b90e4a..3b7dc0db57 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -442,7 +442,7 @@ fn asmJccReloc(self: *Self, target: Mir.Inst.Index, cc: bits.Condition) !Mir.Ins }); } -fn asmNone(self: *Self, tag: Mir.Inst.Tag) !void { +fn asmOpOnly(self: *Self, tag: Mir.Inst.Tag) !void { _ = try self.addInst(.{ .tag = tag, .ops = .none, @@ -709,7 +709,7 @@ fn gen(self: *Self) InnerError!void { }); try self.asmRegister(.pop, .rbp); - try self.asmNone(.ret); + try self.asmOpOnly(.ret); // Adjust the stack if (self.max_end_stack > math.maxInt(i32)) { @@ -1856,7 +1856,7 @@ fn genIntMulDivOpMir( } switch (signedness) { - .signed => try self.asmNone(.cqo), + .signed => try self.asmOpOnly(.cqo), .unsigned => try self.asmRegisterRegister(.xor, .rdx, .rdx), } @@ -3901,12 +3901,12 @@ fn genVarDbgInfo( } fn airTrap(self: *Self) !void { - try self.asmNone(.ud2); + try self.asmOpOnly(.ud2); return self.finishAirBookkeeping(); } fn airBreakpoint(self: *Self) !void { - try self.asmNone(.int3); + try self.asmOpOnly(.int3); return self.finishAirBookkeeping(); } @@ -5109,7 +5109,7 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { var iter = std.mem.tokenize(u8, asm_source, "\n\r"); while (iter.next()) |ins| { if (mem.eql(u8, ins, "syscall")) { - try self.asmNone(.syscall); + try self.asmOpOnly(.syscall); } else if (mem.indexOf(u8, ins, "push")) |_| { const arg = ins[4..]; if (mem.indexOf(u8, arg, "$")) |l| { From 433558a92f005d3ad68528c62ffd6006c48b80bd Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 11 Mar 2023 19:50:23 +0100 Subject: [PATCH 27/31] x86_64: clean up --- src/arch/x86_64/Emit.zig | 4 ++-- src/arch/x86_64/Encoding.zig | 2 +- src/arch/x86_64/Mir.zig | 11 ++++------- src/arch/x86_64/bits.zig | 7 ------- src/arch/x86_64/encoder.zig | 5 +++-- 5 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 5a7f2ef224..32699d35cb 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -177,12 +177,12 @@ fn encode(emit: *Emit, mnemonic: Instruction.Mnemonic, ops: struct { op3: Instruction.Operand = .none, op4: Instruction.Operand = .none, }) InnerError!void { - const inst = Instruction.new(mnemonic, .{ + const inst = try Instruction.new(mnemonic, .{ .op1 = ops.op1, .op2 = ops.op2, .op3 = ops.op3, .op4 = ops.op4, - }) catch unreachable; + }); return inst.encode(emit.code.writer()); } diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 94f816eaa1..c6a8d044c3 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -121,7 +121,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { .encoding = encoding, }; var cwriter = std.io.countingWriter(std.io.null_writer); - inst.encode(cwriter.writer()) catch unreachable; + inst.encode(cwriter.writer()) catch unreachable; // Not allowed to fail here unless OOM. return cwriter.bytes_written; } }; diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 2f611258fd..3951108e3a 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -24,9 +24,6 @@ instructions: std.MultiArrayList(Inst).Slice, /// The meaning of this data is determined by `Inst.Tag` value. extra: []const u32, -pub const Mnemonic = encoder.Instruction.Mnemonic; -pub const Operand = encoder.Instruction.Operand; - pub const Inst = struct { tag: Tag, ops: Ops, @@ -69,8 +66,6 @@ pub const Inst = struct { imul, /// int3, - /// Conditional jump - jcc, /// Jump jmp, /// Load effective address @@ -99,8 +94,6 @@ pub const Inst = struct { sar, /// Integer subtraction with borrow sbb, - /// Set byte on condition - setcc, /// Logical shift left shl, /// Logical shift right @@ -135,6 +128,10 @@ pub const Inst = struct { /// Conditional move cmovcc, + /// Conditional jump + jcc, + /// Set byte on condition + setcc, /// Mov absolute to/from memory wrt segment register to/from rax mov_moffs, diff --git a/src/arch/x86_64/bits.zig b/src/arch/x86_64/bits.zig index d974070e5d..043e589af4 100644 --- a/src/arch/x86_64/bits.zig +++ b/src/arch/x86_64/bits.zig @@ -242,13 +242,6 @@ pub const Register = enum(u7) { }; } - pub fn isRexInvalid(reg: Register) bool { - return switch (@enumToInt(reg)) { - @enumToInt(Register.ah)...@enumToInt(Register.bh) => true, - else => false, - }; - } - pub fn enc(reg: Register) u4 { const base = switch (@enumToInt(reg)) { // zig fmt: off diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index 292b61ee21..9206b621bc 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -1,5 +1,6 @@ const std = @import("std"); const assert = std.debug.assert; +const log = std.log.scoped(.x86_64_encoder); const math = std.math; const bits = @import("bits.zig"); @@ -106,7 +107,7 @@ pub const Instruction = struct { .op3 = args.op3, .op4 = args.op4, })) orelse { - std.log.warn("{s} {s} {s} {s} {s}", .{ + log.debug("no encoding found for: {s} {s} {s} {s} {s}", .{ @tagName(mnemonic), @tagName(Encoding.Op.fromOperand(args.op1)), @tagName(Encoding.Op.fromOperand(args.op2)), @@ -115,7 +116,7 @@ pub const Instruction = struct { }); return error.InvalidInstruction; }; - std.log.debug("{}", .{encoding}); + log.debug("selected encoding: {}", .{encoding}); return .{ .op1 = args.op1, .op2 = args.op2, From 707a74655be9fd702cb1be84baa719b29435fcf7 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 12 Mar 2023 08:41:44 +0100 Subject: [PATCH 28/31] x86_64: downstream encoder/assembler tests --- src/arch/x86_64/encoder.zig | 1489 +++++++++++++++++++++++++++++++++++ 1 file changed, 1489 insertions(+) diff --git a/src/arch/x86_64/encoder.zig b/src/arch/x86_64/encoder.zig index 9206b621bc..7e29f95069 100644 --- a/src/arch/x86_64/encoder.zig +++ b/src/arch/x86_64/encoder.zig @@ -2,6 +2,7 @@ const std = @import("std"); const assert = std.debug.assert; const log = std.log.scoped(.x86_64_encoder); const math = std.math; +const testing = std.testing; const bits = @import("bits.zig"); const Encoding = @import("Encoding.zig"); @@ -784,3 +785,1491 @@ pub const Rex = struct { return rex.w or rex.r or rex.x or rex.b; } }; + +// Tests +fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void { + assert(expected.len > 0); + if (std.mem.eql(u8, expected, given)) return; + const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)}); + defer testing.allocator.free(expected_fmt); + const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)}); + defer testing.allocator.free(given_fmt); + const idx = std.mem.indexOfDiff(u8, expected_fmt, given_fmt).?; + var padding = try testing.allocator.alloc(u8, idx + 5); + defer testing.allocator.free(padding); + std.mem.set(u8, padding, ' '); + std.debug.print("\nASM: {s}\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ + assembly, + expected_fmt, + given_fmt, + padding, + }); + return error.TestFailed; +} + +const TestEncode = struct { + buffer: [32]u8 = undefined, + index: usize = 0, + + fn encode(enc: *TestEncode, mnemonic: Instruction.Mnemonic, args: struct { + op1: Instruction.Operand = .none, + op2: Instruction.Operand = .none, + op3: Instruction.Operand = .none, + op4: Instruction.Operand = .none, + }) !void { + var stream = std.io.fixedBufferStream(&enc.buffer); + var count_writer = std.io.countingWriter(stream.writer()); + const inst = try Instruction.new(mnemonic, .{ + .op1 = args.op1, + .op2 = args.op2, + .op3 = args.op3, + .op4 = args.op4, + }); + try inst.encode(count_writer.writer()); + enc.index = count_writer.bytes_written; + } + + fn code(enc: TestEncode) []const u8 { + return enc.buffer[0..enc.index]; + } +}; + +test "encode" { + var buf = std.ArrayList(u8).init(testing.allocator); + defer buf.deinit(); + + const inst = try Instruction.new(.mov, .{ + .op1 = .{ .reg = .rbx }, + .op2 = .{ .imm = Immediate.u(4) }, + }); + try inst.encode(buf.writer()); + try testing.expectEqualSlices(u8, &.{ 0x48, 0xc7, 0xc3, 0x4, 0x0, 0x0, 0x0 }, buf.items); +} + +test "lower I encoding" { + var enc = TestEncode{}; + + try enc.encode(.push, .{ .op1 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x6A\x10", enc.code(), "push 0x10"); + + try enc.encode(.push, .{ .op1 = .{ .imm = Immediate.u(0x1000) } }); + try expectEqualHexStrings("\x66\x68\x00\x10", enc.code(), "push 0x1000"); + + try enc.encode(.push, .{ .op1 = .{ .imm = Immediate.u(0x10000000) } }); + try expectEqualHexStrings("\x68\x00\x00\x00\x10", enc.code(), "push 0x10000000"); + + try enc.encode(.adc, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .imm = Immediate.u(0x10000000) } }); + try expectEqualHexStrings("\x48\x15\x00\x00\x00\x10", enc.code(), "adc rax, 0x10000000"); + + try enc.encode(.add, .{ .op1 = .{ .reg = .al }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x04\x10", enc.code(), "add al, 0x10"); + + try enc.encode(.add, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x48\x83\xC0\x10", enc.code(), "add rax, 0x10"); + + try enc.encode(.sbb, .{ .op1 = .{ .reg = .ax }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x66\x1D\x10\x00", enc.code(), "sbb ax, 0x10"); + + try enc.encode(.xor, .{ .op1 = .{ .reg = .al }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x34\x10", enc.code(), "xor al, 0x10"); +} + +test "lower MI encoding" { + var enc = TestEncode{}; + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r12 }, .op2 = .{ .imm = Immediate.u(0x1000) } }); + try expectEqualHexStrings("\x49\xC7\xC4\x00\x10\x00\x00", enc.code(), "mov r12, 0x1000"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.byte, .{ + .base = .r12, + .disp = 0, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x41\xC6\x04\x24\x10", enc.code(), "mov BYTE PTR [r12], 0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r12 }, .op2 = .{ .imm = Immediate.u(0x1000) } }); + try expectEqualHexStrings("\x49\xC7\xC4\x00\x10\x00\x00", enc.code(), "mov r12, 0x1000"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r12 }, .op2 = .{ .imm = Immediate.u(0x1000) } }); + try expectEqualHexStrings("\x49\xC7\xC4\x00\x10\x00\x00", enc.code(), "mov r12, 0x1000"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x48\xc7\xc0\x10\x00\x00\x00", enc.code(), "mov rax, 0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .r11, + .disp = 0, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x41\xc7\x03\x10\x00\x00\x00", enc.code(), "mov DWORD PTR [r11], 0x10"); + + try enc.encode(.mov, .{ + .op1 = .{ .mem = Memory.rip(.qword, 0x10) }, + .op2 = .{ .imm = Immediate.u(0x10) }, + }); + try expectEqualHexStrings( + "\x48\xC7\x05\x10\x00\x00\x00\x10\x00\x00\x00", + enc.code(), + "mov QWORD PTR [rip + 0x10], 0x10", + ); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .disp = -8, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x48\xc7\x45\xf8\x10\x00\x00\x00", enc.code(), "mov QWORD PTR [rbp - 8], 0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.word, .{ + .base = .rbp, + .disp = -2, + }) }, .op2 = .{ .imm = Immediate.s(-16) } }); + try expectEqualHexStrings("\x66\xC7\x45\xFE\xF0\xFF", enc.code(), "mov WORD PTR [rbp - 2], -16"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.byte, .{ + .base = .rbp, + .disp = -1, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\xC6\x45\xFF\x10", enc.code(), "mov BYTE PTR [rbp - 1], 0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .ds, + .disp = 0x10000000, + .scale_index = .{ + .scale = 2, + .index = .rcx, + }, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings( + "\x48\xC7\x04\x4D\x00\x00\x00\x10\x10\x00\x00\x00", + enc.code(), + "mov QWORD PTR [rcx*2 + 0x10000000], 0x10", + ); + + try enc.encode(.adc, .{ .op1 = .{ .mem = Memory.sib(.byte, .{ + .base = .rbp, + .disp = -0x10, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x80\x55\xF0\x10", enc.code(), "adc BYTE PTR [rbp - 0x10], 0x10"); + + try enc.encode(.adc, .{ .op1 = .{ .mem = Memory.rip(.qword, 0) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x48\x83\x15\x00\x00\x00\x00\x10", enc.code(), "adc QWORD PTR [rip], 0x10"); + + try enc.encode(.adc, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x48\x83\xD0\x10", enc.code(), "adc rax, 0x10"); + + try enc.encode(.add, .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .rdx, + .disp = -8, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x83\x42\xF8\x10", enc.code(), "add DWORD PTR [rdx - 8], 0x10"); + + try enc.encode(.add, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x48\x83\xC0\x10", enc.code(), "add rax, 0x10"); + + try enc.encode(.add, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .disp = -0x10, + }) }, .op2 = .{ .imm = Immediate.s(-0x10) } }); + try expectEqualHexStrings("\x48\x83\x45\xF0\xF0", enc.code(), "add QWORD PTR [rbp - 0x10], -0x10"); + + try enc.encode(.@"and", .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .ds, + .disp = 0x10000000, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings( + "\x83\x24\x25\x00\x00\x00\x10\x10", + enc.code(), + "and DWORD PTR ds:0x10000000, 0x10", + ); + + try enc.encode(.@"and", .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .es, + .disp = 0x10000000, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings( + "\x26\x83\x24\x25\x00\x00\x00\x10\x10", + enc.code(), + "and DWORD PTR es:0x10000000, 0x10", + ); + + try enc.encode(.@"and", .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .r12, + .disp = 0x10000000, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings( + "\x41\x83\xA4\x24\x00\x00\x00\x10\x10", + enc.code(), + "and DWORD PTR [r12 + 0x10000000], 0x10", + ); + + try enc.encode(.sub, .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .r11, + .disp = 0x10000000, + }) }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings( + "\x41\x83\xAB\x00\x00\x00\x10\x10", + enc.code(), + "sub DWORD PTR [r11 + 0x10000000], 0x10", + ); +} + +test "lower RM encoding" { + var enc = TestEncode{}; + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .r11, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x49\x8b\x03", enc.code(), "mov rax, QWORD PTR [r11]"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rbx }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .ds, + .disp = 0x10, + }) } }); + try expectEqualHexStrings("\x48\x8B\x1C\x25\x10\x00\x00\x00", enc.code(), "mov rbx, QWORD PTR ds:0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .disp = -4, + }) } }); + try expectEqualHexStrings("\x48\x8B\x45\xFC", enc.code(), "mov rax, QWORD PTR [rbp - 4]"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .scale_index = .{ + .scale = 1, + .index = .rcx, + }, + .disp = -8, + }) } }); + try expectEqualHexStrings("\x48\x8B\x44\x0D\xF8", enc.code(), "mov rax, QWORD PTR [rbp + rcx*1 - 8]"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .mem = Memory.sib(.dword, .{ + .base = .rbp, + .scale_index = .{ + .scale = 4, + .index = .rdx, + }, + .disp = -4, + }) } }); + try expectEqualHexStrings("\x8B\x44\x95\xFC", enc.code(), "mov eax, dword ptr [rbp + rdx*4 - 4]"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .scale_index = .{ + .scale = 8, + .index = .rcx, + }, + .disp = -8, + }) } }); + try expectEqualHexStrings("\x48\x8B\x44\xCD\xF8", enc.code(), "mov rax, QWORD PTR [rbp + rcx*8 - 8]"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r8b }, .op2 = .{ .mem = Memory.sib(.byte, .{ + .base = .rsi, + .scale_index = .{ + .scale = 1, + .index = .rcx, + }, + .disp = -24, + }) } }); + try expectEqualHexStrings("\x44\x8A\x44\x0E\xE8", enc.code(), "mov r8b, BYTE PTR [rsi + rcx*1 - 24]"); + + // TODO this mnemonic needs cleanup as some prefixes are obsolete. + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .reg = .cs } }); + try expectEqualHexStrings("\x48\x8C\xC8", enc.code(), "mov rax, cs"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .disp = -16, + }) }, .op2 = .{ .reg = .fs } }); + try expectEqualHexStrings("\x48\x8C\x65\xF0", enc.code(), "mov QWORD PTR [rbp - 16], fs"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r12w }, .op2 = .{ .reg = .cs } }); + try expectEqualHexStrings("\x66\x41\x8C\xCC", enc.code(), "mov r12w, cs"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.word, .{ + .base = .rbp, + .disp = -16, + }) }, .op2 = .{ .reg = .fs } }); + try expectEqualHexStrings("\x66\x8C\x65\xF0", enc.code(), "mov WORD PTR [rbp - 16], fs"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .reg = .bx } }); + try expectEqualHexStrings("\x0F\xBF\xC3", enc.code(), "movsx eax, bx"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .reg = .bl } }); + try expectEqualHexStrings("\x0F\xBE\xC3", enc.code(), "movsx eax, bl"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .ax }, .op2 = .{ .reg = .bl } }); + try expectEqualHexStrings("\x66\x0F\xBE\xC3", enc.code(), "movsx ax, bl"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .mem = Memory.sib(.word, .{ + .base = .rbp, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x0F\xBF\x45\x00", enc.code(), "movsx eax, BYTE PTR [rbp]"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .mem = Memory.sib(.byte, .{ + .base = null, + .scale_index = .{ + .index = .rax, + .scale = 2, + }, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x0F\xBE\x04\x45\x00\x00\x00\x00", enc.code(), "movsx eax, BYTE PTR [rax * 2]"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .ax }, .op2 = .{ .mem = Memory.rip(.byte, 0x10) } }); + try expectEqualHexStrings("\x66\x0F\xBE\x05\x10\x00\x00\x00", enc.code(), "movsx ax, BYTE PTR [rip + 0x10]"); + + try enc.encode(.movsx, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .reg = .bx } }); + try expectEqualHexStrings("\x48\x0F\xBF\xC3", enc.code(), "movsx rax, bx"); + + try enc.encode(.movsxd, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .reg = .ebx } }); + try expectEqualHexStrings("\x48\x63\xC3", enc.code(), "movsxd rax, ebx"); + + try enc.encode(.lea, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.rip(.qword, 0x10) } }); + try expectEqualHexStrings("\x48\x8D\x05\x10\x00\x00\x00", enc.code(), "lea rax, QWORD PTR [rip + 0x10]"); + + try enc.encode(.lea, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.rip(.dword, 0x10) } }); + try expectEqualHexStrings("\x48\x8D\x05\x10\x00\x00\x00", enc.code(), "lea rax, DWORD PTR [rip + 0x10]"); + + try enc.encode(.lea, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .mem = Memory.rip(.dword, 0x10) } }); + try expectEqualHexStrings("\x8D\x05\x10\x00\x00\x00", enc.code(), "lea eax, DWORD PTR [rip + 0x10]"); + + try enc.encode(.lea, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .mem = Memory.rip(.word, 0x10) } }); + try expectEqualHexStrings("\x8D\x05\x10\x00\x00\x00", enc.code(), "lea eax, WORD PTR [rip + 0x10]"); + + try enc.encode(.lea, .{ .op1 = .{ .reg = .ax }, .op2 = .{ .mem = Memory.rip(.byte, 0x10) } }); + try expectEqualHexStrings("\x66\x8D\x05\x10\x00\x00\x00", enc.code(), "lea ax, BYTE PTR [rip + 0x10]"); + + try enc.encode(.lea, .{ .op1 = .{ .reg = .rsi }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .scale_index = .{ + .scale = 1, + .index = .rcx, + }, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x48\x8D\x74\x0D\x00", enc.code(), "lea rsi, QWORD PTR [rbp + rcx*1 + 0]"); + + try enc.encode(.add, .{ .op1 = .{ .reg = .r11 }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .ds, + .disp = 0x10000000, + }) } }); + try expectEqualHexStrings("\x4C\x03\x1C\x25\x00\x00\x00\x10", enc.code(), "add r11, QWORD PTR ds:0x10000000"); + + try enc.encode(.add, .{ .op1 = .{ .reg = .r12b }, .op2 = .{ .mem = Memory.sib(.byte, .{ + .base = .ds, + .disp = 0x10000000, + }) } }); + try expectEqualHexStrings("\x44\x02\x24\x25\x00\x00\x00\x10", enc.code(), "add r11b, BYTE PTR ds:0x10000000"); + + try enc.encode(.add, .{ .op1 = .{ .reg = .r12b }, .op2 = .{ .mem = Memory.sib(.byte, .{ + .base = .fs, + .disp = 0x10000000, + }) } }); + try expectEqualHexStrings("\x64\x44\x02\x24\x25\x00\x00\x00\x10", enc.code(), "add r11b, BYTE PTR fs:0x10000000"); + + try enc.encode(.sub, .{ .op1 = .{ .reg = .r11 }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .r13, + .disp = 0x10000000, + }) } }); + try expectEqualHexStrings("\x4D\x2B\x9D\x00\x00\x00\x10", enc.code(), "sub r11, QWORD PTR [r13 + 0x10000000]"); + + try enc.encode(.sub, .{ .op1 = .{ .reg = .r11 }, .op2 = .{ .mem = Memory.sib(.qword, .{ + .base = .r12, + .disp = 0x10000000, + }) } }); + try expectEqualHexStrings("\x4D\x2B\x9C\x24\x00\x00\x00\x10", enc.code(), "sub r11, QWORD PTR [r12 + 0x10000000]"); + + try enc.encode(.imul, .{ .op1 = .{ .reg = .r11 }, .op2 = .{ .reg = .r12 } }); + try expectEqualHexStrings("\x4D\x0F\xAF\xDC", enc.code(), "mov r11, r12"); +} + +test "lower RMI encoding" { + var enc = TestEncode{}; + + try enc.encode(.imul, .{ + .op1 = .{ .reg = .r11 }, + .op2 = .{ .reg = .r12 }, + .op3 = .{ .imm = Immediate.s(-2) }, + }); + try expectEqualHexStrings("\x4D\x6B\xDC\xFE", enc.code(), "imul r11, r12, -2"); + + try enc.encode(.imul, .{ + .op1 = .{ .reg = .r11 }, + .op2 = .{ .mem = Memory.rip(.qword, -16) }, + .op3 = .{ .imm = Immediate.s(-1024) }, + }); + try expectEqualHexStrings( + "\x4C\x69\x1D\xF0\xFF\xFF\xFF\x00\xFC\xFF\xFF", + enc.code(), + "imul r11, QWORD PTR [rip - 16], -1024", + ); + + try enc.encode(.imul, .{ + .op1 = .{ .reg = .bx }, + .op2 = .{ .mem = Memory.sib(.word, .{ + .base = .rbp, + .disp = -16, + }) }, + .op3 = .{ .imm = Immediate.s(-1024) }, + }); + try expectEqualHexStrings( + "\x66\x69\x5D\xF0\x00\xFC", + enc.code(), + "imul bx, WORD PTR [rbp - 16], -1024", + ); + + try enc.encode(.imul, .{ + .op1 = .{ .reg = .bx }, + .op2 = .{ .mem = Memory.sib(.word, .{ + .base = .rbp, + .disp = -16, + }) }, + .op3 = .{ .imm = Immediate.u(1024) }, + }); + try expectEqualHexStrings( + "\x66\x69\x5D\xF0\x00\x04", + enc.code(), + "imul bx, WORD PTR [rbp - 16], 1024", + ); +} + +test "lower MR encoding" { + var enc = TestEncode{}; + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .reg = .rbx } }); + try expectEqualHexStrings("\x48\x89\xD8", enc.code(), "mov rax, rbx"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .disp = -4, + }) }, .op2 = .{ .reg = .r11 } }); + try expectEqualHexStrings("\x4c\x89\x5d\xfc", enc.code(), "mov QWORD PTR [rbp - 4], r11"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.rip(.qword, 0x10) }, .op2 = .{ .reg = .r12 } }); + try expectEqualHexStrings("\x4C\x89\x25\x10\x00\x00\x00", enc.code(), "mov QWORD PTR [rip + 0x10], r12"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .r11, + .scale_index = .{ + .scale = 2, + .index = .r12, + }, + .disp = 0x10, + }) }, .op2 = .{ .reg = .r13 } }); + try expectEqualHexStrings("\x4F\x89\x6C\x63\x10", enc.code(), "mov QWORD PTR [r11 + 2 * r12 + 0x10], r13"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.rip(.word, -0x10) }, .op2 = .{ .reg = .r12w } }); + try expectEqualHexStrings("\x66\x44\x89\x25\xF0\xFF\xFF\xFF", enc.code(), "mov WORD PTR [rip - 0x10], r12w"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.sib(.byte, .{ + .base = .r11, + .scale_index = .{ + .scale = 2, + .index = .r12, + }, + .disp = 0x10, + }) }, .op2 = .{ .reg = .r13b } }); + try expectEqualHexStrings("\x47\x88\x6C\x63\x10", enc.code(), "mov BYTE PTR [r11 + 2 * r12 + 0x10], r13b"); + + try enc.encode(.add, .{ .op1 = .{ .mem = Memory.sib(.byte, .{ + .base = .ds, + .disp = 0x10000000, + }) }, .op2 = .{ .reg = .r12b } }); + try expectEqualHexStrings("\x44\x00\x24\x25\x00\x00\x00\x10", enc.code(), "add BYTE PTR ds:0x10000000, r12b"); + + try enc.encode(.add, .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .ds, + .disp = 0x10000000, + }) }, .op2 = .{ .reg = .r12d } }); + try expectEqualHexStrings("\x44\x01\x24\x25\x00\x00\x00\x10", enc.code(), "add DWORD PTR [ds:0x10000000], r12d"); + + try enc.encode(.add, .{ .op1 = .{ .mem = Memory.sib(.dword, .{ + .base = .gs, + .disp = 0x10000000, + }) }, .op2 = .{ .reg = .r12d } }); + try expectEqualHexStrings("\x65\x44\x01\x24\x25\x00\x00\x00\x10", enc.code(), "add DWORD PTR [gs:0x10000000], r12d"); + + try enc.encode(.sub, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .r11, + .disp = 0x10000000, + }) }, .op2 = .{ .reg = .r12 } }); + try expectEqualHexStrings("\x4D\x29\xA3\x00\x00\x00\x10", enc.code(), "sub QWORD PTR [r11 + 0x10000000], r12"); +} + +test "lower M encoding" { + var enc = TestEncode{}; + + try enc.encode(.call, .{ .op1 = .{ .reg = .r12 } }); + try expectEqualHexStrings("\x41\xFF\xD4", enc.code(), "call r12"); + + try enc.encode(.call, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .r12, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x41\xFF\x14\x24", enc.code(), "call QWORD PTR [r12]"); + + try enc.encode(.call, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = null, + .scale_index = .{ + .index = .r11, + .scale = 2, + }, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x42\xFF\x14\x5D\x00\x00\x00\x00", enc.code(), "call QWORD PTR [r11 * 2]"); + + try enc.encode(.call, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = null, + .scale_index = .{ + .index = .r12, + .scale = 2, + }, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x42\xFF\x14\x65\x00\x00\x00\x00", enc.code(), "call QWORD PTR [r12 * 2]"); + + try enc.encode(.call, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .gs, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x65\xFF\x14\x25\x00\x00\x00\x00", enc.code(), "call gs:0x0"); + + try enc.encode(.call, .{ .op1 = .{ .imm = Immediate.s(0) } }); + try expectEqualHexStrings("\xE8\x00\x00\x00\x00", enc.code(), "call 0x0"); + + try enc.encode(.push, .{ .op1 = .{ .mem = Memory.sib(.qword, .{ + .base = .rbp, + .disp = 0, + }) } }); + try expectEqualHexStrings("\xFF\x75\x00", enc.code(), "push QWORD PTR [rbp]"); + + try enc.encode(.push, .{ .op1 = .{ .mem = Memory.sib(.word, .{ + .base = .rbp, + .disp = 0, + }) } }); + try expectEqualHexStrings("\x66\xFF\x75\x00", enc.code(), "push QWORD PTR [rbp]"); + + try enc.encode(.pop, .{ .op1 = .{ .mem = Memory.rip(.qword, 0) } }); + try expectEqualHexStrings("\x8F\x05\x00\x00\x00\x00", enc.code(), "pop QWORD PTR [rip]"); + + try enc.encode(.pop, .{ .op1 = .{ .mem = Memory.rip(.word, 0) } }); + try expectEqualHexStrings("\x66\x8F\x05\x00\x00\x00\x00", enc.code(), "pop WORD PTR [rbp]"); + + try enc.encode(.imul, .{ .op1 = .{ .reg = .rax } }); + try expectEqualHexStrings("\x48\xF7\xE8", enc.code(), "imul rax"); + + try enc.encode(.imul, .{ .op1 = .{ .reg = .r12 } }); + try expectEqualHexStrings("\x49\xF7\xEC", enc.code(), "imul r12"); +} + +test "lower O encoding" { + var enc = TestEncode{}; + + try enc.encode(.push, .{ .op1 = .{ .reg = .rax } }); + try expectEqualHexStrings("\x50", enc.code(), "push rax"); + + try enc.encode(.push, .{ .op1 = .{ .reg = .r12w } }); + try expectEqualHexStrings("\x66\x41\x54", enc.code(), "push r12w"); + + try enc.encode(.pop, .{ .op1 = .{ .reg = .r12 } }); + try expectEqualHexStrings("\x41\x5c", enc.code(), "pop r12"); +} + +test "lower OI encoding" { + var enc = TestEncode{}; + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .imm = Immediate.u(0x1000000000000000) } }); + try expectEqualHexStrings( + "\x48\xB8\x00\x00\x00\x00\x00\x00\x00\x10", + enc.code(), + "movabs rax, 0x1000000000000000", + ); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r11 }, .op2 = .{ .imm = Immediate.u(0x1000000000000000) } }); + try expectEqualHexStrings( + "\x49\xBB\x00\x00\x00\x00\x00\x00\x00\x10", + enc.code(), + "movabs r11, 0x1000000000000000", + ); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r11d }, .op2 = .{ .imm = Immediate.u(0x10000000) } }); + try expectEqualHexStrings("\x41\xBB\x00\x00\x00\x10", enc.code(), "mov r11d, 0x10000000"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r11w }, .op2 = .{ .imm = Immediate.u(0x1000) } }); + try expectEqualHexStrings("\x66\x41\xBB\x00\x10", enc.code(), "mov r11w, 0x1000"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .r11b }, .op2 = .{ .imm = Immediate.u(0x10) } }); + try expectEqualHexStrings("\x41\xB3\x10", enc.code(), "mov r11b, 0x10"); +} + +test "lower FD/TD encoding" { + var enc = TestEncode{}; + + try enc.encode(.mov, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .mem = Memory.moffs(.cs, 0x10) } }); + try expectEqualHexStrings("\x2E\x48\xA1\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs rax, cs:0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .eax }, .op2 = .{ .mem = Memory.moffs(.fs, 0x10) } }); + try expectEqualHexStrings("\x64\xA1\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs eax, fs:0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .ax }, .op2 = .{ .mem = Memory.moffs(.gs, 0x10) } }); + try expectEqualHexStrings("\x65\x66\xA1\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs ax, gs:0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .reg = .al }, .op2 = .{ .mem = Memory.moffs(.ds, 0x10) } }); + try expectEqualHexStrings("\xA0\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs al, ds:0x10"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.moffs(.cs, 0x10) }, .op2 = .{ .reg = .rax } }); + try expectEqualHexStrings("\x2E\x48\xA3\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs cs:0x10, rax"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.moffs(.fs, 0x10) }, .op2 = .{ .reg = .eax } }); + try expectEqualHexStrings("\x64\xA3\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs fs:0x10, eax"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.moffs(.gs, 0x10) }, .op2 = .{ .reg = .ax } }); + try expectEqualHexStrings("\x65\x66\xA3\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs gs:0x10, ax"); + + try enc.encode(.mov, .{ .op1 = .{ .mem = Memory.moffs(.ds, 0x10) }, .op2 = .{ .reg = .al } }); + try expectEqualHexStrings("\xA2\x10\x00\x00\x00\x00\x00\x00\x00", enc.code(), "movabs ds:0x10, al"); +} + +test "lower NP encoding" { + var enc = TestEncode{}; + + try enc.encode(.int3, .{}); + try expectEqualHexStrings("\xCC", enc.code(), "int3"); + + try enc.encode(.nop, .{}); + try expectEqualHexStrings("\x90", enc.code(), "nop"); + + try enc.encode(.ret, .{}); + try expectEqualHexStrings("\xC3", enc.code(), "ret"); + + try enc.encode(.syscall, .{}); + try expectEqualHexStrings("\x0f\x05", enc.code(), "syscall"); +} + +fn invalidInstruction(mnemonic: Instruction.Mnemonic, args: struct { + op1: Instruction.Operand = .none, + op2: Instruction.Operand = .none, + op3: Instruction.Operand = .none, + op4: Instruction.Operand = .none, +}) !void { + const err = Instruction.new(mnemonic, .{ + .op1 = args.op1, + .op2 = args.op2, + .op3 = args.op3, + .op4 = args.op4, + }); + try testing.expectError(error.InvalidInstruction, err); +} + +test "invalid instruction" { + try invalidInstruction(.call, .{ .op1 = .{ .reg = .eax } }); + try invalidInstruction(.call, .{ .op1 = .{ .reg = .ax } }); + try invalidInstruction(.call, .{ .op1 = .{ .reg = .al } }); + try invalidInstruction(.call, .{ .op1 = .{ .mem = Memory.rip(.dword, 0) } }); + try invalidInstruction(.call, .{ .op1 = .{ .mem = Memory.rip(.word, 0) } }); + try invalidInstruction(.call, .{ .op1 = .{ .mem = Memory.rip(.byte, 0) } }); + try invalidInstruction(.mov, .{ .op1 = .{ .mem = Memory.rip(.word, 0x10) }, .op2 = .{ .reg = .r12 } }); + try invalidInstruction(.lea, .{ .op1 = .{ .reg = .rax }, .op2 = .{ .reg = .rbx } }); + try invalidInstruction(.lea, .{ .op1 = .{ .reg = .al }, .op2 = .{ .mem = Memory.rip(.byte, 0) } }); + try invalidInstruction(.pop, .{ .op1 = .{ .reg = .r12b } }); + try invalidInstruction(.pop, .{ .op1 = .{ .reg = .r12d } }); + try invalidInstruction(.push, .{ .op1 = .{ .reg = .r12b } }); + try invalidInstruction(.push, .{ .op1 = .{ .reg = .r12d } }); + try invalidInstruction(.push, .{ .op1 = .{ .imm = Immediate.u(0x1000000000000000) } }); +} + +fn cannotEncode(mnemonic: Instruction.Mnemonic, args: struct { + op1: Instruction.Operand = .none, + op2: Instruction.Operand = .none, + op3: Instruction.Operand = .none, + op4: Instruction.Operand = .none, +}) !void { + try testing.expectError(error.CannotEncode, Instruction.new(mnemonic, .{ + .op1 = args.op1, + .op2 = args.op2, + .op3 = args.op3, + .op4 = args.op4, + })); +} + +test "cannot encode" { + try cannotEncode(.@"test", .{ + .op1 = .{ .mem = Memory.sib(.byte, .{ .base = .r12, .disp = 0 }) }, + .op2 = .{ .reg = .ah }, + }); + try cannotEncode(.@"test", .{ + .op1 = .{ .reg = .r11b }, + .op2 = .{ .reg = .bh }, + }); + try cannotEncode(.mov, .{ + .op1 = .{ .reg = .sil }, + .op2 = .{ .reg = .ah }, + }); +} + +const Assembler = struct { + it: Tokenizer, + + const Tokenizer = struct { + input: []const u8, + pos: usize = 0, + + const Error = error{InvalidToken}; + + const Token = struct { + id: Id, + start: usize, + end: usize, + + const Id = enum { + eof, + + space, + new_line, + + colon, + comma, + open_br, + close_br, + plus, + minus, + star, + + string, + numeral, + }; + }; + + const Iterator = struct {}; + + fn next(it: *Tokenizer) !Token { + var result = Token{ + .id = .eof, + .start = it.pos, + .end = it.pos, + }; + + var state: enum { + start, + space, + new_line, + string, + numeral, + numeral_hex, + } = .start; + + while (it.pos < it.input.len) : (it.pos += 1) { + const ch = it.input[it.pos]; + switch (state) { + .start => switch (ch) { + ',' => { + result.id = .comma; + it.pos += 1; + break; + }, + ':' => { + result.id = .colon; + it.pos += 1; + break; + }, + '[' => { + result.id = .open_br; + it.pos += 1; + break; + }, + ']' => { + result.id = .close_br; + it.pos += 1; + break; + }, + '+' => { + result.id = .plus; + it.pos += 1; + break; + }, + '-' => { + result.id = .minus; + it.pos += 1; + break; + }, + '*' => { + result.id = .star; + it.pos += 1; + break; + }, + ' ', '\t' => state = .space, + '\n', '\r' => state = .new_line, + 'a'...'z', 'A'...'Z' => state = .string, + '0'...'9' => state = .numeral, + else => return error.InvalidToken, + }, + + .space => switch (ch) { + ' ', '\t' => {}, + else => { + result.id = .space; + break; + }, + }, + + .new_line => switch (ch) { + '\n', '\r', ' ', '\t' => {}, + else => { + result.id = .new_line; + break; + }, + }, + + .string => switch (ch) { + 'a'...'z', 'A'...'Z', '0'...'9' => {}, + else => { + result.id = .string; + break; + }, + }, + + .numeral => switch (ch) { + 'x' => state = .numeral_hex, + '0'...'9' => {}, + else => { + result.id = .numeral; + break; + }, + }, + + .numeral_hex => switch (ch) { + 'a'...'f' => {}, + '0'...'9' => {}, + else => { + result.id = .numeral; + break; + }, + }, + } + } + + if (it.pos >= it.input.len) { + switch (state) { + .string => result.id = .string, + .numeral, .numeral_hex => result.id = .numeral, + else => {}, + } + } + + result.end = it.pos; + return result; + } + + fn seekTo(it: *Tokenizer, pos: usize) void { + it.pos = pos; + } + }; + + pub fn init(input: []const u8) Assembler { + return .{ + .it = Tokenizer{ .input = input }, + }; + } + + pub fn assemble(as: *Assembler, writer: anytype) !void { + while (try as.next()) |parsed_inst| { + const inst = try Instruction.new(parsed_inst.mnemonic, .{ + .op1 = parsed_inst.ops[0], + .op2 = parsed_inst.ops[1], + .op3 = parsed_inst.ops[2], + .op4 = parsed_inst.ops[3], + }); + try inst.encode(writer); + } + } + + const ParseResult = struct { + mnemonic: Instruction.Mnemonic, + ops: [4]Instruction.Operand, + }; + + const ParseError = error{ + UnexpectedToken, + InvalidMnemonic, + InvalidOperand, + InvalidRegister, + InvalidPtrSize, + InvalidMemoryOperand, + InvalidScaleIndex, + } || Tokenizer.Error || std.fmt.ParseIntError; + + fn next(as: *Assembler) ParseError!?ParseResult { + try as.skip(2, .{ .space, .new_line }); + const mnemonic_tok = as.expect(.string) catch |err| switch (err) { + error.UnexpectedToken => return if (try as.peek() == .eof) null else err, + else => return err, + }; + const mnemonic = mnemonicFromString(as.source(mnemonic_tok)) orelse + return error.InvalidMnemonic; + try as.skip(1, .{.space}); + + const rules = .{ + .{}, + .{.register}, + .{.memory}, + .{.immediate}, + .{ .register, .register }, + .{ .register, .memory }, + .{ .memory, .register }, + .{ .register, .immediate }, + .{ .memory, .immediate }, + .{ .register, .register, .immediate }, + .{ .register, .memory, .immediate }, + }; + + const pos = as.it.pos; + inline for (rules) |rule| { + var ops = [4]Instruction.Operand{ .none, .none, .none, .none }; + if (as.parseOperandRule(rule, &ops)) { + return .{ + .mnemonic = mnemonic, + .ops = ops, + }; + } else |_| { + as.it.seekTo(pos); + } + } + + return error.InvalidOperand; + } + + fn source(as: *Assembler, token: Tokenizer.Token) []const u8 { + return as.it.input[token.start..token.end]; + } + + fn peek(as: *Assembler) Tokenizer.Error!Tokenizer.Token.Id { + const pos = as.it.pos; + const next_tok = try as.it.next(); + const id = next_tok.id; + as.it.seekTo(pos); + return id; + } + + fn expect(as: *Assembler, id: Tokenizer.Token.Id) ParseError!Tokenizer.Token { + const next_tok_id = try as.peek(); + if (next_tok_id == id) return as.it.next(); + return error.UnexpectedToken; + } + + fn skip(as: *Assembler, comptime num: comptime_int, tok_ids: [num]Tokenizer.Token.Id) Tokenizer.Error!void { + outer: while (true) { + const pos = as.it.pos; + const next_tok = try as.it.next(); + inline for (tok_ids) |tok_id| { + if (next_tok.id == tok_id) continue :outer; + } + as.it.seekTo(pos); + break; + } + } + + fn mnemonicFromString(bytes: []const u8) ?Instruction.Mnemonic { + const ti = @typeInfo(Instruction.Mnemonic).Enum; + inline for (ti.fields) |field| { + if (std.mem.eql(u8, bytes, field.name)) { + return @field(Instruction.Mnemonic, field.name); + } + } + return null; + } + + fn parseOperandRule(as: *Assembler, rule: anytype, ops: *[4]Instruction.Operand) ParseError!void { + inline for (rule, 0..) |cond, i| { + comptime assert(i < 4); + if (i > 0) { + _ = try as.expect(.comma); + try as.skip(1, .{.space}); + } + if (@typeInfo(@TypeOf(cond)) != .EnumLiteral) { + @compileError("invalid condition in the rule: " ++ @typeName(@TypeOf(cond))); + } + switch (cond) { + .register => { + const reg_tok = try as.expect(.string); + const reg = registerFromString(as.source(reg_tok)) orelse + return error.InvalidOperand; + ops[i] = .{ .reg = reg }; + }, + .memory => { + const mem = try as.parseMemory(); + ops[i] = .{ .mem = mem }; + }, + .immediate => { + const is_neg = if (as.expect(.minus)) |_| true else |_| false; + const imm_tok = try as.expect(.numeral); + const imm: Immediate = if (is_neg) blk: { + const imm = try std.fmt.parseInt(i32, as.source(imm_tok), 0); + break :blk .{ .signed = imm * -1 }; + } else .{ .unsigned = try std.fmt.parseInt(u64, as.source(imm_tok), 0) }; + ops[i] = .{ .imm = imm }; + }, + else => @compileError("unhandled enum literal " ++ @tagName(cond)), + } + try as.skip(1, .{.space}); + } + + try as.skip(1, .{.space}); + const tok = try as.it.next(); + switch (tok.id) { + .new_line, .eof => {}, + else => return error.InvalidOperand, + } + } + + fn registerFromString(bytes: []const u8) ?Register { + const ti = @typeInfo(Register).Enum; + inline for (ti.fields) |field| { + if (std.mem.eql(u8, bytes, field.name)) { + return @field(Register, field.name); + } + } + return null; + } + + fn parseMemory(as: *Assembler) ParseError!Memory { + const ptr_size: ?Memory.PtrSize = blk: { + const pos = as.it.pos; + const ptr_size = as.parsePtrSize() catch |err| switch (err) { + error.UnexpectedToken => { + as.it.seekTo(pos); + break :blk null; + }, + else => return err, + }; + break :blk ptr_size; + }; + + try as.skip(1, .{.space}); + + // Supported rules and orderings. + const rules = .{ + .{ .open_br, .base, .close_br }, // [ base ] + .{ .open_br, .base, .plus, .disp, .close_br }, // [ base + disp ] + .{ .open_br, .base, .minus, .disp, .close_br }, // [ base - disp ] + .{ .open_br, .disp, .plus, .base, .close_br }, // [ disp + base ] + .{ .open_br, .base, .plus, .index, .close_br }, // [ base + index ] + .{ .open_br, .base, .plus, .index, .star, .scale, .close_br }, // [ base + index * scale ] + .{ .open_br, .index, .star, .scale, .plus, .base, .close_br }, // [ index * scale + base ] + .{ .open_br, .base, .plus, .index, .star, .scale, .plus, .disp, .close_br }, // [ base + index * scale + disp ] + .{ .open_br, .base, .plus, .index, .star, .scale, .minus, .disp, .close_br }, // [ base + index * scale - disp ] + .{ .open_br, .index, .star, .scale, .plus, .base, .plus, .disp, .close_br }, // [ index * scale + base + disp ] + .{ .open_br, .index, .star, .scale, .plus, .base, .minus, .disp, .close_br }, // [ index * scale + base - disp ] + .{ .open_br, .disp, .plus, .index, .star, .scale, .plus, .base, .close_br }, // [ disp + index * scale + base ] + .{ .open_br, .disp, .plus, .base, .plus, .index, .star, .scale, .close_br }, // [ disp + base + index * scale ] + .{ .open_br, .base, .plus, .disp, .plus, .index, .star, .scale, .close_br }, // [ base + disp + index * scale ] + .{ .open_br, .base, .minus, .disp, .plus, .index, .star, .scale, .close_br }, // [ base - disp + index * scale ] + .{ .open_br, .base, .plus, .disp, .plus, .scale, .star, .index, .close_br }, // [ base + disp + scale * index ] + .{ .open_br, .base, .minus, .disp, .plus, .scale, .star, .index, .close_br }, // [ base - disp + scale * index ] + .{ .open_br, .rip, .plus, .disp, .close_br }, // [ rip + disp ] + .{ .open_br, .rip, .minus, .disp, .close_br }, // [ rig - disp ] + .{ .base, .colon, .disp }, // seg:disp + }; + + const pos = as.it.pos; + inline for (rules) |rule| { + if (as.parseMemoryRule(rule)) |res| { + if (res.rip) { + if (res.base != null or res.scale_index != null or res.offset != null) + return error.InvalidMemoryOperand; + return Memory.rip(ptr_size orelse .qword, res.disp orelse 0); + } + if (res.base) |base| { + if (res.rip) + return error.InvalidMemoryOperand; + if (res.offset) |offset| { + if (res.scale_index != null or res.disp != null) + return error.InvalidMemoryOperand; + return Memory.moffs(base, offset); + } + return Memory.sib(ptr_size orelse .qword, .{ + .base = base, + .scale_index = res.scale_index, + .disp = res.disp orelse 0, + }); + } + return error.InvalidMemoryOperand; + } else |_| { + as.it.seekTo(pos); + } + } + + return error.InvalidOperand; + } + + const MemoryParseResult = struct { + rip: bool = false, + base: ?Register = null, + scale_index: ?Memory.ScaleIndex = null, + disp: ?i32 = null, + offset: ?u64 = null, + }; + + fn parseMemoryRule(as: *Assembler, rule: anytype) ParseError!MemoryParseResult { + var res: MemoryParseResult = .{}; + inline for (rule, 0..) |cond, i| { + if (@typeInfo(@TypeOf(cond)) != .EnumLiteral) { + @compileError("unsupported condition type in the rule: " ++ @typeName(@TypeOf(cond))); + } + switch (cond) { + .open_br, .close_br, .plus, .minus, .star, .colon => { + _ = try as.expect(cond); + }, + .base => { + const tok = try as.expect(.string); + res.base = registerFromString(as.source(tok)) orelse return error.InvalidMemoryOperand; + }, + .rip => { + const tok = try as.expect(.string); + if (!std.mem.eql(u8, as.source(tok), "rip")) return error.InvalidMemoryOperand; + res.rip = true; + }, + .index => { + const tok = try as.expect(.string); + const index = registerFromString(as.source(tok)) orelse + return error.InvalidMemoryOperand; + if (res.scale_index) |*si| { + si.index = index; + } else { + res.scale_index = .{ .scale = 1, .index = index }; + } + }, + .scale => { + const tok = try as.expect(.numeral); + const scale = try std.fmt.parseInt(u2, as.source(tok), 0); + if (res.scale_index) |*si| { + si.scale = scale; + } else { + res.scale_index = .{ .scale = scale, .index = undefined }; + } + }, + .disp => { + const tok = try as.expect(.numeral); + const is_neg = blk: { + if (i > 0) { + if (rule[i - 1] == .minus) break :blk true; + } + break :blk false; + }; + if (std.fmt.parseInt(i32, as.source(tok), 0)) |disp| { + res.disp = if (is_neg) -1 * disp else disp; + } else |err| switch (err) { + error.Overflow => { + if (is_neg) return err; + if (res.base) |base| { + if (base.class() != .segment) return err; + } + const offset = try std.fmt.parseInt(u64, as.source(tok), 0); + res.offset = offset; + }, + else => return err, + } + }, + else => @compileError("unhandled operand output type: " ++ @tagName(cond)), + } + try as.skip(1, .{.space}); + } + return res; + } + + fn parsePtrSize(as: *Assembler) ParseError!Memory.PtrSize { + const size = try as.expect(.string); + try as.skip(1, .{.space}); + const ptr = try as.expect(.string); + + const size_raw = as.source(size); + const ptr_raw = as.source(ptr); + const len = size_raw.len + ptr_raw.len + 1; + var buf: ["qword ptr".len]u8 = undefined; + if (len > buf.len) return error.InvalidPtrSize; + + for (size_raw, 0..) |c, i| { + buf[i] = std.ascii.toLower(c); + } + buf[size_raw.len] = ' '; + for (ptr_raw, 0..) |c, i| { + buf[size_raw.len + i + 1] = std.ascii.toLower(c); + } + + const slice = buf[0..len]; + if (std.mem.eql(u8, slice, "qword ptr")) return .qword; + if (std.mem.eql(u8, slice, "dword ptr")) return .dword; + if (std.mem.eql(u8, slice, "word ptr")) return .word; + if (std.mem.eql(u8, slice, "byte ptr")) return .byte; + if (std.mem.eql(u8, slice, "tbyte ptr")) return .tbyte; + return error.InvalidPtrSize; + } +}; + +test "assemble" { + const input = + \\int3 + \\mov rax, rbx + \\mov qword ptr [rbp], rax + \\mov qword ptr [rbp - 16], rax + \\mov qword ptr [16 + rbp], rax + \\mov rax, 0x10 + \\mov byte ptr [rbp - 0x10], 0x10 + \\mov word ptr [rbp + r12], r11w + \\mov word ptr [rbp + r12 * 2], r11w + \\mov word ptr [rbp + r12 * 2 - 16], r11w + \\mov dword ptr [rip - 16], r12d + \\mov rax, fs:0x0 + \\mov rax, gs:0x1000000000000000 + \\movzx r12, al + \\imul r12, qword ptr [rbp - 16], 6 + \\jmp 0x0 + \\jc 0x0 + \\jb 0x0 + \\sal rax, 1 + \\sal rax, 63 + \\shl rax, 63 + \\sar rax, 63 + \\shr rax, 63 + \\test byte ptr [rbp - 16], r12b + \\sal r12, cl + \\mul qword ptr [rip - 16] + \\div r12 + \\idiv byte ptr [rbp - 16] + \\cwde + \\cbw + \\cdqe + \\test byte ptr [rbp], ah + \\test byte ptr [r12], spl + \\cdq + \\cwd + \\cqo + \\test bl, 0x1 + \\mov rbx,0x8000000000000000 + \\movss xmm0, dword ptr [rbp] + \\movss xmm0, xmm1 + \\movss dword ptr [rbp - 16 + rax * 2], xmm7 + \\movss dword ptr [rbp - 16 + rax * 2], xmm8 + \\movss xmm15, xmm9 + \\movsd xmm8, qword ptr [rbp - 16] + \\movsd qword ptr [rbp - 8], xmm0 + \\movq xmm8, qword ptr [rbp - 16] + \\movq qword ptr [rbp - 16], xmm8 + \\ucomisd xmm0, qword ptr [rbp - 16] + \\fisttp qword ptr [rbp - 16] + \\fisttp word ptr [rip + 32] + \\fisttp dword ptr [rax] + \\fld tbyte ptr [rbp] + \\fld dword ptr [rbp] + \\xor bl, 0xff + \\ud2 + \\add rsp, -1 + \\add rsp, 0xff + \\mov sil, byte ptr [rax + rcx * 1] + \\ + ; + + // zig fmt: off + const expected = &[_]u8{ + 0xCC, + 0x48, 0x89, 0xD8, + 0x48, 0x89, 0x45, 0x00, + 0x48, 0x89, 0x45, 0xF0, + 0x48, 0x89, 0x45, 0x10, + 0x48, 0xC7, 0xC0, 0x10, 0x00, 0x00, 0x00, + 0xC6, 0x45, 0xF0, 0x10, + 0x66, 0x46, 0x89, 0x5C, 0x25, 0x00, + 0x66, 0x46, 0x89, 0x5C, 0x65, 0x00, + 0x66, 0x46, 0x89, 0x5C, 0x65, 0xF0, + 0x44, 0x89, 0x25, 0xF0, 0xFF, 0xFF, 0xFF, + 0x64, 0x48, 0x8B, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, + 0x65, 0x48, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x4C, 0x0F, 0xB6, 0xE0, + 0x4C, 0x6B, 0x65, 0xF0, 0x06, + 0xE9, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0x82, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0x82, 0x00, 0x00, 0x00, 0x00, + 0x48, 0xD1, 0xE0, + 0x48, 0xC1, 0xE0, 0x3F, + 0x48, 0xC1, 0xE0, 0x3F, + 0x48, 0xC1, 0xF8, 0x3F, + 0x48, 0xC1, 0xE8, 0x3F, + 0x44, 0x84, 0x65, 0xF0, + 0x49, 0xD3, 0xE4, + 0x48, 0xF7, 0x25, 0xF0, 0xFF, 0xFF, 0xFF, + 0x49, 0xF7, 0xF4, + 0xF6, 0x7D, 0xF0, + 0x98, + 0x66, 0x98, + 0x48, 0x98, + 0x84, 0x65, 0x00, + 0x41, 0x84, 0x24, 0x24, + 0x99, + 0x66, 0x99, + 0x48, 0x99, + 0xF6, 0xC3, 0x01, + 0x48, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0xF3, 0x0F, 0x10, 0x45, 0x00, + 0xF3, 0x0F, 0x10, 0xC1, + 0xF3, 0x0F, 0x11, 0x7C, 0x45, 0xF0, + 0xF3, 0x44, 0x0F, 0x11, 0x44, 0x45, 0xF0, + 0xF3, 0x45, 0x0F, 0x10, 0xF9, + 0xF2, 0x44, 0x0F, 0x10, 0x45, 0xF0, + 0xF2, 0x0F, 0x11, 0x45, 0xF8, + 0xF3, 0x44, 0x0F, 0x7E, 0x45, 0xF0, + 0x66, 0x44, 0x0F, 0xD6, 0x45, 0xF0, + 0x66, 0x0F, 0x2E, 0x45, 0xF0, + 0xDD, 0x4D, 0xF0, + 0xDF, 0x0D, 0x20, 0x00, 0x00, 0x00, + 0xDB, 0x08, + 0xDB, 0x6D, 0x00, + 0xD9, 0x45, 0x00, + 0x80, 0xF3, 0xFF, + 0x0F, 0x0B, + 0x48, 0x83, 0xC4, 0xFF, + 0x48, 0x81, 0xC4, 0xFF, 0x00, 0x00, 0x00, + 0x40, 0x8A, 0x34, 0x08, + }; + // zig fmt: on + + var as = Assembler.init(input); + var output = std.ArrayList(u8).init(testing.allocator); + defer output.deinit(); + try as.assemble(output.writer()); + try expectEqualHexStrings(expected, output.items, input); +} + +test "assemble - Jcc" { + const mnemonics = [_]struct { Instruction.Mnemonic, u8 }{ + .{ .ja, 0x87 }, + .{ .jae, 0x83 }, + .{ .jb, 0x82 }, + .{ .jbe, 0x86 }, + .{ .jc, 0x82 }, + .{ .je, 0x84 }, + .{ .jg, 0x8f }, + .{ .jge, 0x8d }, + .{ .jl, 0x8c }, + .{ .jle, 0x8e }, + .{ .jna, 0x86 }, + .{ .jnae, 0x82 }, + .{ .jnb, 0x83 }, + .{ .jnbe, 0x87 }, + .{ .jnc, 0x83 }, + .{ .jne, 0x85 }, + .{ .jng, 0x8e }, + .{ .jnge, 0x8c }, + .{ .jnl, 0x8d }, + .{ .jnle, 0x8f }, + .{ .jno, 0x81 }, + .{ .jnp, 0x8b }, + .{ .jns, 0x89 }, + .{ .jnz, 0x85 }, + .{ .jo, 0x80 }, + .{ .jp, 0x8a }, + .{ .jpe, 0x8a }, + .{ .jpo, 0x8b }, + .{ .js, 0x88 }, + .{ .jz, 0x84 }, + }; + + inline for (&mnemonics) |mnemonic| { + const input = @tagName(mnemonic[0]) ++ " 0x0"; + const expected = [_]u8{ 0x0f, mnemonic[1], 0x0, 0x0, 0x0, 0x0 }; + var as = Assembler.init(input); + var output = std.ArrayList(u8).init(testing.allocator); + defer output.deinit(); + try as.assemble(output.writer()); + try expectEqualHexStrings(&expected, output.items, input); + } +} + +test "assemble - SETcc" { + const mnemonics = [_]struct { Instruction.Mnemonic, u8 }{ + .{ .seta, 0x97 }, + .{ .setae, 0x93 }, + .{ .setb, 0x92 }, + .{ .setbe, 0x96 }, + .{ .setc, 0x92 }, + .{ .sete, 0x94 }, + .{ .setg, 0x9f }, + .{ .setge, 0x9d }, + .{ .setl, 0x9c }, + .{ .setle, 0x9e }, + .{ .setna, 0x96 }, + .{ .setnae, 0x92 }, + .{ .setnb, 0x93 }, + .{ .setnbe, 0x97 }, + .{ .setnc, 0x93 }, + .{ .setne, 0x95 }, + .{ .setng, 0x9e }, + .{ .setnge, 0x9c }, + .{ .setnl, 0x9d }, + .{ .setnle, 0x9f }, + .{ .setno, 0x91 }, + .{ .setnp, 0x9b }, + .{ .setns, 0x99 }, + .{ .setnz, 0x95 }, + .{ .seto, 0x90 }, + .{ .setp, 0x9a }, + .{ .setpe, 0x9a }, + .{ .setpo, 0x9b }, + .{ .sets, 0x98 }, + .{ .setz, 0x94 }, + }; + + inline for (&mnemonics) |mnemonic| { + const input = @tagName(mnemonic[0]) ++ " al"; + const expected = [_]u8{ 0x0f, mnemonic[1], 0xC0 }; + var as = Assembler.init(input); + var output = std.ArrayList(u8).init(testing.allocator); + defer output.deinit(); + try as.assemble(output.writer()); + try expectEqualHexStrings(&expected, output.items, input); + } +} + +test "assemble - CMOVcc" { + const mnemonics = [_]struct { Instruction.Mnemonic, u8 }{ + .{ .cmova, 0x47 }, + .{ .cmovae, 0x43 }, + .{ .cmovb, 0x42 }, + .{ .cmovbe, 0x46 }, + .{ .cmovc, 0x42 }, + .{ .cmove, 0x44 }, + .{ .cmovg, 0x4f }, + .{ .cmovge, 0x4d }, + .{ .cmovl, 0x4c }, + .{ .cmovle, 0x4e }, + .{ .cmovna, 0x46 }, + .{ .cmovnae, 0x42 }, + .{ .cmovnb, 0x43 }, + .{ .cmovnbe, 0x47 }, + .{ .cmovnc, 0x43 }, + .{ .cmovne, 0x45 }, + .{ .cmovng, 0x4e }, + .{ .cmovnge, 0x4c }, + .{ .cmovnl, 0x4d }, + .{ .cmovnle, 0x4f }, + .{ .cmovno, 0x41 }, + .{ .cmovnp, 0x4b }, + .{ .cmovns, 0x49 }, + .{ .cmovnz, 0x45 }, + .{ .cmovo, 0x40 }, + .{ .cmovp, 0x4a }, + .{ .cmovpe, 0x4a }, + .{ .cmovpo, 0x4b }, + .{ .cmovs, 0x48 }, + .{ .cmovz, 0x44 }, + }; + + inline for (&mnemonics) |mnemonic| { + const input = @tagName(mnemonic[0]) ++ " rax, rbx"; + const expected = [_]u8{ 0x48, 0x0f, mnemonic[1], 0xC3 }; + var as = Assembler.init(input); + var output = std.ArrayList(u8).init(testing.allocator); + defer output.deinit(); + try as.assemble(output.writer()); + try expectEqualHexStrings(&expected, output.items, input); + } +} From 955e394792af69fc8c795802c0165e5c86f5ea73 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 12 Mar 2023 08:47:23 +0100 Subject: [PATCH 29/31] x86_64: fix 32bit build issues in the encoder --- src/arch/x86_64/Encoding.zig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index c6a8d044c3..7b00679b92 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -105,6 +105,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { } if (count == 0) return null; + if (count == 1) return candidates[0]; const EncodingLength = struct { fn estimate(encoding: Encoding, params: struct { @@ -112,7 +113,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { op2: Instruction.Operand, op3: Instruction.Operand, op4: Instruction.Operand, - }) !usize { + }) usize { var inst = Instruction{ .op1 = params.op1, .op2 = params.op2, @@ -122,7 +123,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { }; var cwriter = std.io.countingWriter(std.io.null_writer); inst.encode(cwriter.writer()) catch unreachable; // Not allowed to fail here unless OOM. - return cwriter.bytes_written; + return @intCast(usize, cwriter.bytes_written); } }; @@ -138,7 +139,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { else => {}, } - const len = try EncodingLength.estimate(candidate, .{ + const len = EncodingLength.estimate(candidate, .{ .op1 = args.op1, .op2 = args.op2, .op3 = args.op3, From ac434fd8cc1105912d29f209e2f9f67e5af3a744 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 12 Mar 2023 22:06:22 +0100 Subject: [PATCH 30/31] x86_64: avoid inline for-loops when scanning the encodings table --- src/arch/x86_64/Encoding.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arch/x86_64/Encoding.zig b/src/arch/x86_64/Encoding.zig index 7b00679b92..a51f954aed 100644 --- a/src/arch/x86_64/Encoding.zig +++ b/src/arch/x86_64/Encoding.zig @@ -68,7 +68,7 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: struct { // TODO work out what is the maximum number of variants we can actually find in one swoop. var candidates: [10]Encoding = undefined; var count: usize = 0; - inline for (table) |entry| { + for (table) |entry| { const enc = Encoding{ .mnemonic = entry[0], .op_en = entry[1], @@ -162,7 +162,7 @@ pub fn findByOpcode(opc: []const u8, prefixes: struct { legacy: LegacyPrefixes, rex: Rex, }, modrm_ext: ?u3) ?Encoding { - inline for (table) |entry| { + for (table) |entry| { const enc = Encoding{ .mnemonic = entry[0], .op_en = entry[1], From e9fc0aba4c3b0855e580ff3afe7251920ae3dcf9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 12 Mar 2023 22:08:29 +0100 Subject: [PATCH 31/31] x86_64: add missing source files to CMakeLists.txt --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 46d9c701b0..5afea9354e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -559,9 +559,12 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/arch/wasm/Mir.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/CodeGen.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Emit.zig" + "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Encoding.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Mir.zig" - "${CMAKE_SOURCE_DIR}/src/arch/x86_64/bits.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/abi.zig" + "${CMAKE_SOURCE_DIR}/src/arch/x86_64/bits.zig" + "${CMAKE_SOURCE_DIR}/src/arch/x86_64/encoder.zig" + "${CMAKE_SOURCE_DIR}/src/arch/x86_64/encodings.zig" "${CMAKE_SOURCE_DIR}/src/clang.zig" "${CMAKE_SOURCE_DIR}/src/clang_options.zig" "${CMAKE_SOURCE_DIR}/src/clang_options_data.zig"