| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| /** |
| * @author Alexander Astapchuk |
| */ |
| |
| /** |
| * @file |
| * @brief Implementation of Encoder class for IA-32 and Intel 64 platforms. |
| */ |
| #include "enc.h" |
| #include "enc_ia32.h" |
| #include "trace.h" |
| |
| #ifdef _IA32_ |
| #include "enc_prvt.h" |
| #endif |
| |
| namespace Jitrino { |
| namespace Jet { |
| |
| const Opnd eax(i32, virt(RegName_EAX)); |
| const Opnd ecx(i32, virt(RegName_ECX)); |
| const Opnd edx(i32, virt(RegName_EDX)); |
| |
| static OpndSize to_size(jtype jt) |
| { |
| switch (jt) { |
| case i8: return OpndSize_8; |
| case i16: |
| case u16: return OpndSize_16; |
| case i32: return OpndSize_32; |
| case i64: return OpndSize_64; |
| case flt32: return OpndSize_32; |
| case dbl64: return OpndSize_64; |
| default: |
| assert(jt == jretAddr); |
| // fall through to jobj |
| case jobj: |
| assert(sizeof(void*)==4 || sizeof(void*)==8); |
| return sizeof(void*)==4 ? OpndSize_32 : OpndSize_64; |
| } |
| } |
| |
| static const RegName reg_map[] = { |
| RegName_EAX, RegName_EBX, RegName_ECX, RegName_EDX, |
| RegName_ESI, RegName_EDI, |
| #ifdef _EM64T_ |
| RegName_R8D, RegName_R9D, RegName_R10D, RegName_R11D, |
| RegName_R12D, RegName_R13D, RegName_R14D, RegName_R15D, |
| #endif //_EM64T_ |
| RegName_EBP, RegName_ESP |
| }; |
| |
| RegName devirt(AR ar, jtype jt) |
| { |
| RegName reg = RegName_Null; |
| if (ar != gr_x) { // && ar!=ar_total) { |
| //return RegName_Null; |
| assert(COUNTOF(reg_map) == gr_total); |
| unsigned idx = type_idx(ar); |
| if (is_f(ar)) { |
| reg = getRegName(OpndKind_XMMReg, |
| jt == jvoid ? OpndSize_64 : to_size(jt), idx); |
| } |
| else if (ar == fp0) { |
| reg = RegName_FP0; |
| } else { |
| assert(idx<COUNTOF(reg_map)); |
| reg = reg_map[idx]; |
| |
| #if !defined(_EM64T_) && defined(_DEBUG) |
| // On IA32 can not encode the lower 8 bits of the following regs |
| if (equals(reg, RegName_EBP) || equals(reg, RegName_ESI) || |
| equals(reg, RegName_EDI) || equals(reg, RegName_ESP)) { |
| assert(jt != i8); |
| } |
| #endif |
| OpndSize sz; |
| if (jt == jvoid) { |
| #ifdef _EM64T_ |
| sz = OpndSize_64; |
| #else |
| sz = OpndSize_32; |
| #endif |
| } |
| else { |
| sz = to_size(jt); |
| } |
| reg = getAliasReg(reg, sz); |
| } // if !is_f |
| } |
| return reg; |
| } |
| |
| static void check_args(const Opnd& op0, const Opnd& op1) |
| { |
| assert(!op0.is_mem() || !op1.is_mem()); |
| assert(!op0.is_imm()); |
| } |
| |
| static void add_arg(EncoderBase::Operands& args, const Opnd& op, |
| bool can_shrink = false) |
| { |
| OpndSize sz = to_size(op.jt()); |
| if (op.is_reg()) { |
| RegName reg = devirt(op.reg(), op.jt()); |
| //::OpndKind kind = is_f(op.reg()) ? OpndKind_XMMReg : OpndKind_GPReg; |
| //args.add(EncoderBase::Operand(sz, kind, reg)); |
| args.add(EncoderBase::Operand(reg)); |
| } |
| else if (op.is_mem()) { |
| RegName base = op.base() != ar_x ? devirt(op.base()) : RegName_Null; |
| RegName idx = op.index() == ar_x ? RegName_Null : devirt(op.index()); |
| EncoderBase::Operand mem(sz, base, idx, op.scale(), op.disp()); |
| args.add(mem); |
| } |
| else { |
| assert(!is_f(op.jt())); |
| if (op.jt() == i64) { |
| EncoderBase::Operand imm(sz, op.lval()); |
| args.add(imm); |
| } |
| else if (op.jt() == jobj) { |
| EncoderBase::Operand imm(sz, (int_ptr)(void*)op.lval()); |
| args.add(imm); |
| } |
| else { |
| assert(op.jt()<=i32); |
| if (can_shrink && fits_i8(op.ival())) { |
| sz = OpndSize_8; |
| } |
| EncoderBase::Operand imm(sz, op.ival()); |
| args.add(imm); |
| } |
| } |
| } |
| |
| static void add_args(EncoderBase::Operands& args, const Opnd& op) |
| { |
| add_arg(args, op); |
| } |
| |
| static void add_args(EncoderBase::Operands& args, const Opnd& op0, |
| const Opnd& op1) |
| { |
| add_arg(args, op0); |
| add_arg(args, op1); |
| } |
| |
| static void add_args_same_size(EncoderBase::Operands& args, |
| const Opnd& op0, const Opnd& op1) |
| { |
| OpndSize sz = to_size(op0.jt()); |
| assert(to_size(op1.jt()) == sz); |
| if (op0.is_reg()) { |
| RegName reg = devirt(op0.reg(), op0.jt()); |
| args.add(EncoderBase::Operand(reg)); |
| } |
| else if (op0.is_mem()) { |
| RegName base = op0.base() != ar_x ? devirt(op0.base()) : RegName_Null; |
| RegName idx = op0.index() == ar_x ? RegName_Null : devirt(op0.index()); |
| EncoderBase::Operand mem(sz, base, idx, op0.scale(), op0.disp()); |
| args.add(mem); |
| } |
| else { |
| assert(false); |
| } |
| |
| if (op1.is_reg()) { |
| RegName reg = devirt(op1.reg(), op1.jt()); |
| args.add(EncoderBase::Operand(reg)); |
| } |
| else if (op1.is_mem()) { |
| RegName base = op1.base() != ar_x ? devirt(op1.base()) : RegName_Null; |
| RegName idx = op1.index() == ar_x ? RegName_Null : devirt(op1.index()); |
| EncoderBase::Operand mem(sz, base, idx, op1.scale(), op1.disp()); |
| args.add(mem); |
| } |
| else { |
| assert(!is_f(op1.jt())); |
| EncoderBase::Operand imm(sz, (int_ptr)(void*)op1.lval()); |
| args.add(imm); |
| } |
| } |
| |
| AR get_cconv_fr(unsigned i, unsigned pos_in_args) { |
| #ifdef _EM64T_ |
| #ifdef _WIN64 |
| bool compact = false; |
| #else |
| bool compact = true; |
| #endif |
| static const AR frs[] = { |
| virt(RegName_XMM0), virt(RegName_XMM1), |
| virt(RegName_XMM2), virt(RegName_XMM3), |
| #ifndef _WIN64 |
| virt(RegName_XMM4), virt(RegName_XMM5), |
| virt(RegName_XMM6), virt(RegName_XMM7), |
| #endif |
| }; |
| const unsigned count = COUNTOF(frs); |
| unsigned pos = compact ? i : pos_in_args; |
| return (pos<count) ? frs[pos] : fr_x; |
| #else |
| assert(false); |
| return gr_x; |
| #endif |
| } |
| |
| AR get_cconv_gr(unsigned i, unsigned pos_in_args) { |
| #ifdef _EM64T_ |
| #ifdef _WIN64 |
| bool compact = false; |
| static const AR grs[] = { |
| virt(RegName_RCX), virt(RegName_RDX), |
| virt(RegName_R8), virt(RegName_R9), |
| }; |
| #else |
| bool compact = true; |
| static const AR grs[] = { |
| virt(RegName_RDI), virt(RegName_RSI), |
| virt(RegName_RDX), virt(RegName_RCX), |
| virt(RegName_R8), virt(RegName_R9), |
| }; |
| #endif |
| const unsigned count = COUNTOF(grs); |
| unsigned pos = compact ? i : pos_in_args; |
| return (pos<count) ? grs[pos] : gr_x; |
| #else |
| assert(false); |
| return gr_x; |
| #endif |
| } |
| |
| |
| AR virt(RegName reg) |
| { |
| if (getRegKind(reg) == OpndKind_XMMReg) { |
| return _ar(dbl64, getRegIndex(reg)); |
| } |
| assert(getRegKind(reg) == OpndKind_GPReg); |
| for(unsigned i=0; i<COUNTOF(reg_map); i++) { |
| if (equals(reg, reg_map[i])) { |
| return _ar(jobj, i); |
| } |
| } |
| assert(false); |
| return gr_x; |
| } |
| |
| |
| ConditionMnemonic devirt(COND cond) |
| { |
| switch (cond) { |
| case ge: return ConditionMnemonic_GE; |
| case le: return ConditionMnemonic_LE; |
| case gt: return ConditionMnemonic_G; |
| case lt: return ConditionMnemonic_L; |
| |
| case eq: return ConditionMnemonic_Z; |
| case ne: return ConditionMnemonic_NZ; |
| |
| case be : return ConditionMnemonic_BE; |
| case ae : return ConditionMnemonic_AE; |
| case above: return ConditionMnemonic_A; |
| case below: return ConditionMnemonic_B; |
| |
| default: break; |
| } |
| assert(false); |
| return ConditionMnemonic_Count; |
| } |
| |
| InstPrefix devirt(HINT hint) |
| { |
| if (hint==taken) return InstPrefix_HintTaken; |
| if (hint==not_taken) return InstPrefix_HintNotTaken; |
| assert(false); |
| return (InstPrefix)0xCC; |
| } |
| |
| static bool is_callee_save(RegName reg) |
| { |
| if(reg == RegName_Null) { |
| return false; |
| } |
| if (getRegKind(reg) != OpndKind_GPReg) { |
| return false; |
| } |
| assert(getRegKind(reg)==OpndKind_GPReg); |
| if (equals(reg, RegName_EBX)) return true; |
| if (equals(reg, RegName_EBP)) return true; |
| #ifdef _EM64T_ |
| #ifdef _WIN64 |
| if (equals(reg, RegName_RDI)) return true; |
| if (equals(reg, RegName_RSI)) return true; |
| #endif |
| if (equals(reg, RegName_R12)) return true; |
| if (equals(reg, RegName_R13)) return true; |
| if (equals(reg, RegName_R14)) return true; |
| if (equals(reg, RegName_R15)) return true; |
| #else |
| if (equals(reg, RegName_ESI)) return true; |
| if (equals(reg, RegName_EDI)) return true; |
| #endif |
| return false; |
| } |
| |
| static Mnemonic to_mn(jtype jt, ALU alu) |
| { |
| assert(alu_add==0); |
| assert(alu_sub==1); |
| assert(alu_mul==2); |
| assert(alu_div==3); |
| assert(alu_rem==4); |
| assert(alu_or==5); |
| assert(alu_xor==6); |
| assert(alu_and==7); |
| assert(alu_cmp==8); |
| assert(alu_test==9); |
| assert(alu_shl==10); |
| assert(alu_shr==11); |
| assert(alu_sar==12); |
| static const Mnemonic i_map[] = { |
| Mnemonic_ADD, Mnemonic_SUB, |
| Mnemonic_IMUL, Mnemonic_IDIV, Mnemonic_IDIV, |
| Mnemonic_OR, Mnemonic_XOR, Mnemonic_AND, Mnemonic_CMP, Mnemonic_TEST, |
| Mnemonic_SHL, Mnemonic_SHR, Mnemonic_SAR, |
| }; |
| static const Mnemonic s_map[] = { |
| Mnemonic_ADDSS, Mnemonic_SUBSS, |
| Mnemonic_MULSS, Mnemonic_DIVSS, Mnemonic_Null, |
| Mnemonic_Null, Mnemonic_PXOR, Mnemonic_Null, Mnemonic_COMISS, Mnemonic_Null, |
| Mnemonic_Null, Mnemonic_Null, Mnemonic_Null |
| }; |
| static const Mnemonic d_map[] = { |
| Mnemonic_ADDSD, Mnemonic_SUBSD, |
| Mnemonic_MULSD, Mnemonic_DIVSD, Mnemonic_Null, |
| Mnemonic_Null, Mnemonic_PXOR, Mnemonic_Null, Mnemonic_COMISD, Mnemonic_Null, |
| Mnemonic_Null, Mnemonic_Null, Mnemonic_Null |
| }; |
| // all items covered ? |
| assert(COUNTOF(i_map) == alu_count); |
| assert(COUNTOF(i_map) == COUNTOF(s_map)); |
| assert(COUNTOF(i_map) == COUNTOF(d_map)); |
| Mnemonic mn = (jt==dbl64 ? d_map : (jt==flt32 ? s_map : i_map))[alu]; |
| assert(mn != Mnemonic_Null); |
| return mn; |
| } |
| |
| OpndSize to_sz(jtype jt) |
| { |
| if (jt == dbl64 || jt == i64) return OpndSize_64; |
| if (jt == i32 || jt == flt32) return OpndSize_32; |
| if (jt == i16 || jt == u16) return OpndSize_16; |
| if (jt == i8) return OpndSize_8; |
| #ifdef _IA32_ |
| if (jt == jobj) return OpndSize_32; |
| #else |
| if (jt == jobj) return OpndSize_64; |
| #endif |
| assert(false); |
| return OpndSize_Null; |
| } |
| |
| static void emu_fix_opnds(Encoder * enc, |
| Opnd& op0, Opnd& op1, |
| const Opnd& _op0, const Opnd& _op1) |
| { |
| #ifdef _EM64T_ |
| // no op. |
| op0 = _op0; |
| op1 = _op1; |
| if (true) return; |
| #endif |
| op0 = _op0; |
| op1 = _op1; |
| if (!op0.is_reg() && !op1.is_reg()) { |
| return; |
| } |
| if (!(op0.is_reg() && op0.jt() == i8) && |
| !(op1.is_reg() && op1.jt() == i8)) { |
| return; |
| } |
| Opnd& reg_op = op0.is_reg() ? op0 : op1; |
| const Opnd& mem_op = op0.is_reg() ? op1 : op0; |
| |
| RegName reg = devirt(reg_op.reg()); |
| // On IA32 can not encode the lower 8 bits of the following regs |
| if (!equals(reg, RegName_EBP) && !equals(reg, RegName_ESI) && |
| !equals(reg, RegName_EDI)) { |
| assert(!equals(reg, RegName_ESP)); |
| return; |
| } |
| // Try to find a register which is neither a op0 itself, nor it's used |
| // in the op1. |
| RegName r1, r2; |
| if (mem_op.is_reg()) { |
| r1 = devirt(mem_op.reg()); |
| r2 = RegName_Null; |
| } |
| else { |
| assert(mem_op.is_mem()); |
| r1 = devirt(mem_op.base()); |
| r2 = devirt(mem_op.index()); |
| } |
| RegName emu = RegName_EAX; |
| if (equals(reg, emu) || equals(r1, emu) || equals(r2, emu)) { |
| emu = RegName_EDX; |
| if (equals(reg, emu) || equals(r1, emu) || equals(r2, emu)) { |
| emu = RegName_ECX; |
| if (equals(reg, emu) || equals(r1, emu) || equals(r2, emu)) { |
| emu = RegName_EDX; |
| } |
| } |
| } |
| EncoderBase::Operands xargs; |
| xargs.add(emu); |
| xargs.add(reg); |
| enc->ip(EncoderBase::encode(enc->ip(), Mnemonic_XCHG, xargs)); |
| reg_op = Opnd(reg_op.jt(), virt(emu)); |
| } |
| |
| static void emu_unfix_opnds(Encoder * enc, |
| const Opnd& op0, const Opnd& op1, |
| const Opnd& _op0, const Opnd& _op1) |
| { |
| #ifdef _EM64T_ |
| // no op. |
| if (true) return; |
| #endif |
| if (op0 == _op0 && op1 == _op1) { |
| return; |
| } |
| const Opnd& emu_op = op0.is_reg() ? op0 : op1; |
| const Opnd& true_op = op0.is_reg() ? _op0 : _op1; |
| |
| assert(emu_op.is_reg() && true_op.is_reg()); |
| |
| RegName emu = devirt(emu_op.reg()); |
| RegName reg = devirt(true_op.reg()); |
| |
| EncoderBase::Operands xargs; |
| xargs.add(emu); |
| xargs.add(reg); |
| enc->ip(EncoderBase::encode(enc->ip(), Mnemonic_XCHG, xargs)); |
| } |
| |
| bool Encoder::is_callee_save_impl(AR gr) |
| { |
| return Jitrino::Jet::is_callee_save(devirt(gr)); |
| } |
| |
| string Encoder::to_str_impl(AR ar) |
| { |
| RegName reg = devirt(ar); |
| return getRegNameString(reg); |
| } |
| |
| #if defined(_IA32_) |
| static int get_reg_idx(AR ar) |
| { |
| assert(is_gr(ar) && ar != ar_x); |
| return getRegIndex(reg_map[gr_idx(ar)]); |
| } |
| |
| |
| /** |
| * Generates (encodes) memory operand. |
| * |
| * Kind of copy of EncoderBase::encodeModRM. |
| */ |
| static char* do_mem(char* buf, const Opnd& op) |
| { |
| assert(op.is_mem()); |
| |
| ModRM& modrm = *(ModRM*)buf; |
| ++buf; |
| SIB& sib = *(SIB*)buf; |
| AR base = op.base(); |
| bool haveSib = false; |
| haveSib = haveSib || (sp == base); |
| haveSib = haveSib || (op.index() != ar_x); |
| |
| if (haveSib) { |
| ++buf; |
| } |
| if ((op.disp() == 0 || base == ar_x) && base != bp) { |
| modrm.mod = 0; |
| modrm.rm = haveSib ? 4 : (base==ar_x ? 5 : get_reg_idx(base)); |
| if (base == ar_x) { |
| *(int*)buf = op.disp(); |
| buf += 4; |
| } |
| } |
| else if (fits_i8(op.disp())) { |
| modrm.mod = 1; |
| modrm.rm = haveSib ? 4 : get_reg_idx(base); |
| *buf = (char)op.disp(); |
| buf += 1; |
| } |
| else { |
| modrm.mod = 2; |
| modrm.rm = haveSib ? 4 : get_reg_idx(base); |
| *(int*)buf = op.disp(); |
| buf += 4; |
| } |
| if (haveSib) { |
| sib.base = base == ar_x ? 5 : get_reg_idx(base); |
| sib.index = op.index() == ar_x ? 4 : get_reg_idx(op.index()); |
| if (op.index() != ar_x) { |
| if (op.scale() == 1) sib.scale = 0; |
| else if (op.scale() == 2) sib.scale = 1; |
| else if (op.scale() == 4) sib.scale = 2; |
| else sib.scale = 3; |
| } |
| else { sib.scale = 0; } |
| } |
| return buf; |
| } |
| |
| static void do_reg(char* buf, const Opnd& op) |
| { |
| assert(op.is_reg()); |
| RegName reg = devirt(op.reg(), op.jt()); |
| ((ModRM*)buf)->reg = getRegIndex(reg); |
| } |
| |
| #endif // ifdef _IA32_ |
| |
| void Encoder::mov_impl(const Opnd& _op0, const Opnd& _op1) |
| { |
| #if defined(_IA32_) |
| // |
| // mov_impl() and its calls to add_arg() are the hottest methods on |
| // client startups, especially with the massive ones like Eclipse. |
| // Here is a simple, but very useful optimization - we do not call |
| // to regular encoding engine, but rather generate instructions right |
| // here, in-place. We do this only for the most possible variants, |
| // that cover about 80-90% of all mov instructions. |
| // On Eclipse startups this removes about 1'000'000 trips to Encoder |
| // and related stuff. |
| // |
| if (_op0.jt() == _op1.jt() && (_op0.jt() == i32 || _op0.jt() == jobj)) { |
| char* p = ip(); |
| char * saveP = p; |
| #ifdef _DEBUG |
| char dbgbuff[20]; |
| unsigned dbglen; |
| char * dbgSave = saveP; |
| { |
| EncoderBase::Operands args; |
| add_args(args, _op0); |
| add_args(args, _op1); |
| char * p = EncoderBase::encode(dbgbuff, Mnemonic_MOV, args); |
| dbglen = p-dbgbuff; |
| } |
| #endif |
| if (_op0.is_mem() && _op1.is_imm() && (p=do_mem(p+1, _op0)) != NULL) { |
| // mov mem, imm: C7 /0 |
| *(unsigned char*)saveP = 0xC7; |
| ((ModRM*)(saveP+1))->reg = 0; |
| *(int*)p = _op1.ival(); |
| assert(!memcmp(dbgbuff, dbgSave, dbglen)); |
| ip(p+4); |
| return; |
| } |
| else if (_op0.is_reg() && _op1.is_mem() && (p=do_mem(p+1, _op1)) != NULL) { |
| // mov reg, mem: 8B /r |
| *(unsigned char*)saveP = 0x8B; |
| ++saveP; |
| do_reg(saveP, _op0); |
| assert(!memcmp(dbgbuff, dbgSave, dbglen)); |
| ip(p); |
| return; |
| } |
| else if (_op1.is_reg() && _op0.is_mem() && (p=do_mem(p+1, _op0)) != NULL) { |
| // mov mem, reg: 89 /r |
| *(unsigned char*)saveP = 0x89; |
| ++saveP; |
| do_reg(saveP, _op1); |
| assert(!memcmp(dbgbuff, dbgSave, dbglen)); |
| ip(p); |
| return; |
| } |
| } |
| #endif |
| EncoderBase::Operands args; |
| |
| assert(_op0.reg() != fp0 && _op1.reg() != fp0); |
| assert(is_f(_op0.jt()) == is_f(_op1.jt())); |
| |
| #ifdef _EM64T_ |
| // A special case on EM64T - emulation of 'mov mem64, imm64' |
| if (_op0.is_mem() && _op1.is_imm() && |
| (_op1.jt() == i64 || _op1.jt() == jobj)) { |
| // if _op1 fits into 32 bits, then we use a single |
| // 'MOV mem64, imm32' which sign-extend operand - for i64. |
| // Otherwise we generate 2 moves. |
| assert(_op0.jt() == i64 || _op0.jt() == jobj); |
| if (fits32(_op1.lval()) && _op0.jt() == i64) { |
| add_arg(args, _op0); |
| add_arg(args, Opnd(_op1.ival())); |
| ip(EncoderBase::encode(ip(), Mnemonic_MOV, args)); |
| } |
| else { |
| RegName base = devirt(_op0.base()); |
| RegName index = devirt(_op0.index()); |
| int disp = _op0.disp(); |
| unsigned scale = _op0.scale(); |
| const OpndSize sz = OpndSize_32; |
| EncoderBase::Operand mem_lo(sz, base, index, scale, disp); |
| |
| args.add(mem_lo); |
| add_arg(args, Opnd(lo32(_op1.lval()))); |
| ip(EncoderBase::encode(ip(), Mnemonic_MOV, args)); |
| |
| args.clear(); |
| disp += 4; |
| EncoderBase::Operand mem_hi(sz, base, index, scale, disp); |
| args.add(mem_hi); |
| add_arg(args, Opnd(hi32(_op1.lval()))); |
| ip(EncoderBase::encode(ip(), Mnemonic_MOV, args)); |
| } |
| return; |
| } |
| #endif |
| Opnd op0, op1; |
| emu_fix_opnds(this, op0, op1, _op0, _op1); |
| if (op0.jt() == op1.jt()) { |
| add_args_same_size(args, op0, op1); |
| } |
| else { |
| add_args(args, op0); |
| add_args(args, op1); |
| } |
| Mnemonic mn = |
| op0.jt() == flt32 ? Mnemonic_MOVSS : |
| (op0.jt() == dbl64 ? Mnemonic_MOVQ : Mnemonic_MOV); |
| ip(EncoderBase::encode(ip(), mn, args)); |
| emu_unfix_opnds(this, op0, op1, _op0, _op1); |
| } |
| |
| void Encoder::not_impl(const Opnd& op0) |
| { |
| Mnemonic mn = Mnemonic_NOT; |
| EncoderBase::Operands args; |
| add_arg(args, op0, false); |
| ip(EncoderBase::encode(ip(), mn, args)); |
| } |
| |
| void Encoder::alu_impl(ALU alu, const Opnd& op0, const Opnd& op1) |
| { |
| Mnemonic mn = to_mn(op0.jt(), alu); |
| EncoderBase::Operands args; |
| add_arg(args, op0, false);//devirt(op0, OpndSize_32)); |
| // For alu_test can not shrink imm32 to imm8. |
| add_arg(args, Opnd(op1), alu != alu_test); |
| ip(EncoderBase::encode(ip(), mn, args)); |
| } |
| |
| void Encoder::nop_impl(U_32 n) |
| { |
| ip(EncoderBase::nops(ip(), n)); |
| } |
| |
| |
| void Encoder::cmovcc_impl(COND c, const Opnd& op0, const Opnd& op1) |
| { |
| ConditionMnemonic cm = devirt(c); |
| Mnemonic mn = (Mnemonic)(Mnemonic_CMOVcc + cm); |
| EncoderBase::Operands args; |
| add_args(args, op0); |
| add_args(args, op1); |
| ip(EncoderBase::encode(ip(), mn, args)); |
| } |
| |
| //TODO: reuse the same func for all XCHG ops in this file |
| static void xchg_regs(Encoder * enc, RegName reg1, RegName reg2) { |
| EncoderBase::Operands xargs; |
| xargs.add(reg1); |
| xargs.add(reg2); |
| enc->ip(EncoderBase::encode(enc->ip(), Mnemonic_XCHG, xargs)); |
| } |
| |
| void Encoder::cmpxchg_impl(bool lockPrefix, AR addrReg, AR newReg, AR oldReg) { |
| RegName dNewReg = devirt(newReg); |
| RegName dOldReg = devirt(oldReg); |
| RegName dAddrReg = devirt(addrReg); |
| bool eaxFix = dOldReg != RegName_EAX; |
| if (eaxFix) { |
| if (dAddrReg == RegName_EAX) { |
| dAddrReg = dOldReg; |
| } else if (dNewReg == RegName_EAX) { |
| dNewReg = dOldReg; |
| } |
| xchg_regs(this, dOldReg, RegName_EAX); |
| } |
| |
| if (lockPrefix) { |
| ip(EncoderBase::prefix(ip(), InstPrefix_LOCK)); |
| } |
| |
| EncoderBase::Operands args; |
| args.add(EncoderBase::Operand(OpndSize_32, dAddrReg, 0)); //TODO: EM64t fix! |
| args.add(dNewReg); |
| args.add(RegName_EAX); |
| ip(EncoderBase::encode(ip(), Mnemonic_CMPXCHG, args)); |
| |
| if (eaxFix) { |
| xchg_regs(this, RegName_EAX, devirt(oldReg)); |
| } |
| |
| } |
| |
| static void mov_regs(Encoder* enc, RegName r_dst, RegName r_src) { |
| EncoderBase::Operands args; |
| args.add(r_dst); |
| args.add(r_src); |
| enc->ip(EncoderBase::encode(enc->ip(), Mnemonic_MOV, args)); |
| } |
| |
| void Encoder::volatile64_op_impl(Opnd& where, AR hi_part, AR lo_part, bool is_put) { |
| int regSize=sizeof(void*); |
| RegName regs[] = {RegName_EAX, RegName_EBX, RegName_ECX, RegName_EDX, RegName_ESI}; |
| {//free EAX,EBX,ECX,EDX and ESI registers to use cmpxchg8b |
| { //SUB ESP 20 -> protect spill area from OS |
| EncoderBase::Operands args; |
| args.add(RegName_ESP); |
| args.add(EncoderBase::Operand(OpndSize_32, (long long)5*regSize)); |
| ip(EncoderBase::encode(ip(), Mnemonic_SUB, args)); |
| } |
| for (int i=0;i<5;i++) { |
| EncoderBase::Operands args; |
| args.add(EncoderBase::Operand(OpndSize_32, RegName_ESP, i*regSize)); |
| args.add(regs[i]); |
| ip(EncoderBase::encode(ip(), Mnemonic_MOV, args)); |
| } |
| } |
| |
| //load address to ESI |
| lea_impl(virt(RegName_ESI), where); |
| |
| |
| |
| RegName r_hi = devirt(hi_part); |
| RegName r_lo = devirt(lo_part); |
| |
| if (is_put) { |
| //load value to ECX/EBX. |
| RegName r_lo2=r_lo; |
| if (r_lo2 == RegName_ECX) { |
| RegName r_free = r_lo2!=RegName_EAX ? RegName_EAX : RegName_EDX; |
| mov_regs(this, r_free, r_lo2); |
| r_lo2 = r_free; |
| } |
| mov_regs(this, RegName_ECX, r_hi); |
| mov_regs(this, RegName_EBX, r_lo2); |
| } |
| |
| //3. set lock prefix |
| unsigned loop_ip = ipoff(); |
| ip(EncoderBase::prefix(ip(), InstPrefix_LOCK)); |
| |
| //do cmpxchg8b |
| { |
| EncoderBase::Operands args; |
| args.add(EncoderBase::Operand(OpndSize_64, RegName_ESI, 0)); |
| ip(EncoderBase::encode(ip(), Mnemonic_CMPXCHG8B, args)); |
| } |
| |
| if (is_put) { |
| //loop until not successful |
| unsigned br_off = br(nz, 0, 0); |
| patch(br_off, ip(loop_ip)); |
| } else { |
| //store result of get to lo_part and hi_part |
| RegName lo_res = RegName_EAX; |
| if (r_hi == lo_res) { |
| mov_regs(this, RegName_EBX, lo_res); |
| lo_res = RegName_EBX; |
| } |
| mov_regs(this, r_hi, RegName_EDX); |
| mov_regs(this, r_lo, lo_res); |
| } |
| |
| //restore initial regs values |
| { |
| for (int i=0;i<5;i++) { |
| RegName r = regs[i]; |
| if (!is_put && (r == r_hi || r == r_lo)) { |
| continue; |
| } |
| EncoderBase::Operands args; |
| args.add(regs[i]); |
| args.add(EncoderBase::Operand(OpndSize_32, RegName_ESP, i*regSize)); |
| ip(EncoderBase::encode(ip(), Mnemonic_MOV, args)); |
| } |
| { //ADD ESP 20 -> restore ESP |
| EncoderBase::Operands args; |
| args.add(RegName_ESP); |
| args.add(EncoderBase::Operand(OpndSize_32, (long long)5*regSize)); |
| ip(EncoderBase::encode(ip(), Mnemonic_ADD, args)); |
| } |
| |
| } |
| } |
| |
| void Encoder::lea_impl(const Opnd& reg, const Opnd& mem) |
| { |
| EncoderBase::Operands args; |
| add_args(args, reg); |
| add_args(args, mem); |
| ip(EncoderBase::encode(ip(), Mnemonic_LEA, args)); |
| } |
| |
| void Encoder::movp_impl(AR op0, const void *op1) |
| { |
| EncoderBase::Operands args; |
| args.add(devirt(op0)); |
| #ifdef _EM64T_ |
| args.add(EncoderBase::Operand(OpndSize_64, (int_ptr)op1)); |
| #else |
| args.add(EncoderBase::Operand(OpndSize_32, (int_ptr)op1)); |
| #endif |
| ip(EncoderBase::encode(ip(), Mnemonic_MOV, args)); |
| } |
| |
| void Encoder::sx1_impl(const Opnd& _op0, const Opnd& _op1) |
| { |
| check_args(_op0, _op1); |
| Opnd op0, op1; |
| emu_fix_opnds(this, op0, op1, _op0, _op1); |
| EncoderBase::Operands args; |
| add_args(args, op0, op1); |
| ip(EncoderBase::encode(ip(), Mnemonic_MOVSX, args)); |
| emu_unfix_opnds(this, op0, op1, _op0, _op1); |
| } |
| |
| void Encoder::sx2_impl(const Opnd& op0, const Opnd& op1) |
| { |
| check_args(op0, op1); |
| EncoderBase::Operands args; |
| add_args(args, op0, op1); |
| ip(EncoderBase::encode(ip(), Mnemonic_MOVSX, args)); |
| } |
| |
| void Encoder::sx_impl(const Opnd& op0, const Opnd& op1) |
| { |
| check_args(op0, op1); |
| if (op1.jt() == i8) { |
| sx1(op0, op1); |
| } |
| else if (op1.jt() == i16 || op1.jt() == u16) { |
| sx2(op0, op1); |
| } |
| else { |
| EncoderBase::Operands args; |
| add_args(args, op0, op1); |
| ip(EncoderBase::encode(ip(), Mnemonic_MOVSX, args)); |
| } |
| } |
| |
| void Encoder::zx1_impl(const Opnd& _op0, const Opnd& _op1) |
| { |
| check_args(_op0, _op1); |
| Opnd op0, op1; |
| emu_fix_opnds(this, op0, op1, _op0, _op1); |
| EncoderBase::Operands args; |
| add_args(args, op0, op1); |
| ip(EncoderBase::encode(ip(), Mnemonic_MOVZX, args)); |
| emu_unfix_opnds(this, op0, op1, _op0, _op1); |
| } |
| |
| void Encoder::zx2_impl(const Opnd& op0, const Opnd& op1) |
| { |
| check_args(op0, op1); |
| EncoderBase::Operands args; |
| add_args(args, op0, op1); |
| ip(EncoderBase::encode(ip(), Mnemonic_MOVZX, args)); |
| } |
| |
| void Encoder::fld_impl(jtype jt, AR op0, AR base, int disp, AR index, unsigned scale) |
| { |
| EncoderBase::Operands args; |
| Mnemonic mn = jt == dbl64 ? Mnemonic_MOVSD : Mnemonic_MOVSS; |
| OpndSize sz = jt == dbl64 ? OpndSize_64 : OpndSize_32; |
| if (op0 == fp0) { |
| mn = Mnemonic_FLD; |
| args.add(jt == dbl64 ? RegName_FP0D : RegName_FP0S); |
| } |
| else { |
| args.add(devirt(op0, jt)); |
| } |
| args.add(EncoderBase::Operand(sz, devirt(base), devirt(index), scale, disp)); |
| ip(EncoderBase::encode(ip(), mn, args)); |
| } |
| |
| void Encoder::fst_impl(jtype jt, AR op0, AR base, int disp, AR index, |
| unsigned scale) |
| { |
| EncoderBase::Operands args; |
| OpndSize sz = jt == dbl64 ? OpndSize_64 : OpndSize_32; |
| Mnemonic mn = jt == dbl64 ? Mnemonic_MOVSD : Mnemonic_MOVSS; |
| args.add(EncoderBase::Operand(sz, devirt(base), devirt(index), scale, disp)); |
| if (op0 == fp0) { |
| mn = Mnemonic_FSTP; |
| args.add(jt == dbl64 ? RegName_FP0D : RegName_FP0S); |
| } |
| else { |
| args.add(devirt(op0, jt)); |
| } |
| ip(EncoderBase::encode(ip(), mn, args)); |
| } |
| |
| int Encoder::push_impl(const Opnd& op0) |
| { |
| //assert(!is_f(gr)); |
| EncoderBase::Operands args; |
| add_args(args, op0); |
| ip(EncoderBase::encode(ip(), Mnemonic_PUSH, args)); |
| return STACK_SLOT_SIZE; |
| } |
| |
| int Encoder::pop_impl(const Opnd& op0) |
| { |
| //assert(!is_f(gr)); |
| EncoderBase::Operands args; |
| add_args(args, op0); |
| ip(EncoderBase::encode(ip(), Mnemonic_POP, args)); |
| return STACK_SLOT_SIZE; |
| } |
| |
| void Encoder::call_impl(const Opnd& target) |
| { |
| EncoderBase::Operands args; |
| add_args(args, target); |
| ip(EncoderBase::encode(ip(), Mnemonic_CALL, args)); |
| } |
| |
| void Encoder::ret_impl(unsigned pop) |
| { |
| EncoderBase::Operands args; |
| if (pop != 0) { |
| assert(fits_i16(pop)); |
| args.add(EncoderBase::Operand(OpndSize_16, pop)); |
| } |
| ip(EncoderBase::encode(ip(), Mnemonic_RET, args)); |
| } |
| |
| void Encoder::br_impl(COND cond, HINT hint) |
| { |
| if (hint != hint_none) { |
| // Hints are only allowed for conditional branches |
| assert(cond != cond_none); |
| ip(EncoderBase::prefix(ip(), devirt(hint))); |
| } |
| EncoderBase::Operands args(0); |
| Mnemonic mn; |
| if (cond == cond_none) { |
| mn = Mnemonic_JMP; |
| } |
| else { |
| mn = (Mnemonic)(Mnemonic_Jcc+devirt(cond)); |
| } |
| ip(EncoderBase::encode(ip(), mn, args)); |
| } |
| |
| void Encoder::br_impl(const Opnd& op, COND cond, HINT hint) |
| { |
| // Conditional indirect branches are not supported on IA32/EM64T |
| assert(cond==cond_none); |
| // makes no sense to have hint with unconditional branch |
| assert(hint==hint_none); |
| EncoderBase::Operands args; |
| add_args(args, op); |
| ip(EncoderBase::encode(ip(), Mnemonic_JMP, args)); |
| } |
| |
| void Encoder::trap_impl(void) |
| { |
| ip(EncoderBase::encode(ip(), Mnemonic_INT3, EncoderBase::Operands())); |
| } |
| |
| } |
| }; // ~namespace Jitrino::Jet |