blob: 8f6f343946d2aa82709fc2e9ec6f232cb0f2d86c [file] [log] [blame]
/*
* 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