hphp/util/asm-x64.h (979 lines of code) (raw):

/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #pragma once #include <type_traits> #include <memory> #include "hphp/util/atomic.h" #include "hphp/util/data-block.h" #include "hphp/util/immed.h" #include "hphp/util/safe-cast.h" #include "hphp/util/trace.h" /* * An experimental macro assembler for x64, that strives for low coupling to * the runtime environment. * * There are more complete assemblers out there; if you use this one * yourself, expect not to find all the instructions you wanted to use. You * may have to go spelunking in the Intel manuals: * * http://www.intel.com/products/processor/manuals/ * * If you're looking for something more fully baked, here are some options * to consider: * * 1. Nanojit or llvm, both of which translate abstract virtual machine * instructions to the native target architecture, or * 2. The embedded assemblers from v8, the Sun JVM, etc. */ /* * Some members cannot be const because their values aren't known in * an initialization list. Like the opposite of the "mutable" keyword. * This declares this property to readers. */ #define logical_const /* nothing */ namespace HPHP::jit { #define TRACEMOD ::HPHP::Trace::asmx64 ////////////////////////////////////////////////////////////////////// struct MemoryRef; struct RIPRelativeRef; struct ScaledIndex; struct ScaledIndexDisp; struct DispReg; const uint8_t kOpsizePrefix = 0x66; enum Segment : uint8_t { DS = 0, FS, GS }; struct Reg64 { explicit constexpr Reg64(int rn) : rn(rn) {} // Integer conversion is allowed but only explicitly. (It's not // unusual to want to printf registers, etc. Just cast it first.) explicit constexpr operator int() const { return rn; } MemoryRef operator[](intptr_t disp) const; MemoryRef operator[](Reg64) const; MemoryRef operator[](ScaledIndex) const; MemoryRef operator[](ScaledIndexDisp) const; MemoryRef operator[](DispReg) const; constexpr bool operator==(Reg64 o) const { return rn == o.rn; } constexpr bool operator!=(Reg64 o) const { return rn != o.rn; } private: int rn; }; #define SIMPLE_REGTYPE(What) \ struct What { \ explicit constexpr What(int rn) : rn(rn) {} \ explicit constexpr operator int() const { return rn; } \ constexpr bool operator==(What o) const { return rn == o.rn; } \ constexpr bool operator!=(What o) const { return rn != o.rn; } \ private: \ int rn; \ } SIMPLE_REGTYPE(Reg32); SIMPLE_REGTYPE(Reg16); SIMPLE_REGTYPE(Reg8); SIMPLE_REGTYPE(RegXMM); SIMPLE_REGTYPE(RegSF); #undef SIMPLE_REGTYPE struct RegRIP { RIPRelativeRef operator[](intptr_t disp) const; }; // Convert between physical registers of different sizes inline Reg8 rbyte(Reg32 r) { return Reg8(int(r)); } inline Reg8 rbyte(Reg64 r) { return Reg8(int(r)); } inline Reg16 r16(Reg8 r) { return Reg16(int(r)); } inline Reg16 r16(Reg32 r) { return Reg16(int(r)); } inline Reg32 r32(Reg8 r) { return Reg32(int(r)); } inline Reg32 r32(Reg16 r) { return Reg32(int(r)); } inline Reg32 r32(Reg32 r) { return r; } inline Reg32 r32(Reg64 r) { return Reg32(int(r)); } inline Reg64 r64(Reg8 r) { return Reg64(int(r)); } inline Reg64 r64(Reg16 r) { return Reg64(int(r)); } inline Reg64 r64(Reg32 r) { return Reg64(int(r)); } inline Reg64 r64(Reg64 r) { return r; } ////////////////////////////////////////////////////////////////////// /* * The following structures define intermediate types for various * addressing modes. They overload some operators to allow using * registers to look somewhat like pointers. * * E.g. rax[rbx*2 + 3] or *(rax + rbx*2 + 3). * * These operators are not defined commutatively; the thought is it * mandates the order you normally write them in a .S, but it could be * changed if this proves undesirable. */ // reg*x struct ScaledIndex { explicit ScaledIndex(Reg64 index, intptr_t scale) : index(index) , scale(scale) { assert((scale == 0x1 || scale == 0x2 || scale == 0x4 || scale == 0x8) && "Invalid index register scaling (must be 1,2,4 or 8)."); assert(int(index) != -1 && "invalid register"); } Reg64 index; intptr_t scale; }; // reg*x + disp struct ScaledIndexDisp { explicit ScaledIndexDisp(ScaledIndex si, intptr_t disp) : si(si) , disp(disp) {} ScaledIndexDisp operator+(intptr_t x) const { return ScaledIndexDisp(si, disp + x); } ScaledIndexDisp operator-(intptr_t x) const { return ScaledIndexDisp(si, disp - x); } ScaledIndex si; intptr_t disp; }; // reg+x struct DispReg { explicit DispReg(Reg64 base, intptr_t disp = 0) : base(base) , disp(disp) { assert(int(base) != -1 && "invalid register"); } // Constructor for baseless(). explicit DispReg(intptr_t disp) : base(-1) , disp(disp) {} MemoryRef operator*() const; MemoryRef operator[](intptr_t) const; DispReg operator+(intptr_t x) const { return DispReg(base, disp + x); } DispReg operator-(intptr_t x) const { return DispReg(base, disp - x); } Reg64 base; intptr_t disp; }; // reg + reg*x + y struct IndexedDispReg { explicit IndexedDispReg(Reg64 base, ScaledIndex sr) : base(base) , index(sr.index) , scale(sr.scale) , disp(0) {} explicit IndexedDispReg(DispReg r) : base(r.base) , index(-1) , scale(1) , disp(r.disp) {} // Constructor for baseless() explicit IndexedDispReg(ScaledIndexDisp sid) : base(-1) , index(sid.si.index) , scale(sid.si.scale) , disp(sid.disp) {} MemoryRef operator*() const; MemoryRef operator[](intptr_t disp) const; IndexedDispReg operator+(intptr_t disp) const { auto ret = *this; ret.disp += disp; return ret; } IndexedDispReg operator-(intptr_t disp) const { auto ret = *this; ret.disp -= disp; return ret; } Reg64 base; Reg64 index; int scale; intptr_t disp; // TODO #4613274: should be int32_t }; // rip+x struct DispRIP { explicit constexpr DispRIP(intptr_t disp) : disp(disp) {} RIPRelativeRef operator*() const; RIPRelativeRef operator[](intptr_t x) const; DispRIP operator+(intptr_t x) const { return DispRIP(disp + x); } DispRIP operator-(intptr_t x) const { return DispRIP(disp - x); } bool operator==(DispRIP o) const { return disp == o.disp; } bool operator!=(DispRIP o) const { return disp != o.disp; } intptr_t disp; }; // *(rip + x) struct RIPRelativeRef { explicit constexpr RIPRelativeRef(DispRIP r) : r(r) {} DispRIP r; bool operator==(const RIPRelativeRef& o) const { return r == o.r; } bool operator!=(const RIPRelativeRef& o) const { return r != o.r; } }; ////////////////////////////////////////////////////////////////////// namespace reg { constexpr Reg64 rax(0); constexpr Reg64 rcx(1); constexpr Reg64 rdx(2); constexpr Reg64 rbx(3); constexpr Reg64 rsp(4); constexpr Reg64 rbp(5); constexpr Reg64 rsi(6); constexpr Reg64 rdi(7); constexpr Reg64 r8 (8); constexpr Reg64 r9 (9); constexpr Reg64 r10(10); constexpr Reg64 r11(11); constexpr Reg64 r12(12); constexpr Reg64 r13(13); constexpr Reg64 r14(14); constexpr Reg64 r15(15); constexpr RegRIP rip = RegRIP(); constexpr Reg32 eax (0); constexpr Reg32 ecx (1); constexpr Reg32 edx (2); constexpr Reg32 ebx (3); constexpr Reg32 esp (4); constexpr Reg32 ebp (5); constexpr Reg32 esi (6); constexpr Reg32 edi (7); constexpr Reg32 r8d (8); constexpr Reg32 r9d (9); constexpr Reg32 r10d(10); constexpr Reg32 r11d(11); constexpr Reg32 r12d(12); constexpr Reg32 r13d(13); constexpr Reg32 r14d(14); constexpr Reg32 r15d(15); constexpr Reg16 ax (0); constexpr Reg16 cx (1); constexpr Reg16 dx (2); constexpr Reg16 bx (3); constexpr Reg16 sp (4); constexpr Reg16 bp (5); constexpr Reg16 si (6); constexpr Reg16 di (7); constexpr Reg16 r8w (8); constexpr Reg16 r9w (9); constexpr Reg16 r10w(10); constexpr Reg16 r11w(11); constexpr Reg16 r12w(12); constexpr Reg16 r13w(13); constexpr Reg16 r14w(14); constexpr Reg16 r15w(15); constexpr Reg8 al (0); constexpr Reg8 cl (1); constexpr Reg8 dl (2); constexpr Reg8 bl (3); constexpr Reg8 spl (4); constexpr Reg8 bpl (5); constexpr Reg8 sil (6); constexpr Reg8 dil (7); constexpr Reg8 r8b (8); constexpr Reg8 r9b (9); constexpr Reg8 r10b(10); constexpr Reg8 r11b(11); constexpr Reg8 r12b(12); constexpr Reg8 r13b(13); constexpr Reg8 r14b(14); constexpr Reg8 r15b(15); // Reminder: these registers may not be mixed in any instruction // using a REX prefix (i.e. anything using r8-r15, spl, bpl, sil, // dil, etc). constexpr Reg8 ah(0x80 | 4); constexpr Reg8 ch(0x80 | 5); constexpr Reg8 dh(0x80 | 6); constexpr Reg8 bh(0x80 | 7); constexpr RegXMM xmm0(0); constexpr RegXMM xmm1(1); constexpr RegXMM xmm2(2); constexpr RegXMM xmm3(3); constexpr RegXMM xmm4(4); constexpr RegXMM xmm5(5); constexpr RegXMM xmm6(6); constexpr RegXMM xmm7(7); constexpr RegXMM xmm8(8); constexpr RegXMM xmm9(9); constexpr RegXMM xmm10(10); constexpr RegXMM xmm11(11); constexpr RegXMM xmm12(12); constexpr RegXMM xmm13(13); constexpr RegXMM xmm14(14); constexpr RegXMM xmm15(15); #define X(x) if (r == x) return "%"#x inline const char* regname(Reg64 r) { X(rax); X(rbx); X(rcx); X(rdx); X(rsp); X(rbp); X(rsi); X(rdi); X(r8); X(r9); X(r10); X(r11); X(r12); X(r13); X(r14); X(r15); return nullptr; } inline const char* regname(Reg32 r) { X(eax); X(ecx); X(edx); X(ebx); X(esp); X(ebp); X(esi); X(edi); X(r8d); X(r9d); X(r10d); X(r11d); X(r12d); X(r13d); X(r14d); X(r15d); return nullptr; } inline const char* regname(Reg16 r) { X(ax); X(cx); X(dx); X(bx); X(sp); X(bp); X(si); X(di); X(r8w); X(r9w); X(r10w); X(r11w); X(r12w); X(r13w); X(r14w); X(r15w); return nullptr; } inline const char* regname(Reg8 r) { X(al); X(cl); X(dl); X(bl); X(spl); X(bpl); X(sil); X(dil); X(r8b); X(r9b); X(r10b); X(r11b); X(r12b); X(r13b); X(r14b); X(r15b); X(ah); X(ch); X(dh); X(bh); return nullptr; } inline const char* regname(RegXMM r) { X(xmm0); X(xmm1); X(xmm2); X(xmm3); X(xmm4); X(xmm5); X(xmm6); X(xmm7); X(xmm8); X(xmm9); X(xmm10); X(xmm11); X(xmm12); X(xmm13); X(xmm14); X(xmm15); return nullptr; } inline const char* regname(RegSF /*r*/) { return "%flags"; } #undef X } enum class RoundDirection : ssize_t { nearest = 0, floor = 1, ceil = 2, truncate = 3, }; const char* show(RoundDirection); enum class ComparisonPred : uint8_t { // True if... eq_ord = 0, // ...operands are ordered AND equal ne_unord = 4, // ...operands are unordered OR unequal }; enum ConditionCode { CC_None = -1, CC_O = 0x00, CC_NO = 0x01, CC_B = 0x02, CC_NAE = 0x02, CC_AE = 0x03, CC_NB = 0x03, CC_NC = 0x03, CC_E = 0x04, CC_Z = 0x04, CC_NE = 0x05, CC_NZ = 0x05, CC_BE = 0x06, CC_NA = 0x06, CC_A = 0x07, CC_NBE = 0x07, CC_S = 0x08, CC_NS = 0x09, CC_P = 0x0A, CC_NP = 0x0B, CC_L = 0x0C, CC_NGE = 0x0C, CC_GE = 0x0D, CC_NL = 0x0D, CC_LE = 0x0E, CC_NG = 0x0E, CC_G = 0x0F, CC_NLE = 0x0F, }; // names of condition codes, indexable by the ConditionCode enum value. extern const char* cc_names[]; inline ConditionCode ccNegate(ConditionCode c) { return ConditionCode(int(c) ^ 1); // And you thought x86 was irregular! } // *(reg + x) struct MemoryRef { explicit MemoryRef(DispReg dr, Segment s = DS) : r(dr), segment(s) {} explicit MemoryRef(IndexedDispReg idr, Segment s = DS) : r(idr), segment(s) {} IndexedDispReg r; Segment segment; }; /* * Simple wrapper over a Segment value used to obtain MemoryRefs that have * MemoryRef::segment set to something different than the default (DS) value. */ struct SegReg { explicit constexpr SegReg(Segment seg) : seg(seg) {}; MemoryRef operator[](const IndexedDispReg& idr) { return MemoryRef(idr, seg); } MemoryRef operator[](const ScaledIndexDisp& sid) { return MemoryRef(IndexedDispReg(sid), seg); } MemoryRef operator[](const DispReg& dr) { return MemoryRef(dr, seg); } MemoryRef operator[](const intptr_t disp) { return MemoryRef(DispReg(disp), seg); } Segment seg; }; #ifdef HAVE_LIBXED #define NEW_X64_ASM(var, cb) \ std::unique_ptr<X64AssemblerBase> _assembler( \ RuntimeOption::EvalUseXedAssembler ? \ (X64AssemblerBase*)new XedAssembler(cb) : \ (X64AssemblerBase*)new X64Assembler(cb)); \ X64AssemblerBase& var = *_assembler #else #define NEW_X64_ASM(var, cb) X64Assembler var(cb) #endif struct Label; struct X64AssemblerBase { protected: friend struct Label; /* * Type for register numbers, independent of the size we're going to * be using it as. Also, the same register number may mean different * physical registers for different instructions (e.g. xmm0 and rax * are both 0). Only for internal use in X64Assembler. */ enum class RegNumber : int {}; static const RegNumber noreg = RegNumber(-1); public: explicit X64AssemblerBase(CodeBlock& cb) : codeBlock(cb) {} virtual ~X64AssemblerBase() {} CodeBlock& code() const { return codeBlock; } CodeAddress base() const { return codeBlock.base(); } CodeAddress frontier() const { return codeBlock.frontier(); } CodeAddress toDestAddress(CodeAddress addr) const { return codeBlock.toDestAddress(addr); } void setFrontier(CodeAddress newFrontier) { codeBlock.setFrontier(newFrontier); } size_t capacity() const { return codeBlock.capacity(); } size_t used() const { return codeBlock.used(); } size_t available() const { return codeBlock.available(); } bool contains(CodeAddress addr) const { return codeBlock.contains(addr); } bool empty() const { return codeBlock.empty(); } void clear() { codeBlock.clear(); } bool canEmit(size_t nBytes) const { assert(capacity() >= used()); return nBytes < (capacity() - used()); } #define BYTE_LOAD_OP(name) \ virtual void name##b(MemoryRef m, Reg8 r) = 0; #define LOAD_OP(name) \ virtual void name##q(MemoryRef m, Reg64 r) = 0; \ virtual void name##l(MemoryRef m, Reg32 r) = 0; \ virtual void name##w(MemoryRef m, Reg16 r) = 0; \ virtual void name##q(RIPRelativeRef m, Reg64 r) = 0; \ BYTE_LOAD_OP(name) #define BYTE_STORE_OP(name) \ virtual void name##b(Reg8 r, MemoryRef m) = 0; \ virtual void name##b(Immed i, MemoryRef m) = 0; #define STORE_OP(name) \ virtual void name##w(Immed i, MemoryRef m) = 0; \ virtual void name##l(Immed i, MemoryRef m) = 0; \ virtual void name##w(Reg16 r, MemoryRef m) = 0; \ virtual void name##l(Reg32 r, MemoryRef m) = 0; \ virtual void name##q(Reg64 r, MemoryRef m) = 0; \ BYTE_STORE_OP(name) #define BYTE_REG_OP(name) \ virtual void name##b(Reg8 r1, Reg8 r2) = 0; \ virtual void name##b(Immed i, Reg8 r) = 0; #define REG_OP(name) \ virtual void name##q(Reg64 r1, Reg64 r2) = 0; \ virtual void name##l(Reg32 r1, Reg32 r2) = 0; \ virtual void name##w(Reg16 r1, Reg16 r2) = 0; \ virtual void name##l(Immed i, Reg32 r) = 0; \ virtual void name##w(Immed i, Reg16 r) = 0; \ BYTE_REG_OP(name) #define IMM64_STORE_OP(name) \ virtual void name##q(Immed i, MemoryRef m) = 0; #define IMM64R_OP(name) \ virtual void name##q(Immed imm, Reg64 r) = 0; #define FULL_OP(name) \ LOAD_OP(name) \ STORE_OP(name) \ REG_OP(name) \ IMM64_STORE_OP(name) \ IMM64R_OP(name) LOAD_OP (load) STORE_OP (store) IMM64_STORE_OP (store) REG_OP (mov) FULL_OP(add) FULL_OP(xor) FULL_OP(sub) FULL_OP(and) FULL_OP(or) FULL_OP(test) FULL_OP(cmp) FULL_OP(sbb) #undef IMM64R_OP #undef FULL_OP #undef REG_OP #undef STORE_OP #undef LOAD_OP #undef BYTE_LOAD_OP #undef BYTE_STORE_OP #undef BYTE_REG_OP #undef IMM64_STORE_OP virtual void movq(Immed64 imm, Reg64 r) = 0; virtual void loadzbl(MemoryRef m, Reg32 r) = 0; virtual void loadzwl(MemoryRef m, Reg32 r) = 0; virtual void movzbl(Reg8 src, Reg32 dest) = 0; virtual void movsbl(Reg8 src, Reg32 dest) = 0; virtual void movzwl(Reg16 src, Reg32 dest) = 0; virtual void loadsbq(MemoryRef m, Reg64 r) = 0; virtual void movsbq(Reg8 src, Reg64 dest) = 0; virtual void crc32q(Reg64 src, Reg64 dest) = 0; virtual void lea(MemoryRef p, Reg64 reg) = 0; virtual void lea(RIPRelativeRef p, Reg64 reg) = 0; virtual void xchgq(Reg64 r1, Reg64 r2) = 0; virtual void xchgl(Reg32 r1, Reg32 r2) = 0; virtual void xchgb(Reg8 r1, Reg8 r2) = 0; virtual void imul(Reg64 r1, Reg64 r2) = 0; virtual void push(Reg64 r) = 0; virtual void pushl(Reg32 r) = 0; virtual void pop (Reg64 r) = 0; virtual void idiv(Reg64 r) = 0; virtual void incq(Reg64 r) = 0; virtual void incl(Reg32 r) = 0; virtual void incw(Reg16 r) = 0; virtual void decq(Reg64 r) = 0; virtual void decl(Reg32 r) = 0; virtual void decw(Reg16 r) = 0; virtual void notb(Reg8 r) = 0; virtual void not(Reg64 r) = 0; virtual void neg(Reg64 r) = 0; virtual void negb(Reg8 r) = 0; virtual void ret() = 0; virtual void ret(Immed i) = 0; virtual void cqo() = 0; virtual void nop() = 0; virtual void int3() = 0; virtual void ud2() = 0; virtual void pushf() = 0; virtual void popf() = 0; virtual void lock() = 0; virtual void push(MemoryRef m) = 0; virtual void pop (MemoryRef m) = 0; virtual void prefetch(MemoryRef m) = 0; virtual void incq(MemoryRef m) = 0; virtual void incl(MemoryRef m) = 0; virtual void incw(MemoryRef m) = 0; virtual void decqlock(MemoryRef m) = 0; virtual void decq(MemoryRef m) = 0; virtual void decl(MemoryRef m) = 0; virtual void decw(MemoryRef m) = 0; virtual void push(Immed64 i) = 0; virtual void movups(RegXMM x, MemoryRef m) = 0; virtual void movups(MemoryRef m, RegXMM x) = 0; virtual void movdqu(RegXMM x, MemoryRef m) = 0; virtual void movdqu(MemoryRef m, RegXMM x) = 0; virtual void movdqa(RegXMM x, RegXMM y) = 0; virtual void movdqa(RegXMM x, MemoryRef m) = 0; virtual void movdqa(MemoryRef m, RegXMM x) = 0; virtual void movsd (RegXMM x, RegXMM y) = 0; virtual void movsd (RegXMM x, MemoryRef m) = 0; virtual void movsd (MemoryRef m, RegXMM x) = 0; virtual void movsd (RIPRelativeRef m, RegXMM x) = 0; virtual void lddqu (MemoryRef m, RegXMM x) = 0; virtual void unpcklpd(RegXMM s, RegXMM d) = 0; virtual void rorq (Immed i, Reg64 r) = 0; virtual void shlq (Immed i, Reg64 r) = 0; virtual void shrq (Immed i, Reg64 r) = 0; virtual void sarq (Immed i, Reg64 r) = 0; virtual void shll (Immed i, Reg32 r) = 0; virtual void shrl (Immed i, Reg32 r) = 0; virtual void shlw (Immed i, Reg16 r) = 0; virtual void shrw (Immed i, Reg16 r) = 0; virtual void shlq (Reg64 r) = 0; virtual void shrq (Reg64 r) = 0; virtual void sarq (Reg64 r) = 0; virtual void roundsd (RoundDirection d, RegXMM src, RegXMM dst) = 0; virtual void cmpsd(RegXMM src, RegXMM dst, ComparisonPred pred) = 0; /* * The following utility functions do more than emit specific code. * (E.g. combine common idioms or patterns, smash code, etc.) */ void emitImmReg(Immed64 imm, Reg64 dest) { if (imm.q() == 0) { // Zeros the top bits also. xorl (r32(dest), r32(dest)); return; } if (LIKELY(imm.q() > 0 && imm.fits(sz::dword))) { // This will zero out the high-order bits. movl (imm.l(), r32(dest)); return; } movq (imm.q(), dest); } bool jmpDeltaFits(CodeAddress dest) { int64_t delta = dest - (codeBlock.frontier() + 5); return deltaFits(delta, sz::dword); } virtual void jmp(Reg64 r) = 0; virtual void jmp(MemoryRef m) = 0; virtual void jmp(RIPRelativeRef m) = 0; virtual void call(Reg64 r) = 0; virtual void call(MemoryRef m) = 0; virtual void call(RIPRelativeRef m) = 0; virtual void jmp8(CodeAddress dest) = 0; virtual void jmp(CodeAddress dest) = 0; virtual void call(CodeAddress dest) = 0; virtual void jcc(ConditionCode cond, CodeAddress dest) = 0; virtual void jcc8(ConditionCode cond, CodeAddress dest)= 0; void jmpAuto(CodeAddress dest) { auto delta = dest - (codeBlock.frontier() + 2); if (deltaFits(delta, sz::byte)) { jmp8(dest); } else { jmp(dest); } } void jccAuto(ConditionCode cc, CodeAddress dest) { auto delta = dest - (codeBlock.frontier() + 2); if (deltaFits(delta, sz::byte)) { jcc8(cc, dest); } else { jcc(cc, dest); } } void call(Label&); void jmp(Label&); void jmp8(Label&); void jcc(ConditionCode, Label&); void jcc8(ConditionCode, Label&); #define CCS \ CC(o, CC_O) \ CC(no, CC_NO) \ CC(nae, CC_NAE) \ CC(ae, CC_AE) \ CC(nb, CC_NB) \ CC(e, CC_E) \ CC(z, CC_Z) \ CC(ne, CC_NE) \ CC(nz, CC_NZ) \ CC(b, CC_B) \ CC(be, CC_BE) \ CC(nbe, CC_NBE) \ CC(s, CC_S) \ CC(ns, CC_NS) \ CC(p, CC_P) \ CC(np, CC_NP) \ CC(nge, CC_NGE) \ CC(g, CC_G) \ CC(l, CC_L) \ CC(ge, CC_GE) \ CC(nl, CC_NL) \ CC(ng, CC_NG) \ CC(le, CC_LE) \ CC(nle, CC_NLE) #define CC(_nm, _code) \ void j ## _nm(CodeAddress dest) { jcc(_code, dest); } \ void j ## _nm ## 8(CodeAddress dest) { jcc8(_code, dest); } \ void j ## _nm(Label&); \ void j ## _nm ## 8(Label&); CCS #undef CC #define CC(_nm, _cond) \ void set ## _nm(Reg8 byteReg) { \ setcc(_cond, byteReg); \ } CCS #undef CC virtual void setcc(int cc, Reg8 byteReg) = 0; virtual void psllq(Immed i, RegXMM r) = 0; virtual void psrlq(Immed i, RegXMM r) = 0; virtual void movq_rx(Reg64 rSrc, RegXMM rdest) = 0; virtual void movq_xr(RegXMM rSrc, Reg64 rdest) = 0; virtual void addsd(RegXMM src, RegXMM srcdest) = 0; virtual void mulsd(RegXMM src, RegXMM srcdest) = 0; virtual void subsd(RegXMM src, RegXMM srcdest) = 0; virtual void pxor(RegXMM src, RegXMM srcdest) = 0; virtual void cvtsi2sd(Reg64 src, RegXMM dest) = 0; virtual void cvtsi2sd(MemoryRef m, RegXMM dest) = 0; virtual void ucomisd(RegXMM l, RegXMM r) = 0; virtual void sqrtsd(RegXMM src, RegXMM dest) = 0; virtual void divsd(RegXMM src, RegXMM srcdest) = 0; virtual void cvttsd2siq(RegXMM src, Reg64 dest) = 0; static void patchJcc(CodeAddress jmp, CodeAddress from, CodeAddress dest) { assert(jmp[0] == 0x0F && (jmp[1] & 0xF0) == 0x80); ssize_t diff = dest - (from + 6); *(int32_t*)(jmp + 2) = safe_cast<int32_t>(diff); } static void patchJcc8(CodeAddress jmp, CodeAddress from, CodeAddress dest) { assert((jmp[0] & 0xF0) == 0x70); ssize_t diff = dest - (from + 2); // one for opcode, one for offset *(int8_t*)(jmp + 1) = safe_cast<int8_t>(diff); } static void patchJmp(CodeAddress jmp, CodeAddress from, CodeAddress dest) { assert(jmp[0] == 0xE9); ssize_t diff = dest - (from + 5); *(int32_t*)(jmp + 1) = safe_cast<int32_t>(diff); } static void patchJmp8(CodeAddress jmp, CodeAddress from, CodeAddress dest) { assert(jmp[0] == 0xEB); ssize_t diff = dest - (from + 2); // one for opcode, one for offset *(int8_t*)(jmp + 1) = safe_cast<int8_t>(diff); } static void patchCall(CodeAddress call, CodeAddress from, CodeAddress dest) { assert(call[0] == 0xE8); ssize_t diff = dest - (from + 5); *(int32_t*)(call + 1) = safe_cast<int32_t>(diff); } virtual void emitInt3s(int n) = 0; virtual void emitNop(int n) = 0; virtual void pad() = 0; void byte(uint8_t b) { codeBlock.byte(b); } void word(uint16_t w) { codeBlock.word(w); } void dword(uint32_t dw) { codeBlock.dword(dw); } void qword(uint64_t qw) { codeBlock.qword(qw); } void bytes(size_t n, const uint8_t* bs) { codeBlock.bytes(n, bs); } virtual void cload_reg64_disp_reg64(ConditionCode cc, Reg64 rbase, int off, Reg64 rdest) = 0; virtual void cload_reg64_disp_reg32(ConditionCode cc, Reg64 rbase, int off, Reg32 rdest) = 0; virtual void cmov_reg64_reg64(ConditionCode cc, Reg64 rsrc, Reg64 rdest) = 0; protected: RegNumber rn(Reg8 r) { return RegNumber(int(r)); } RegNumber rn(Reg16 r) { return RegNumber(int(r)); } RegNumber rn(Reg32 r) { return RegNumber(int(r)); } RegNumber rn(Reg64 r) { return RegNumber(int(r)); } RegNumber rn(RegXMM r) { return RegNumber(int(r)); } CodeBlock& codeBlock; }; } #ifdef HAVE_LIBXED #include "hphp/util/asm-x64-intelxed.h" #endif #include "hphp/util/asm-x64-legacy.h" namespace HPHP::jit { inline MemoryRef IndexedDispReg::operator*() const { return MemoryRef(*this); } inline MemoryRef IndexedDispReg::operator[](intptr_t x) const { return *(*this + x); } inline MemoryRef DispReg::operator*() const { return MemoryRef(*this); } inline MemoryRef DispReg::operator[](intptr_t x) const { return *(*this + x); } inline RIPRelativeRef DispRIP::operator*() const { return RIPRelativeRef(*this); } inline RIPRelativeRef DispRIP::operator[](intptr_t x) const { return *(*this + x); } inline DispReg operator+(Reg64 r, intptr_t d) { return DispReg(r, d); } inline DispReg operator-(Reg64 r, intptr_t d) { return DispReg(r, -d); } inline DispRIP operator+(RegRIP /*r*/, intptr_t d) { return DispRIP(d); } inline DispRIP operator-(RegRIP /*r*/, intptr_t d) { return DispRIP(d); } inline ScaledIndex operator*(Reg64 r, int scale) { return ScaledIndex(r, scale); } inline IndexedDispReg operator+(Reg64 base, ScaledIndex sr) { return IndexedDispReg(base, sr); } inline ScaledIndexDisp operator+(ScaledIndex si, intptr_t disp) { return ScaledIndexDisp(si, disp); } inline IndexedDispReg operator+(Reg64 b, Reg64 i) { return b + ScaledIndex(i, 0x1); } inline MemoryRef operator*(Reg64 r) { return MemoryRef(DispReg(r)); } inline DispRIP operator*(RegRIP /*r*/) { return DispRIP(0); } inline MemoryRef Reg64::operator[](intptr_t disp) const { return *(*this + disp); } inline MemoryRef Reg64::operator[](Reg64 idx) const { return *(*this + idx * 1); } inline MemoryRef Reg64::operator[](ScaledIndex si) const { return *(*this + si); } inline MemoryRef Reg64::operator[](DispReg dr) const { return *(*this + ScaledIndex(dr.base, 0x1) + dr.disp); } inline MemoryRef Reg64::operator[](ScaledIndexDisp sid) const { return *(*this + sid.si + sid.disp); } inline RIPRelativeRef RegRIP::operator[](intptr_t disp) const { return *(*this + disp); } /* * Used for the x64 addressing mode where there is a displacement, * possibly with a scaled index, but no base register. */ inline MemoryRef baseless(intptr_t disp) { return *(DispReg { disp }); } inline MemoryRef baseless(ScaledIndexDisp sid) { return *(IndexedDispReg { sid }); } ////////////////////////////////////////////////////////////////////// struct Label { explicit Label() : m_a(nullptr) , m_address(nullptr) {} ~Label() { // Label had jumps but was never set -- this can happen if we fill the TC. if (!m_a || !m_address) { return; } for (auto& ji : m_toPatch) { auto realSrc = ji.a->toDestAddress(ji.addr); switch (ji.type) { case Branch::Jmp: ji.a->patchJmp(realSrc, ji.addr, m_address); break; case Branch::Jmp8: ji.a->patchJmp8(realSrc, ji.addr, m_address); break; case Branch::Jcc: ji.a->patchJcc(realSrc, ji.addr, m_address); break; case Branch::Jcc8: ji.a->patchJcc8(realSrc, ji.addr, m_address); break; case Branch::Call: ji.a->patchCall(realSrc, ji.addr, m_address); break; } } } Label(const Label&) = delete; Label& operator=(const Label&) = delete; void jmp(X64AssemblerBase& a) { addJump(&a, Branch::Jmp); a.jmp(m_address ? m_address : a.frontier()); } void jmp8(X64AssemblerBase& a) { addJump(&a, Branch::Jmp8); a.jmp8(m_address ? m_address : a.frontier()); } void jcc(X64AssemblerBase& a, ConditionCode cc) { addJump(&a, Branch::Jcc); a.jcc(cc, m_address ? m_address : a.frontier()); } void jcc8(X64AssemblerBase& a, ConditionCode cc) { addJump(&a, Branch::Jcc8); a.jcc8(cc, m_address ? m_address : a.frontier()); } void call(X64AssemblerBase& a) { addJump(&a, Branch::Call); a.call(m_address ? m_address : a.frontier()); } void jmpAuto(X64AssemblerBase& a) { assert(m_address); auto delta = m_address - (a.frontier() + 2); if (deltaFits(delta, sz::byte)) { jmp8(a); } else { jmp(a); } } void jccAuto(X64AssemblerBase& a, ConditionCode cc) { assert(m_address); auto delta = m_address - (a.frontier() + 2); if (deltaFits(delta, sz::byte)) { jcc8(a, cc); } else { jcc(a, cc); } } friend void asm_label(X64AssemblerBase& a, Label& l) { assert(!l.m_address && !l.m_a && "Label was already set"); l.m_a = &a; l.m_address = a.frontier(); } private: enum class Branch { Jcc, Jcc8, Jmp, Jmp8, Call }; struct JumpInfo { Branch type; X64AssemblerBase* a; CodeAddress addr; }; private: void addJump(X64AssemblerBase* a, Branch type) { if (m_address) return; JumpInfo info; info.type = type; info.a = a; info.addr = a->codeBlock.frontier(); m_toPatch.push_back(info); } private: X64AssemblerBase* m_a; CodeAddress m_address; std::vector<JumpInfo> m_toPatch; }; inline void X64AssemblerBase::jmp(Label& l) { l.jmp(*this); } inline void X64AssemblerBase::jmp8(Label& l) { l.jmp8(*this); } inline void X64AssemblerBase::jcc(ConditionCode c, Label& l) { l.jcc(*this, c); } inline void X64AssemblerBase::jcc8(ConditionCode c, Label& l) { l.jcc8(*this, c); } inline void X64AssemblerBase::call(Label& l) { l.call(*this); } #define CC(nm, code) \ inline void X64AssemblerBase::j##nm(Label& l) { l.jcc(*this, code); } \ inline void X64AssemblerBase::j##nm##8(Label& l) { l.jcc8(*this, code); } CCS #undef CC ////////////////////////////////////////////////////////////////////// /* * Select the assembler which contains a given address. * * E.g.: * * Asm& a = codeBlockChoose(toPatch, a, acold); * a.patchJmp(...); */ inline CodeBlock& codeBlockChoose(CodeAddress addr) { always_assert_flog(false, "address {} was not part of any known code block", addr); } template<class... Blocks> CodeBlock& codeBlockChoose(CodeAddress addr, CodeBlock& a, Blocks&... as) { if (a.contains(addr)) return a; return codeBlockChoose(addr, as...); } ////////////////////////////////////////////////////////////////////// namespace x64 { struct DecodedInstruction { DecodedInstruction(uint8_t* ip, uint8_t* base) : m_base(base) { decode(ip); } explicit DecodedInstruction(uint8_t* ip) : DecodedInstruction(ip, ip) {} std::string toString(); size_t size() { return m_size; } bool hasPicOffset() const { return m_flags.picOff; } uint8_t* picAddress() const; bool setPicAddress(uint8_t* target); bool hasOffset() const { return m_offSz != 0; } int32_t offset() const; bool hasImmediate() const { return m_immSz; } int64_t immediate() const; bool setImmediate(int64_t value); bool isNop() const; enum BranchType { Conditional = 1, Unconditional = 1 << 1, }; bool isBranch(BranchType branchType = BranchType(Conditional | Unconditional)) const; bool isCall() const; bool isJmp() const; bool isLea() const; bool isFuseable(const DecodedInstruction& next) const; ConditionCode jccCondCode() const; bool shrinkBranch(); void widenBranch(); uint8_t getModRm() const; private: void decode(uint8_t* ip); bool decodePrefix(uint8_t* ip); int decodeRexVexXop(uint8_t* ip); int decodeOpcode(uint8_t* ip); void determineOperandsMap0(uint8_t* ip); void determineOperandsMap1(uint8_t* ip); void determineOperandsMap2(uint8_t* ip); void determineOperandsMap3(uint8_t* ip); int decodeModRm(uint8_t* ip); int decodeImm(uint8_t* ip); // We may wish to decode an instruction whose address is m_ip, but treat all // PIC references as relative to m_base. uint8_t* m_base; uint8_t* m_ip; uint32_t m_size; union { uint32_t m_flagsVal; struct { uint32_t lock : 1; uint32_t repNE : 1; uint32_t rep : 1; uint32_t cs : 1; uint32_t ss : 1; uint32_t ds : 1; uint32_t es : 1; uint32_t fs : 1; uint32_t gs : 1; uint32_t bTaken : 1; uint32_t bNotTaken : 1; uint32_t opndSzOvr : 1; uint32_t addrSzOvr : 1; uint32_t rex : 1; uint32_t vex : 1; uint32_t xop : 1; uint32_t w : 1; uint32_t r : 1; uint32_t x : 1; uint32_t b : 1; uint32_t l : 1; uint32_t def64 : 1; uint32_t immIsAddr : 1; uint32_t picOff : 1; uint32_t hasModRm : 1; uint32_t hasSib : 1; } m_flags; }; uint8_t m_map_select; uint8_t m_xtra_op; uint8_t m_opcode; uint8_t m_immSz; uint8_t m_offSz; }; constexpr DecodedInstruction::BranchType operator|( DecodedInstruction::BranchType a, DecodedInstruction::BranchType b ) { return DecodedInstruction::BranchType((int)a | (int)b); } inline DecodedInstruction::BranchType& operator|=( DecodedInstruction::BranchType& a, const DecodedInstruction::BranchType& b ) { return (a = DecodedInstruction::BranchType((int)a | (int)b)); } #undef TRACEMOD #undef logical_const #undef CCS }}