aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/arch/x86_64/CodeGen.zig2007
-rw-r--r--src/arch/x86_64/Emit.zig3186
-rw-r--r--src/arch/x86_64/Encoding.zig587
-rw-r--r--src/arch/x86_64/Mir.zig812
-rw-r--r--src/arch/x86_64/bits.zig1146
-rw-r--r--src/arch/x86_64/encoder.zig2275
-rw-r--r--src/arch/x86_64/encodings.zig621
7 files changed, 5312 insertions, 5322 deletions
diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig
index f8f6a773fa..3b7dc0db57 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;
@@ -303,7 +305,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 +349,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,
};
@@ -355,52 +376,263 @@ 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);
}
-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 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 asmJmpReloc(self: *Self, target: Mir.Inst.Index) !Mir.Inst.Index {
+ return self.addInst(.{
+ .tag = .jmp_reloc,
+ .ops = undefined,
+ .data = .{ .inst = target },
+ });
+}
+
+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 asmOpOnly(self: *Self, tag: Mir.Inst.Tag) !void {
+ _ = try self.addInst(.{
+ .tag = tag,
+ .ops = .none,
+ .data = undefined,
+ });
+}
+
+fn asmRegister(self: *Self, tag: Mir.Inst.Tag, reg: Register) !void {
+ _ = try self.addInst(.{
+ .tag = tag,
+ .ops = .r,
+ .data = .{ .r = reg },
+ });
+}
+
+fn asmImmediate(self: *Self, tag: Mir.Inst.Tag, imm: Immediate) !void {
+ const ops: Mir.Inst.Ops = if (imm == .signed) .imm_s else .imm_u;
+ _ = try self.addInst(.{
+ .tag = tag,
+ .ops = ops,
+ .data = .{ .imm = switch (imm) {
+ .signed => |x| @bitCast(u32, x),
+ .unsigned => |x| @intCast(u32, x),
+ } },
+ });
+}
+
+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 = 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 = .{
+ .r1 = reg,
+ .imm = @bitCast(u32, imm.signed),
+ } },
+ .ri_u => .{ .ri = .{
+ .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(.{
+ .tag = tag,
+ .ops = ops,
+ .data = data,
+ });
+}
+
+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 = .{
+ .r1 = reg1,
+ .r2 = reg2,
+ .imm = @bitCast(u32, imm.signed),
+ } },
+ .rri_u => .{ .rri = .{
+ .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,
+ .rip => .m_rip,
+ else => unreachable,
+ };
+ 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,
+ .data = data,
+ });
+}
+
+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 = .{
+ .xi = .{ .payload = payload, .imm = switch (imm) {
+ .signed => |x| @bitCast(u32, x),
+ .unsigned => |x| @intCast(u32, x),
+ } },
+ };
+ _ = 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 = .{
+ .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,
+ } },
+ };
+ _ = 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 = .{
+ .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,
+ } },
+ };
+ _ = 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.addInst(.{
- .tag = .mov,
- .ops = Mir.Inst.Ops.encode(.{
- .reg1 = .rbp,
- .reg2 = .rsp,
- }),
- .data = undefined,
- });
+ 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.
// 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,
+ .tag = .dead,
.ops = undefined,
.data = undefined,
});
@@ -427,7 +659,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,
+ .tag = .dead,
.ops = undefined,
.data = undefined,
});
@@ -458,7 +690,7 @@ fn gen(self: *Self) InnerError!void {
// Pop saved callee-preserved regs.
const backpatch_pop_callee_preserved_regs = try self.addInst(.{
- .tag = .nop,
+ .tag = .dead,
.ops = undefined,
.data = undefined,
});
@@ -471,22 +703,13 @@ 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,
+ .tag = .dead,
.ops = undefined,
.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.asmRegister(.pop, .rbp);
+ try self.asmOpOnly(.ret);
// Adjust the stack
if (self.max_end_stack > math.maxInt(i32)) {
@@ -500,27 +723,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 = .{
+ .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 = .{
+ .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 },
});
}
@@ -815,10 +1045,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 => {
@@ -1216,7 +1446,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;
@@ -1263,14 +1499,7 @@ 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 },
- });
+ try self.asmCmovccRegisterRegister(dst_mcv.register, lhs_reg, cc);
break :result dst_mcv;
};
@@ -1470,13 +1699,7 @@ 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 },
- });
+ try self.asmSetccRegister(overflow_reg.to8(), cc);
const scratch_reg = temp_regs[1];
try self.genSetReg(extended_ty, scratch_reg, .{ .register = reg });
@@ -1489,12 +1712,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,
@@ -1638,23 +1856,8 @@ fn genIntMulDivOpMir(
}
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,
- });
- },
+ .signed => try self.asmOpOnly(.cqo),
+ .unsigned => try self.asmRegisterRegister(.xor, .rdx, .rdx),
}
const factor = switch (rhs) {
@@ -1667,29 +1870,11 @@ fn genIntMulDivOpMir(
};
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 = .{ .imm = @bitCast(u32, -off) },
- });
- },
+ .register => |reg| try self.asmRegister(tag, reg),
+ .stack_offset => |off| try self.asmMemory(tag, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{
+ .base = .rbp,
+ .disp = -off,
+ })),
else => unreachable,
}
}
@@ -1717,38 +1902,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.addInst(.{
- .tag = .cond_mov,
- .ops = Mir.Inst.Ops.encode(.{
- .reg1 = divisor.to64(),
- .reg2 = .rdx,
- }),
- .data = .{ .cc = .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 };
}
@@ -2182,18 +2339,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| {
- // mov reg, [rbp - 8]
- _ = try self.addInst(.{
- .tag = .mov,
- .ops = Mir.Inst.Ops.encode(.{
- .reg1 = addr_reg.to64(),
- .reg2 = .rbp,
- .flags = 0b01,
- }),
- .data = .{ .imm = @bitCast(u32, -@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
@@ -2268,26 +2417,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 = .{ .imm = @bitCast(u32, -off) },
- });
+ try self.asmRegisterMemory(.lea, addr_reg.to64(), Memory.sib(.qword, .{
+ .base = .rbp,
+ .disp = -off,
+ }));
},
.stack_offset => |off| {
- // lea reg, [rbp]
- _ = try self.addInst(.{
- .tag = .lea,
- .ops = Mir.Inst.Ops.encode(.{
- .reg1 = addr_reg.to64(),
- .reg2 = .rbp,
- }),
- .data = .{ .imm = @bitCast(u32, -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);
@@ -2324,7 +2463,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) {
@@ -2344,16 +2483,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,
+ 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,
}),
- .data = .{ .imm = 0 },
- });
+ );
break :result .{ .register = registerAlias(dst_mcv.register, @intCast(u32, elem_abi_size)) };
}
};
@@ -2580,7 +2717,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,
@@ -2607,16 +2744,11 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo
.undef => unreachable,
.eflags => unreachable,
.register => |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 = .{ .imm = 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) {
@@ -2676,23 +2808,19 @@ 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,
};
_ = 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,
- },
- },
+ .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| {
@@ -2705,7 +2833,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,
@@ -2738,25 +2866,14 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type
.immediate => |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 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);
},
8 => {
// TODO: optimization: if the imm is only using the lower
@@ -2786,13 +2903,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,
@@ -2833,17 +2944,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 = .{ .imm = 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() };
@@ -2853,19 +2958,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 = @truncate(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
@@ -2876,14 +2970,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);
@@ -2895,16 +2985,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 = .{ .imm = 0 },
- });
return self.store(new_ptr, .{ .register = tmp_reg }, ptr_ty, value_ty);
}
@@ -3052,8 +3137,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;
@@ -3066,14 +3151,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;
@@ -3090,13 +3172,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,
@@ -3132,25 +3208,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;
@@ -3162,14 +3220,7 @@ fn genShiftBinOpMir(self: *Self, tag: Mir.Inst.Tag, ty: Type, reg: Register, shi
try self.genSetReg(Type.u8, .rcx, shift);
}
- _ = 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.
@@ -3542,59 +3593,44 @@ 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,
- else => return self.fail("TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}),
+ .add => .addss,
+ .cmp => .ucomiss,
+ 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,
- else => return self.fail("TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}),
+ .add => .addsd,
+ .cmp => .ucomisd,
+ 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()}),
+ 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.asmRegisterRegister(actual_tag, dst_reg.to128(), src_reg.to128());
}
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,
- });
- },
+ else => try self.asmRegisterRegister(
+ mir_tag,
+ registerAlias(dst_reg, abi_size),
+ registerAlias(src_reg, abi_size),
+ ),
},
.immediate => |imm| {
- _ = try self.addInst(.{
- .tag = mir_tag,
- .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(dst_reg, abi_size) }),
- .data = .{ .imm = @truncate(u32, imm) },
- });
+ // TODO
+ try self.asmRegisterImmediate(
+ mir_tag,
+ registerAlias(dst_reg, abi_size),
+ Immediate.u(@intCast(u32, imm)),
+ );
},
.memory,
.linker_load,
@@ -3611,15 +3647,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 = .{ .imm = @bitCast(u32, -off) },
- });
+ try self.asmRegisterMemory(
+ mir_tag,
+ registerAlias(dst_reg, abi_size),
+ Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = .rbp, .disp = -off }),
+ );
},
}
},
@@ -3637,45 +3669,17 @@ 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 = .{ .imm = @bitCast(u32, -off) },
- });
+ try self.asmMemoryRegister(mir_tag, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{
+ .base = .rbp,
+ .disp = -off,
+ }), registerAlias(src_reg, abi_size));
},
.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,
- };
- 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 = @bitCast(u32, -off),
- .operand = @truncate(u32, imm),
- });
- _ = 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,
@@ -3718,30 +3722,21 @@ 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| {
- // 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
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 = @truncate(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);
@@ -3749,15 +3744,11 @@ 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 = .{ .imm = @bitCast(u32, -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", .{});
@@ -3780,16 +3771,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);
- // 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 }, .{});
},
@@ -3915,20 +3901,12 @@ fn genVarDbgInfo(
}
fn airTrap(self: *Self) !void {
- _ = try self.addInst(.{
- .tag = .ud,
- .ops = Mir.Inst.Ops.encode(.{}),
- .data = undefined,
- });
+ try self.asmOpOnly(.ud2);
return self.finishAirBookkeeping();
}
fn airBreakpoint(self: *Self) !void {
- _ = try self.addInst(.{
- .tag = .interrupt,
- .ops = Mir.Inst.Ops.encode(.{}),
- .data = undefined,
- });
+ try self.asmOpOnly(.int3);
return self.finishAirBookkeeping();
}
@@ -4023,11 +4001,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
@@ -4040,12 +4014,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));
- _ = try self.addInst(.{
- .tag = .call,
- .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }),
- .data = .{ .imm = got_addr },
- });
+ const got_addr = atom.getOffsetTableAddress(elf_file);
+ 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().?;
@@ -4055,14 +4028,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().?;
@@ -4072,14 +4038,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);
@@ -4088,11 +4047,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.addInst(.{
- .tag = .call,
- .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }),
- .data = .{ .imm = @intCast(u32, 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;
@@ -4112,14 +4070,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);
@@ -4142,23 +4093,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: {
@@ -4215,11 +4155,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,
- .ops = Mir.Inst.Ops.encode(.{}),
- .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 });
}
@@ -4251,11 +4187,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,
- .ops = Mir.Inst.Ops.encode(.{}),
- .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 });
}
@@ -4430,33 +4362,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 = .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(),
- },
- },
- });
+ // 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.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.asmJccReloc(undefined, .e);
},
.immediate,
.stack_offset,
@@ -4470,6 +4382,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 {
@@ -4795,11 +4708,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,
- .ops = Mir.Inst.Ops.encode(.{}),
- .data = .{ .inst = jmp_target },
- });
+ _ = try self.asmJmpReloc(jmp_target);
return self.finishAirBookkeeping();
}
@@ -4844,23 +4753,16 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u
.none => unreachable,
.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) },
- });
- },
- .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,
- });
- },
+ .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);
@@ -4874,22 +4776,9 @@ 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.asmJccReloc(undefined, .ne);
},
.stack_offset => {
try self.spillEflagsIfOccupied();
@@ -4907,6 +4796,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 {
@@ -5101,10 +4991,10 @@ 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 => {
+ .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,
@@ -5146,11 +5036,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,
- .ops = Mir.Inst.Ops.encode(.{}),
- .data = .{ .inst = undefined },
- });
+ const jmp_reloc = try self.asmJmpReloc(undefined);
block_data.relocs.appendAssumeCapacity(jmp_reloc);
}
@@ -5223,31 +5109,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.addInst(.{
- .tag = .syscall,
- .ops = undefined,
- .data = undefined,
- });
+ try self.asmOpOnly(.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.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.addInst(.{
- .tag = .push,
- .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }),
- .data = undefined,
- });
+ try self.asmRegister(.push, reg);
} else return self.fail("TODO more push operands", .{});
} else if (mem.indexOf(u8, ins, "pop")) |_| {
const arg = ins[3..];
@@ -5255,11 +5129,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.addInst(.{
- .tag = .pop,
- .ops = Mir.Inst.Ops.encode(.{ .reg1 = reg }),
- .data = undefined,
- });
+ 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", .{});
@@ -5332,7 +5202,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,
@@ -5356,26 +5226,17 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE
.immediate => |imm| {
switch (abi_size) {
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
- const payload = try self.addExtra(Mir.ImmPair{
- .dest_off = @bitCast(u32, -stack_offset),
- .operand = @truncate(u32, imm),
- });
- _ = 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,
- },
- }),
- .data = .{ .payload = payload },
- });
+ 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);
},
8 => {
const reg = try self.copyToTmpRegister(ty, mcv);
@@ -5400,44 +5261,32 @@ 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,
- else => return self.fail("TODO genSetStackArg for register for type {}", .{ty.fmtDebug()}),
+ .f32 => .movss,
+ .f64 => .movsd,
+ 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 = .{ .imm = @bitCast(u32, -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 = .{ .imm = @bitCast(u32, -stack_offset) },
- });
+ try self.asmMemoryRegister(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{
+ .base = .rsp,
+ .disp = -stack_offset,
+ }), registerAlias(reg, abi_size));
},
}
},
@@ -5460,7 +5309,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.
@@ -5468,10 +5317,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 },
@@ -5491,13 +5349,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,
@@ -5512,72 +5364,36 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl
},
.immediate => |x_big| {
const base_reg = opts.dest_stack_base orelse .rbp;
+ // TODO
switch (abi_size) {
0 => {
assert(ty.isError());
- const payload = try self.addExtra(Mir.ImmPair{
- .dest_off = @bitCast(u32, -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(u8, x_big)));
},
1, 2, 4 => {
- const payload = try self.addExtra(Mir.ImmPair{
- .dest_off = @bitCast(u32, -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 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);
},
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 = @bitCast(u32, -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 = @bitCast(u32, -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", .{});
@@ -5595,30 +5411,22 @@ 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,
- else => return self.fail("TODO genSetStack for register for type {}", .{ty.fmtDebug()}),
+ .f32 => .movss,
+ .f64 => .movsd,
+ 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 = .{ .imm = @bitCast(u32, -stack_offset) },
- });
- return;
+ 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", .{});
@@ -5666,7 +5474,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);
@@ -5683,16 +5491,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 = .{ .imm = @bitCast(u32, -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, .{
@@ -5704,15 +5506,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 = .{ .imm = @bitCast(u32, -offset) },
- });
+ try self.asmMemoryRegister(.mov, Memory.sib(Memory.PtrSize.fromSize(abi_size), .{
+ .base = dst_reg,
+ .disp = -offset,
+ }), registerAlias(src_reg, abi_size));
}
}
@@ -5752,24 +5549,17 @@ fn genInlineMemcpy(
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 = .{ .imm = @bitCast(u32, -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.addInst(.{
- .tag = .mov,
- .ops = Mir.Inst.Ops.encode(.{
- .reg1 = registerAlias(dst_addr_reg, @divExact(reg.size(), 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});
@@ -5781,24 +5571,17 @@ 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 = .{ .imm = @bitCast(u32, -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.addInst(.{
- .tag = .mov,
- .ops = Mir.Inst.Ops.encode(.{
- .reg1 = registerAlias(src_addr_reg, @divExact(reg.size(), 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});
@@ -5806,74 +5589,35 @@ 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 },
- });
-
- // loop:
- // cmp count, 0
+ try self.asmRegisterImmediate(.mov, index_reg, Immediate.u(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,
+ .ops = .ri_u,
+ .data = .{ .ri = .{
+ .r1 = count_reg,
+ .imm = 0,
} },
});
-
- // 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)) },
- });
-
- // add index_reg, 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 },
- });
-
- // jmp loop
- _ = try self.addInst(.{
- .tag = .jmp,
- .ops = Mir.Inst.Ops.encode(.{}),
- .data = .{ .inst = loop_start },
- });
-
- // end:
+ const loop_reloc = try self.asmJccReloc(undefined, .e);
+ 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));
+ _ = try self.asmJmpReloc(loop_start);
try self.performReloc(loop_reloc);
}
@@ -5905,24 +5649,17 @@ 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 = .{ .imm = @bitCast(u32, -off) },
- });
+ try self.asmRegisterMemory(.lea, addr_reg.to64(), Memory.sib(.qword, .{
+ .base = opts.dest_stack_base orelse .rbp,
+ .disp = -off,
+ }));
},
.register => |reg| {
- _ = try self.addInst(.{
- .tag = .mov,
- .ops = Mir.Inst.Ops.encode(.{
- .reg1 = registerAlias(addr_reg, @divExact(reg.size(), 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});
@@ -5932,54 +5669,35 @@ 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 }),
- .data = .{ .imm = @bitCast(u32, @as(i32, -1)) },
- });
-
- // je end
- const loop_reloc = try self.addInst(.{
- .tag = .cond_jmp,
- .ops = Mir.Inst.Ops.encode(.{}),
- .data = .{ .inst_cc = .{
- .inst = undefined,
- .cc = .e,
+ .ops = .ri_s,
+ .data = .{ .ri = .{
+ .r1 = index_reg,
+ .imm = @bitCast(u32, @as(i32, -1)),
} },
});
+ const loop_reloc = try self.asmJccReloc(undefined, .e);
switch (value) {
.immediate => |x| {
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, @truncate(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}),
}
- // sub index_reg, 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 },
- });
-
- // end:
+ try self.asmRegisterImmediate(.sub, index_reg, Immediate.u(1));
+ _ = try self.asmJmpReloc(loop_start);
try self.performReloc(loop_reloc);
}
@@ -5992,21 +5710,18 @@ 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 = .{ .imm = @bitCast(u32, -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 => {
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 }),
@@ -6015,50 +5730,29 @@ 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 },
- });
+ return self.asmSetccRegister(reg.to8(), 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,
- });
- return;
+ // 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.
- _ = try self.addInst(.{
- .tag = .mov,
- .ops = Mir.Inst.Ops.encode(.{ .reg1 = registerAlias(reg, abi_size) }),
- .data = .{ .imm = @truncate(u32, x) },
- });
- return;
+ 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)),
+ );
+ }
}
- // 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.
@@ -6069,69 +5763,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 => 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 => .movss,
+ .f64 => .movsd,
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;
+ 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()) {
@@ -6141,45 +5804,30 @@ 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 => .movss,
+ .f64 => .movsd,
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 = .{ .imm = 0 },
- });
- return;
+ 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 = .{ .imm = 0 },
- });
+ try self.asmRegisterMemory(
+ .mov,
+ registerAlias(reg, abi_size),
+ Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = reg.to64(), .disp = 0 }),
+ );
},
}
},
@@ -6190,73 +5838,56 @@ 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 => .movss,
+ .f64 => .movsd,
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 = .{ .imm = 0 },
- });
- return;
+ 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,
+ try self.asmRegisterMemory(
+ .mov,
+ registerAlias(reg, abi_size),
+ Memory.sib(Memory.PtrSize.fromSize(abi_size), .{
+ .base = .ds,
+ .disp = @intCast(i32, x),
}),
- .data = .{ .imm = @truncate(u32, 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));
+ if (reg.to64() == .rax) {
+ // 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 = .movabs,
- .ops = Mir.Inst.Ops.encode(.{
- .reg1 = .rax,
- .flags = 0b01, // imm64 will become moffs64
- }),
- .data = .{ .payload = payload },
+ .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 });
-
- // 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 = .{ .imm = 0 },
- });
+ try self.asmRegisterMemory(
+ .mov,
+ registerAlias(reg, abi_size),
+ Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = reg.to64(), .disp = 0 }),
+ );
}
}
},
@@ -6270,85 +5901,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,
- };
- _ = try self.addInst(.{
- .tag = .mov_sign_extend,
- .ops = Mir.Inst.Ops.encode(.{
- .reg1 = reg.to64(),
- .reg2 = .rbp,
- .flags = flags,
+ return self.asmRegisterMemory(
+ .movsx,
+ reg.to64(),
+ Memory.sib(Memory.PtrSize.fromSize(abi_size), .{
+ .base = .rbp,
+ .disp = -off,
}),
- .data = .{ .imm = @bitCast(u32, -off) },
- });
- return;
+ );
}
},
.unsigned => {
if (abi_size <= 2) {
- const flags: u2 = switch (abi_size) {
- 1 => 0b01,
- 2 => 0b10,
- else => unreachable,
- };
- _ = try self.addInst(.{
- .tag = .mov_zero_extend,
- .ops = Mir.Inst.Ops.encode(.{
- .reg1 = reg.to64(),
- .reg2 = .rbp,
- .flags = flags,
+ return self.asmRegisterMemory(
+ .movzx,
+ reg.to64(),
+ Memory.sib(Memory.PtrSize.fromSize(abi_size), .{
+ .base = .rbp,
+ .disp = -off,
}),
- .data = .{ .imm = @bitCast(u32, -off) },
- });
- return;
+ );
}
},
},
.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,
- else => return self.fail("TODO genSetReg from stack offset for {}", .{ty.fmtDebug()}),
+ .f32 => .movss,
+ .f64 => .movsd,
+ 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 = .{ .imm = @bitCast(u32, -off) },
- });
- return;
+ 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 = .{ .imm = @bitCast(u32, -off) },
- });
+ try self.asmRegisterMemory(
+ .mov,
+ registerAlias(reg, abi_size),
+ Memory.sib(Memory.PtrSize.fromSize(abi_size), .{ .base = .rbp, .disp = -off }),
+ );
},
}
}
@@ -6412,6 +6017,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);
+ 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) {
@@ -6419,41 +6034,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;
},
};
- _ = 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 = .{ .imm = @bitCast(u32, -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 = .{ .imm = @bitCast(u32, -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 });
}
@@ -6544,15 +6142,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 = .{ .imm = 0 },
- });
+ try self.asmRegisterMemory(.mov, reg, Memory.sib(.qword, .{
+ .base = reg,
+ .disp = 0,
+ }));
break :blk MCValue{ .register = reg };
},
else => break :blk src_ptr,
@@ -6903,7 +6496,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 {
@@ -6978,28 +6571,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);
@@ -7009,7 +6608,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 e521de4bd4..32699d35cb 100644
--- a/src/arch/x86_64/Emit.zig
+++ b/src/arch/x86_64/Emit.zig
@@ -7,6 +7,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 +20,14 @@ 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;
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 {
@@ -65,133 +70,66 @@ 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_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_near, inst),
- .call => try emit.mirJmpCall(.call_near, 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 instructions
- .mov_f64_sse => try emit.mirMovFloatSse(.movsd, inst),
- .mov_f32_sse => try emit.mirMovFloatSse(.movss, inst),
+ .adc,
+ .add,
+ .@"and",
+ .call,
+ .cbw,
+ .cwde,
+ .cdqe,
+ .cwd,
+ .cdq,
+ .cqo,
+ .cmp,
+ .div,
+ .fisttp,
+ .fld,
+ .idiv,
+ .imul,
+ .int3,
+ .jmp,
+ .lea,
+ .mov,
+ .movzx,
+ .mul,
+ .nop,
+ .@"or",
+ .pop,
+ .push,
+ .ret,
+ .sal,
+ .sar,
+ .sbb,
+ .shl,
+ .shr,
+ .sub,
+ .syscall,
+ .@"test",
+ .ud2,
+ .xor,
- .add_f64_sse => try emit.mirAddFloatSse(.addsd, inst),
- .add_f32_sse => try emit.mirAddFloatSse(.addss, inst),
+ .addss,
+ .cmpss,
+ .movss,
+ .ucomiss,
+ .addsd,
+ .cmpsd,
+ .movsd,
+ .ucomisd,
+ => try emit.mirEncodeGeneric(tag, inst),
- .cmp_f64_sse => try emit.mirCmpFloatSse(.ucomisd, inst),
- .cmp_f32_sse => try emit.mirCmpFloatSse(.ucomiss, inst),
+ .jmp_reloc => try emit.mirJmpReloc(inst),
- // AVX instructions
- .mov_f64_avx => try emit.mirMovFloatAvx(.vmovsd, inst),
- .mov_f32_avx => try emit.mirMovFloatAvx(.vmovss, inst),
+ .call_extern => try emit.mirCallExtern(inst),
- .add_f64_avx => try emit.mirAddFloatAvx(.vaddsd, inst),
- .add_f32_avx => try emit.mirAddFloatAvx(.vaddss, inst),
+ .lea_linker => try emit.mirLeaLinker(inst),
- .cmp_f64_avx => try emit.mirCmpFloatAvx(.vucomisd, inst),
- .cmp_f32_avx => try emit.mirCmpFloatAvx(.vucomiss, inst),
+ .mov_moffs => try emit.mirMovMoffs(inst),
- // Pseudo-instructions
- .call_extern => try emit.mirCallExtern(inst),
+ .movsx => try emit.mirMovsx(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),
@@ -200,9 +138,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});
- },
+ .dead => {},
}
}
@@ -235,946 +171,398 @@ fn fixupRelocs(emit: *Emit) InnerError!void {
}
}
-fn mirUndefinedInstruction(emit: *Emit) InnerError!void {
- return lowerToZoEnc(.ud2, emit.code);
+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 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 lowerToZoEnc(.int3, emit.code),
- else => return emit.fail("TODO handle variant 0b{b} of interrupt instruction", .{ops.flags}),
- }
-}
+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 mirSyscall(emit: *Emit) InnerError!void {
- return lowerToZoEnc(.syscall, emit.code);
-}
+ const ops = emit.mir.instructions.items(.ops)[inst];
+ const data = emit.mir.instructions.items(.data)[inst];
-fn mirPushPop(emit: *Emit, tag: Tag, 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);
- },
- 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);
- },
- 0b10 => {
- // PUSH imm32
- assert(tag == .push);
- const imm = emit.mir.instructions.items(.data)[inst].imm;
- return lowerToIEnc(.push, imm, emit.code);
- },
- 0b11 => unreachable,
- }
-}
+ var op1: Instruction.Operand = .none;
+ var op2: Instruction.Operand = .none;
+ var op3: Instruction.Operand = .none;
+ var op4: Instruction.Operand = .none;
-fn mirPushPopRegisterList(emit: *Emit, tag: Tag, 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;
- 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)) {
- 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),
+ switch (ops) {
+ .none => {},
+ .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, .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;
+ 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,
+ };
+ 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;
+ op1 = .{ .mem = Mir.MemorySib.decode(msib) };
+ },
+ .m_rip => {
+ const mrip = emit.mir.extraData(Mir.MemoryRip, data.payload).data;
+ op1 = .{ .mem = Mir.MemoryRip.decode(mrip) };
+ },
+ .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_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 op_r = .{ .reg = data.rx.r1 };
+ const op_m = .{ .mem = Mir.MemorySib.decode(msib) };
+ switch (ops) {
+ .rm_sib => {
+ op1 = op_r;
+ op2 = op_m;
+ },
+ .mr_sib => {
+ op1 = op_m;
+ op2 = op_r;
+ },
else => unreachable,
}
- disp += 8;
- }
- }
-}
-
-fn mirJmpCall(emit: *Emit, tag: Tag, 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.relocs.append(emit.bin_file.allocator, .{
- .source = source,
- .target = target,
- .offset = emit.code.items.len - 4,
- .length = 5,
- });
},
- 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);
+ .rm_rip, .mr_rip => {
+ const mrip = emit.mir.extraData(Mir.MemoryRip, data.rx.payload).data;
+ const op_r = .{ .reg = data.rx.r1 };
+ const op_m = .{ .mem = Mir.MemoryRip.decode(mrip) };
+ switch (ops) {
+ .rm_sib => {
+ op1 = op_r;
+ op2 = op_m;
+ },
+ .mr_sib => {
+ op1 = op_m;
+ op2 = op_r;
+ },
+ else => unreachable,
}
- // JMP/CALL reg
- return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code);
- },
- 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);
},
- 0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}),
+ else => return emit.fail("TODO handle generic encoding: {s}, {s}", .{
+ @tagName(mnemonic),
+ @tagName(ops),
+ }),
}
-}
-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 inst_cc = emit.mir.instructions.items(.data)[inst].inst_cc;
- const tag: Tag = 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 lowerToDEnc(tag, 0, emit.code);
- try emit.relocs.append(emit.bin_file.allocator, .{
- .source = source,
- .target = inst_cc.inst,
- .offset = emit.code.items.len - 4,
- .length = 6,
+ return emit.encode(mnemonic, .{
+ .op1 = op1,
+ .op2 = op2,
+ .op3 = op3,
+ .op4 = op4,
});
}
-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 ops = emit.mir.instructions.items(.ops)[inst].decode();
- const cc = emit.mir.instructions.items(.data)[inst].cc;
- const tag: Tag = 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 lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1.to8()), emit.code);
-}
-
-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 ops = emit.mir.instructions.items(.ops)[inst].decode();
- const cc = emit.mir.instructions.items(.data)[inst].cc;
- const tag: Tag = 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,
- };
-
- if (ops.flags == 0b00) {
- return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
- }
- const imm = emit.mir.instructions.items(.data)[inst].imm;
- const ptr_size: Memory.PtrSize = switch (ops.flags) {
- 0b00 => unreachable,
- 0b01 => .word_ptr,
- 0b10 => .dword_ptr,
- 0b11 => .qword_ptr,
- };
- return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(ptr_size, .{
- .disp = imm,
- .base = ops.reg2,
- }), emit.code);
-}
-
-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) {
- // 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);
- }
- // TEST r/m64, r64
- return lowerToMrEnc(.@"test", RegisterOrMemory.reg(ops.reg1), ops.reg2, emit.code);
- },
- else => return emit.fail("TODO more TEST alternatives", .{}),
- }
-}
-
-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 => {
- // 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);
- },
- 0b10 => {
- // RET imm16
- // I
- const imm = emit.mir.instructions.items(.data)[inst].imm;
- return lowerToIEnc(.ret_near, imm, emit.code);
+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) },
+ });
},
- 0b11 => {
- return lowerToZoEnc(.ret_near, emit.code);
+ .moffs_rax => {
+ try emit.encode(.mov, .{
+ .op1 = .{ .mem = Memory.moffs(seg, offset) },
+ .op2 = .{ .reg = .rax },
+ });
},
+ else => unreachable,
}
}
-fn mirArith(emit: *Emit, tag: Tag, 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);
- }
- // mov reg1, reg2
- // RM
- return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
- },
- 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);
+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 };
},
- 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);
+ .rm_sib => {
+ const msib = emit.mir.extraData(Mir.MemorySib, data.rx.payload).data;
+ op1 = .{ .reg = data.rx.r1 };
+ op2 = .{ .mem = Mir.MemorySib.decode(msib) };
},
- 0b11 => {
- return emit.fail("TODO unused variant: mov reg1, reg2, 0b11", .{});
+ .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
}
-}
-
-fn mirArithMemImm(emit: *Emit, tag: Tag, 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,
- };
- return lowerToMiEnc(tag, RegisterOrMemory.mem(ptr_size, .{
- .disp = imm_pair.dest_off,
- .base = ops.reg1,
- }), imm_pair.operand, emit.code);
-}
-
-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 {
- 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{
- .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);
-}
-
-fn mirArithScaleDst(emit: *Emit, tag: Tag, 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{
- .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);
-}
-fn mirArithScaleImm(emit: *Emit, tag: Tag, 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{
- .scale = scale,
- .index = index_reg_disp_imm.index,
+ const mnemonic: Instruction.Mnemonic = switch (op1.bitSize()) {
+ 32, 64 => if (op2.bitSize() == 32) .movsxd else .movsx,
+ else => .movsx,
};
- // 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);
-}
-fn mirArithMemIndexImm(emit: *Emit, tag: Tag, 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,
- };
- const scale_index = 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);
-}
-
-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 ops = emit.mir.instructions.items(.ops)[inst].decode();
- const imm = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].imm 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);
- },
- 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);
- },
- }
+ return emit.encode(mnemonic, .{
+ .op1 = op1,
+ .op2 = op2,
+ });
}
-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 ops = emit.mir.instructions.items(.ops)[inst].decode();
- const imm = if (ops.flags != 0b00) emit.mir.instructions.items(.data)[inst].imm else undefined;
- switch (ops.flags) {
- 0b00 => {
- return lowerToRmEnc(.movzx, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
- },
- 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);
- },
- 0b11 => {
- return emit.fail("TODO unused variant: movzx 0b11", .{});
- },
- }
+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 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.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 reg, imm64
- // OI
- return lowerToOiEnc(.mov, ops.reg1, imm, emit.code);
- },
- 0b01 => {
- if (ops.reg1 == .none) {
- const imm: u64 = if (ops.reg2.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 moffs64, rax
- // TD
- return lowerToTdEnc(.mov, imm, ops.reg2, emit.code);
- }
- 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);
+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 = mnemonicFromConditionCode("cmov", data.cc);
+ return emit.encode(mnemonic, .{
+ .op1 = .{ .reg = data.r1 },
+ .op2 = .{ .reg = data.r2 },
+ });
},
- else => return emit.fail("TODO unused movabs variant", .{}),
+ else => unreachable, // TODO
}
}
-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,
- 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);
-}
-
-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,
- 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);
-}
-
-fn mirShift(emit: *Emit, tag: Tag, 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);
- },
- 0b01 => {
- // sal reg1, .cl
- // MC
- return lowerToMcEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code);
- },
- 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);
- },
- 0b11 => {
- return emit.fail("TODO unused variant: SHIFT reg1, 0b11", .{});
+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 = mnemonicFromConditionCode("set", data.cc);
+ return emit.encode(mnemonic, .{
+ .op1 = .{ .reg = data.r1 },
+ });
},
+ else => unreachable, // TODO
}
}
-fn mirMulDiv(emit: *Emit, tag: Tag, 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);
- }
- assert(ops.reg2 != .none);
- const imm = emit.mir.instructions.items(.data)[inst].imm;
- const ptr_size: Memory.PtrSize = switch (ops.flags) {
- 0b00 => .byte_ptr,
- 0b01 => .word_ptr,
- 0b10 => .dword_ptr,
- 0b11 => .qword_ptr,
- };
- return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{
- .disp = imm,
- .base = ops.reg2,
- }), emit.code);
-}
-
-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 lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
- },
- 0b01 => {
- const imm = emit.mir.instructions.items(.data)[inst].imm;
- 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);
- },
- 0b10 => {
- const imm = emit.mir.instructions.items(.data)[inst].imm;
- return lowerToRmiEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), imm, emit.code);
- },
- 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);
+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 mirCwd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
- const ops = emit.mir.instructions.items(.ops)[inst].decode();
- const tag: Tag = switch (ops.flags) {
- 0b00 => .cbw,
- 0b01 => .cwd,
- 0b10 => .cdq,
- 0b11 => .cqo,
- };
- return lowerToZoEnc(tag, emit.code);
-}
-
-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 => {
- // lea 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(
- .lea,
- ops.reg1,
- RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{
- .disp = imm,
- .base = src_reg,
- }),
- emit.code,
- );
- },
- 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,
- );
- 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 => {
- // 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{
- .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,
- .base = src_reg,
- .scale_index = scale_index,
- }),
- emit.code,
- );
- },
- 0b11 => return emit.fail("TODO unused LEA variant 0b11", .{}),
- }
+fn mirJmpReloc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
+ 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 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();
+fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
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", .{}),
- }
-
- // lea reg1, [rip + reloc]
- // RM
- try lowerToRmEnc(
- .lea,
- ops.reg1,
- RegisterOrMemory.rip(Memory.PtrSize.new(ops.reg1.size()), 0),
- emit.code,
- );
-
- const end_offset = emit.code.items.len;
+ 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| {
- 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,
- };
+ // 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 = reloc_type,
- .target = .{ .sym_index = relocation.sym_index, .file = null },
- .offset = @intCast(u32, end_offset - 4),
+ .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 = 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),
+ .type = .direct,
+ .target = target,
+ .offset = offset,
.addend = 0,
.pcrel = true,
.length = 2,
});
} else {
- return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{});
- }
-}
-
-// SSE instructions
-
-fn mirMovFloatSse(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 lowerToRmEnc(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 lowerToMrEnc(tag, RegisterOrMemory.mem(Memory.PtrSize.new(ops.reg1.size()), .{
- .disp = imm,
- .base = ops.reg1,
- }), ops.reg2, emit.code);
- },
- 0b10 => {
- return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
- },
- else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }),
- }
-}
-
-fn mirAddFloatSse(emit: *Emit, tag: Tag, 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);
- },
- else => return emit.fail("TODO unused variant 0b{b} for {}", .{ ops.flags, tag }),
- }
-}
-
-fn mirCmpFloatSse(emit: *Emit, tag: Tag, 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);
- },
- 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 }),
+ return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{});
}
}
-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}),
+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 = base,
+ .disp = disp,
+ }) };
+ const op2: Instruction.Operand = .{ .reg = reg };
+ switch (tag) {
+ .push => try emit.encode(.mov, .{
+ .op1 = op1,
+ .op2 = op2,
+ }),
+ .pop => try emit.encode(.mov, .{
+ .op1 = op2,
+ .op2 = op1,
+ }),
+ else => unreachable,
+ }
+ disp += 8;
+ }
}
}
-// Pseudo-instructions
+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 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;
+ try emit.encode(.lea, .{
+ .op1 = .{ .reg = reg },
+ .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(reg.bitSize()), 0) },
+ });
- const offset = blk: {
- // callq
- try lowerToDEnc(.call_near, 0, emit.code);
- break :blk @intCast(u32, emit.code.items.len) - 4;
- };
+ const end_offset = emit.code.items.len;
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);
+ 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 = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH),
- .target = target,
- .offset = offset,
+ .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| {
- // 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);
+ const atom_index = coff_file.getAtomIndexForSymbol(.{
+ .sym_index = metadata.atom_index,
+ .file = null,
+ }).?;
try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{
- .type = .direct,
- .target = target,
- .offset = offset,
+ .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 call_extern for linking backends different than MachO and COFF", .{});
+ 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 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", .{});
@@ -1232,8 +620,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();
@@ -1249,8 +636,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();
@@ -1264,1841 +650,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..a51f954aed
--- /dev/null
+++ b/src/arch/x86_64/Encoding.zig
@@ -0,0 +1,587 @@
+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);
+
+ 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;
+ 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))
+ {
+ 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 {
+ 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; // Not allowed to fail here unless OOM.
+ return @intCast(usize, cwriter.bytes_written);
+ }
+ };
+
+ var shortest_encoding: ?struct {
+ index: usize,
+ len: usize,
+ } = null;
+ var i: usize = 0;
+ while (i < count) : (i += 1) {
+ const candidate = candidates[i];
+ switch (candidate.mode) {
+ .long, .rex => if (rex_invalid) return error.CannotEncode,
+ else => {},
+ }
+
+ const len = EncodingLength.estimate(candidate, .{
+ .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 {
+ 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, .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,
+ else => {},
+ }
+ } else {
+ if (enc.mode == .none) {
+ switch (enc.operandBitSize()) {
+ 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 operandBitSize(encoding: Encoding) u64 {
+ if (encoding.mode == .long) return 64;
+ const bit_size: u64 = switch (encoding.op_en) {
+ .np => switch (encoding.op1) {
+ .o16 => 16,
+ .o32 => 32,
+ .o64 => 64,
+ else => 32,
+ },
+ .td => encoding.op2.bitSize(),
+ else => encoding.op1.bitSize(),
+ };
+ 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, .imm8s => "ib",
+ .imm16, .imm16s => "iw",
+ .imm32, .imm32s => "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,
+ imm8s, imm16s, imm32s,
+ 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.bitSize()) {
+ 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.bitSize()) {
+ 8 => .r8,
+ 16 => .r16,
+ 32 => .r32,
+ 64 => .r64,
+ else => unreachable,
+ };
+ },
+ }
+ },
+
+ .mem => |mem| switch (mem) {
+ .moffs => return .moffs,
+ .sib, .rip => {
+ const bit_size = mem.bitSize();
+ return switch (bit_size) {
+ 8 => .m8,
+ 16 => .m16,
+ 32 => .m32,
+ 64 => .m64,
+ 80 => .m80,
+ else => unreachable,
+ };
+ },
+ },
+
+ .imm => |imm| {
+ 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 bitSize(op: Op) u64 {
+ return switch (op) {
+ .none, .o16, .o32, .o64, .moffs, .m, .sreg => unreachable,
+ .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,
+ .m80 => 80,
+ .xmm => 128,
+ };
+ }
+
+ 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) {
+ .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,
+ .imm8s, .imm16s, .imm32s,
+ .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.bitSize() == target.bitSize(),
+ },
+ }
+ }
+ if (op.isMemory() and target.isMemory()) {
+ switch (target) {
+ .m => return true,
+ else => return op.bitSize() == target.bitSize(),
+ }
+ }
+ if (op.isImmediate() and target.isImmediate()) {
+ switch (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;
+ },
+ }
+ }
+};
+
+pub const Mode = enum {
+ none,
+ fpu,
+ rex,
+ long,
+ sse,
+ sse2,
+};
diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig
index ba71f4cddd..3951108e3a 100644
--- a/src/arch/x86_64/Mir.zig
+++ b/src/arch/x86_64/Mir.zig
@@ -12,9 +12,12 @@ 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;
+const Memory = bits.Memory;
const Register = bits.Register;
instructions: std.MultiArrayList(Inst).Slice,
@@ -24,431 +27,300 @@ extra: []const u32,
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
- /// 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`.
+ pub const Index = u32;
+ pub const Tag = enum(u8) {
+ /// Add with carry
+ adc,
+ /// Add
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,
+ /// Logical and
@"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,
+ /// 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,
- 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,
+ /// Unsigned division
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
+ /// Store integer with truncation
fisttp,
-
- /// ops flags: form:
- /// 0b01 dword ptr [reg1 + imm32]
- /// 0b10 qword ptr [reg1 + imm32]
+ /// Load floating-point value
fld,
-
- /// ops flags: form:
- /// 0b00 inst
- /// 0b01 reg1
- /// 0b01 [imm32] if reg1 is none
- /// 0b10 [reg1 + imm32]
+ /// Signed division
+ idiv,
+ /// Signed multiplication
+ imul,
+ ///
+ int3,
+ /// Jump
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,
+ /// 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,
-
- /// ops flags: form:
- /// 0b00 retf imm16
- /// 0b01 retf
- /// 0b10 retn imm16
- /// 0b11 retn
+ /// Push
+ push,
+ /// Return
ret,
-
- /// Fast system call
+ /// Arithmetic shift left
+ sal,
+ /// Arithmetic shift right
+ sar,
+ /// Integer subtraction with borrow
+ sbb,
+ /// Logical shift left
+ shl,
+ /// Logical shift right
+ shr,
+ /// Subtract
+ sub,
+ /// Syscall
syscall,
-
- /// ops flags: form:
- /// 0b00 reg1, imm32 if reg2 == .none
- /// 0b00 reg1, reg2
- /// TODO handle more cases
+ /// Test condition
@"test",
+ /// Undefined instruction
+ ud2,
+ /// Logical exclusive-or
+ xor,
- /// Undefined Instruction
- ud,
-
- /// Breakpoint form:
- /// 0b00 int3
- interrupt,
-
- /// Nop
- nop,
-
- /// SSE 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,
-
- /// ops flags: form:
- /// 0b00 reg1, reg2
- add_f64_sse,
- add_f32_sse,
-
- /// 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,
-
- /// Pseudo-instructions
- /// call extern function
- /// Notes:
- /// * target of the call is stored as `relocation` in `Data` union.
+ /// 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,
+
+ /// Conditional move
+ cmovcc,
+ /// Conditional jump
+ jcc,
+ /// Set byte on condition
+ setcc,
+
+ /// Mov absolute to/from memory wrt segment register to/from rax
+ mov_moffs,
+
+ /// Jump with relocation to another local MIR instruction
+ /// Uses `inst` payload.
+ jmp_reloc,
+
+ /// Call to an extern symbol via linker relocation.
+ /// Uses `relocation` payload.
call_extern,
- /// end of prologue
- dbg_prologue_end,
+ /// Load effective address of a symbol not yet allocated in VM.
+ lea_linker,
- /// start of epilogue
+ /// End of prologue
+ dbg_prologue_end,
+ /// Start of epilogue
dbg_epilogue_begin,
-
- /// update debug line
+ /// Update debug line
+ /// Uses `payload` payload with data of type `DbgLineColumn`.
dbg_line,
-
- /// push registers
- /// Uses `payload` field with `SaveRegisterList` as payload.
+ /// Push registers
+ /// Uses `payload` payload with data of type `SaveRegisterList`.
push_regs,
-
- /// pop registers
- /// Uses `payload` field with `SaveRegisterList` as payload.
+ /// Pop registers
+ /// Uses `payload` payload with data of type `SaveRegisterList`.
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 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,
- };
- }
+ /// Tombstone
+ /// Emitter should skip this instruction.
+ dead,
+ };
- 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,
- };
- }
+ 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, register, immediate (sign-extended) operands.
+ /// Uses `rri` payload.
+ rri_s,
+ /// Register, register, immediate (unsigned) operands.
+ /// Uses `rri` payload.
+ rri_u,
+ /// 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` payload.
+ ri_s,
+ /// Register, immediate (unsigned) operands.
+ /// 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` payload.
+ imm_s,
+ /// Immediate (unsigned) operand.
+ /// Uses `imm` payload.
+ imm_u,
+ /// Relative displacement operand.
+ /// Uses `imm` payload.
+ rel,
+ /// Register, memory (SIB) operands.
+ /// Uses `rx` payload.
+ rm_sib,
+ /// Register, memory (RIP) operands.
+ /// Uses `rx` payload.
+ rm_rip,
+ /// 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` payload with extra data of type `MemorySib`.
+ mi_u_sib,
+ /// Memory (RIP), immediate (unsigned) operands.
+ /// Uses `xi` payload with extra data of type `MemoryRip`.
+ mi_u_rip,
+ /// Memory (SIB), immediate (sign-extend) operands.
+ /// Uses `xi` payload with extra data of type `MemorySib`.
+ mi_s_sib,
+ /// Memory (RIP), immediate (sign-extend) operands.
+ /// 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`.
+ 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,
+ /// 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,
+ /// 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,
};
- /// 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 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 immediate value.
+ imm: u32,
+ r: Register,
+ rr: struct {
+ r1: Register,
+ r2: Register,
+ },
+ rrr: struct {
+ r1: Register,
+ r2: Register,
+ r3: Register,
+ },
+ rri: struct {
+ r1: Register,
+ r2: Register,
+ imm: u32,
+ },
+ /// 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, immediate.
+ ri: struct {
+ r1: Register,
+ imm: u32,
+ },
+ /// Register, followed by custom payload found in extra.
+ rx: struct {
+ r1: Register,
+ payload: u32,
+ },
+ /// Custom payload followed by an immediate.
+ xi: struct {
+ payload: u32,
+ imm: u32,
+ },
/// Relocation for the linker where:
/// * `atom_index` is the index of the source
/// * `sym_index` is the index of the target
@@ -471,62 +343,13 @@ pub const Inst = struct {
}
};
-pub const IndexRegisterDisp = struct {
- /// Index register to use with SIB-based encoding
- index: u32,
-
- /// Displacement value
- disp: u32,
-
- pub fn encode(index: Register, disp: u32) IndexRegisterDisp {
- return .{
- .index = @enumToInt(index),
- .disp = disp,
- };
- }
-
- pub fn decode(this: IndexRegisterDisp) struct {
- index: Register,
- disp: u32,
- } {
- 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: u32,
-
- /// Immediate
- imm: u32,
-
- pub fn encode(index: Register, disp: u32, imm: u32) IndexRegisterDispImm {
- return .{
- .index = @enumToInt(index),
- .disp = disp,
- .imm = imm,
- };
- }
-
- pub fn decode(this: IndexRegisterDispImm) struct {
- index: Register,
- disp: u32,
- imm: u32,
- } {
- return .{
- .index = @intToEnum(Register, this.index),
- .disp = this.disp,
- .imm = this.imm,
- };
- }
+pub const LeaRegisterReloc = struct {
+ /// Destination register.
+ reg: u32,
+ /// 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
@@ -570,16 +393,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: u32,
- operand: u32,
-};
-
pub const Imm64 = struct {
msb: u32,
lsb: u32,
@@ -599,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,
@@ -610,9 +514,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/bits.zig b/src/arch/x86_64/bits.zig
index cc123b96b6..043e589af4 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,131 +135,199 @@ 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
};
}
- /// 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,
+ 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);
}
- /// 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 bitSize(reg: Register) u64 {
+ 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
+ };
}
- /// 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 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
+ };
}
- /// Like enc, but only returns the lower 3 bits.
- pub fn lowEnc(self: Register) u3 {
- return @truncate(u3, @enumToInt(self));
+ 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 toBitSize(reg: Register, bit_size: u64) 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,
+ };
+ }
+
+ 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 to256(self: Register) Register {
- return @intToEnum(Register, @as(u8, self.enc()) + 64);
+ pub fn to64(reg: Register) Register {
+ return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.rax));
}
- pub fn to128(self: Register) Register {
- return @intToEnum(Register, @as(u8, self.enc()) + 80);
+ pub fn to32(reg: Register) Register {
+ return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.eax));
}
- /// Convert from any register to its 64 bit alias.
- pub fn to64(self: Register) Register {
- return @intToEnum(Register, self.enc());
+ pub fn to16(reg: Register) Register {
+ return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.ax));
}
- /// Convert from any register to its 32 bit alias.
- pub fn to32(self: Register) Register {
- return @intToEnum(Register, @as(u8, self.enc()) + 16);
+ pub fn to8(reg: Register) Register {
+ return @intToEnum(Register, @enumToInt(reg) - reg.gpBase() + @enumToInt(Register.al));
}
- /// Convert from any register to its 16 bit alias.
- pub fn to16(self: Register) Register {
- return @intToEnum(Register, @as(u8, self.enc()) + 32);
+ 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,
+ };
}
- /// 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 to256(reg: Register) Register {
+ return @intToEnum(Register, @enumToInt(reg) - reg.fpBase() + @enumToInt(Register.ymm0));
}
- pub fn dwarfLocOp(self: Register) u8 {
- switch (@enumToInt(self)) {
- 0...63 => return switch (self.to64()) {
+ pub fn to128(reg: Register) Register {
+ return @intToEnum(Register, @enumToInt(reg) - reg.fpBase() + @enumToInt(Register.xmm0));
+ }
+
+ pub fn dwarfLocOp(reg: Register) u8 {
+ return switch (reg.class()) {
+ .general_purpose => switch (reg.to64()) {
.rax => DW.OP.reg0,
.rdx => DW.OP.reg1,
.rcx => DW.OP.reg2,
@@ -268,31 +336,19 @@ pub const Register = enum(u7) {
.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,
+ else => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.reg0,
},
-
- 64...79 => return @as(u8, self.enc()) + DW.OP.reg17,
-
+ .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()) {
+ pub fn dwarfLocOpDeref(reg: Register) u8 {
+ return switch (reg.class()) {
+ .general_purpose => switch (reg.to64()) {
.rax => DW.OP.breg0,
.rdx => DW.OP.breg1,
.rcx => DW.OP.breg2,
@@ -300,795 +356,195 @@ pub const Register = enum(u7) {
.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,
+ .rsp => DW.OP.breg7,
+ else => @intCast(u8, @enumToInt(reg) - reg.gpBase()) + DW.OP.breg0,
},
-
- 64...79 => return @as(u8, self.enc()) + DW.OP.breg17,
-
+ .floating_point => @intCast(u8, @enumToInt(reg) - reg.fpBase()) + DW.OP.breg17,
else => unreachable,
- }
+ };
}
};
-// zig fmt: on
-
-/// 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),
-
- const Self = @This();
-
- /// 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 };
- }
-
- /// 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,
- );
- }
-
- /// 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,
- );
- }
+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());
- // --------
- // 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,
- };
+ try expect(Register.ymm0.id() == 0b10000);
+ try expect(Register.ymm0.id() != Register.rax.id());
+ try expect(Register.xmm0.id() == Register.ymm0.id());
- /// 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) 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;
- }
+ try expect(Register.es.id() == 0b100000);
+}
- pub fn simd_prefix_66(self: *Vex) void {
- self.simd_prefix = 0b01;
- }
+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());
+}
- pub fn simd_prefix_f3(self: *Vex) void {
- self.simd_prefix = 0b10;
- }
+test "Register classes" {
+ try expect(Register.r11.class() == .general_purpose);
+ try expect(Register.ymm11.class() == .floating_point);
+ try expect(Register.fs.class() == .segment);
+}
- pub fn simd_prefix_f2(self: *Vex) void {
- self.simd_prefix = 0b11;
- }
+pub const Memory = union(enum) {
+ sib: Sib,
+ rip: Rip,
+ moffs: Moffs,
- pub fn wig(self: *Vex) void {
- self.wig_desc = true;
- }
+ pub const ScaleIndex = packed struct {
+ scale: u4,
+ index: Register,
+ };
- pub fn lig(self: *Vex) void {
- self.lig_desc = true;
+ pub const PtrSize = enum {
+ byte,
+ word,
+ dword,
+ qword,
+ tbyte,
+
+ pub fn fromSize(size: u32) PtrSize {
+ 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 lz(self: *Vex) void {
- self.lz_desc = true;
+ pub fn fromBitSize(bit_size: u64) PtrSize {
+ return switch (bit_size) {
+ 8 => .byte,
+ 16 => .word,
+ 32 => .dword,
+ 64 => .qword,
+ 80 => .tbyte,
+ else => unreachable,
+ };
}
- 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 bitSize(s: PtrSize) u64 {
+ return switch (s) {
+ .byte => 8,
+ .word => 16,
+ .dword => 32,
+ .qword => 64,
+ .tbyte => 80,
};
-
- 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 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;
-
- if (byte.w) value |= 0b1000;
- if (byte.r) value |= 0b0100;
- if (byte.x) value |= 0b0010;
- if (byte.b) value |= 0b0001;
-
- if (value != 0b0100_0000) {
- self.code.appendAssumeCapacity(value);
- }
- }
-
- // ------
- // Opcode
- // ------
-
- /// Encodes a 1 byte opcode
- pub fn opcode_1byte(self: Self, opcode: u8) void {
- self.code.appendAssumeCapacity(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 {
- self.code.appendAssumeCapacity(prefix);
- self.code.appendAssumeCapacity(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 {
- self.code.appendAssumeCapacity(prefix_1);
- self.code.appendAssumeCapacity(prefix_2);
- self.code.appendAssumeCapacity(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);
- self.code.appendAssumeCapacity(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 {
- self.code.appendAssumeCapacity(
- @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 {
- 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);
- }
+ pub const Rip = struct {
+ ptr_size: PtrSize,
+ disp: i32,
+ };
- /// 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);
- }
+ pub const Moffs = struct {
+ seg: Register,
+ offset: u64,
+ };
- /// 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);
+ pub fn moffs(reg: Register, offset: u64) Memory {
+ assert(reg.class() == .segment);
+ return .{ .moffs = .{ .seg = reg, .offset = offset } };
}
- // -------------------------
- // 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));
+ 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,
+ } };
}
- /// 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));
+ pub fn rip(ptr_size: PtrSize, disp: i32) Memory {
+ return .{ .rip = .{ .ptr_size = ptr_size, .disp = 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);
+ pub fn isSegmentRegister(mem: Memory) bool {
+ return switch (mem) {
+ .moffs => true,
+ .rip => false,
+ .sib => |s| if (s.base) |r| r.class() == .segment else false,
+ };
}
- /// 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);
+ pub fn base(mem: Memory) ?Register {
+ return switch (mem) {
+ .moffs => |m| m.seg,
+ .sib => |s| s.base,
+ .rip => null,
+ };
}
- /// 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);
+ pub fn scaleIndex(mem: Memory) ?ScaleIndex {
+ return switch (mem) {
+ .moffs, .rip => null,
+ .sib => |s| s.scale_index,
+ };
}
- /// 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 bitSize(mem: Memory) u64 {
+ return switch (mem) {
+ .rip => |r| r.ptr_size.bitSize(),
+ .sib => |s| s.ptr_size.bitSize(),
+ .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 "<arithmetic> 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]);
- }
+pub const Immediate = union(enum) {
+ signed: i32,
+ unsigned: u64,
- {
- 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]);
+ pub fn u(x: u64) Immediate {
+ return .{ .unsigned = x };
}
- {
- 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]);
+ pub fn s(x: i32) Immediate {
+ return .{ .signed = x };
}
- 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);
+ pub fn asUnsigned(imm: Immediate, bit_size: u64) u64 {
+ return switch (imm) {
+ .signed => |x| switch (bit_size) {
+ 1, 8 => @bitCast(u8, @intCast(i8, x)),
+ 16 => @bitCast(u16, @intCast(i16, x)),
+ 32, 64 => @bitCast(u32, x),
+ else => unreachable,
+ },
+ .unsigned => |x| switch (bit_size) {
+ 1, 8 => @intCast(u8, x),
+ 16 => @intCast(u16, x),
+ 32 => @intCast(u32, x),
+ 64 => x,
+ else => unreachable,
+ },
+ };
}
-}
-
-// 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..7e29f95069
--- /dev/null
+++ b/src/arch/x86_64/encoder.zig
@@ -0,0 +1,2275 @@
+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");
+const Immediate = bits.Immediate;
+const Memory = bits.Memory;
+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: Immediate,
+
+ /// Returns the bitsize of the operand.
+ pub fn bitSize(op: Operand) u64 {
+ return switch (op) {
+ .none => unreachable,
+ .reg => |reg| reg.bitSize(),
+ .mem => |mem| mem.bitSize(),
+ .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| try writer.print("0x{x}", .{imm.asUnsigned(enc_op.bitSize())}),
+ }
+ }
+ };
+
+ pub fn new(mnemonic: Mnemonic, args: struct {
+ op1: Operand = .none,
+ op2: Operand = .none,
+ op3: Operand = .none,
+ op4: Operand = .none,
+ }) !Instruction {
+ const encoding = (try Encoding.findByMnemonic(mnemonic, .{
+ .op1 = args.op1,
+ .op2 = args.op2,
+ .op3 = args.op3,
+ .op4 = args.op4,
+ })) orelse {
+ 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)),
+ @tagName(Encoding.Op.fromOperand(args.op3)),
+ @tagName(Encoding.Op.fromOperand(args.op4)),
+ });
+ return error.InvalidInstruction;
+ };
+ log.debug("selected encoding: {}", .{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.operandBitSize();
+ 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;
+
+ var rex = Rex{};
+ rex.present = inst.encoding.mode == .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,
+ }
+ },
+ }
+
+ 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: 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,
+ }
+ }
+};
+
+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.
+ 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;
+ if (byte.r) value |= 0b0100;
+ if (byte.x) value |= 0b0010;
+ if (byte.b) value |= 0b0001;
+
+ 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 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 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 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.
+ 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,
+ present: bool = false,
+
+ pub fn isSet(rex: Rex) bool {
+ 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);
+ }
+}
diff --git a/src/arch/x86_64/encodings.zig b/src/arch/x86_64/encodings.zig
new file mode 100644
index 0000000000..b008eb9f3e
--- /dev/null
+++ b/src/arch/x86_64/encodings.zig
@@ -0,0 +1,621 @@
+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, .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 },
+ .{ .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, .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 },
+
+ .{ .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, .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 },
+ .{ .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, .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 },
+
+ .{ .@"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, .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 },
+ .{ .@"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, .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 },
+
+ // 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, .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 },
+ .{ .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, .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 },
+
+ .{ .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, .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 },
+ .{ .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 },
+
+ .{ .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, .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 },
+ .{ .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, .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 },
+
+ // 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, .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 },
+
+ .{ .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, .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 },
+ .{ .@"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, .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 },
+
+ .{ .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, .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 },
+
+ .{ .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, .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 },
+ .{ .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, .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 },
+
+ .{ .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, .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 },
+ .{ .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, .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 },
+
+ .{ .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, .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 },
+
+ .{ .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, .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 },
+ .{ .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, .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 },
+
+ // 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
+