blob: f5acfe58a1334ad33fc893630e98b81e8812dbb0 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft Corporation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "Backend.h"
#include "X64Encode.h"
static const BYTE OpcodeByte2[]={
#define MACRO(name, jnLayout, attrib, byte2, ...) byte2,
#include "MdOpCodes.h"
#undef MACRO
};
struct FormTemplate{ BYTE form[6]; };
#define f(form) TEMPLATE_FORM_ ## form
static const struct FormTemplate OpcodeFormTemplate[] =
{
#define MACRO(name, jnLayout, attrib, byte2, form, ...) form ,
#include "MdOpCodes.h"
#undef MACRO
};
#undef f
struct OpbyteTemplate { byte opbyte[6]; };
static const struct OpbyteTemplate Opbyte[] =
{
#define MACRO(name, jnLayout, attrib, byte2, form, opbyte, ...) opbyte,
#include "MdOpCodes.h"
#undef MACRO
};
static const uint32 Opdope[] =
{
#define MACRO(name, jnLayout, attrib, byte2, form, opbyte, dope, ...) dope,
#include "MdOpCodes.h"
#undef MACRO
};
static const BYTE OpEncoding[] =
{
#define REGDAT(name, dispName, encoding, ...) encoding ,
#include "RegList.h"
#undef MACRO
};
static const uint32 OpcodeLeadIn[] =
{
#define MACRO(name, jnLayout, attrib, byte2, form, opByte, dope, leadIn, ...) leadIn,
#include "MdOpCodes.h"
#undef MACRO
};
static const enum Forms OpcodeForms[] =
{
#define MACRO(name, jnLayout, attrib, byte2, form, ...) form,
#include "MdOpCodes.h"
#undef MACRO
};
static const BYTE Nop1[] = { 0x90 }; /* nop */
static const BYTE Nop2[] = { 0x66, 0x90 }; /* xchg ax, ax */
static const BYTE Nop3[] = { 0x0F, 0x1F, 0x00 }; /* nop dword ptr [rax] */
static const BYTE Nop4[] = { 0x0F, 0x1F, 0x40, 0x00 }; /* nop dword ptr [rax + 0] */
static const BYTE * const Nop[4] = { Nop1, Nop2, Nop3, Nop4 };
enum CMP_IMM8
{
EQ,
LT,
LE,
UNORD,
NEQ,
NLT,
NLE,
ORD
};
///----------------------------------------------------------------------------
///
/// EncoderMD::Init
///
///----------------------------------------------------------------------------
void
EncoderMD::Init(Encoder *encoder)
{
m_encoder = encoder;
m_relocList = nullptr;
m_lastLoopLabelPosition = -1;
}
///----------------------------------------------------------------------------
///
/// EncoderMD::GetOpcodeByte2
///
/// Get the second byte encoding of the given instr.
///
///----------------------------------------------------------------------------
const BYTE
EncoderMD::GetOpcodeByte2(IR::Instr *instr)
{
return OpcodeByte2[instr->m_opcode - (Js::OpCode::MDStart+1)];
}
///----------------------------------------------------------------------------
///
/// EncoderMD::GetInstrForm
///
/// Get the form list of the given instruction. The form list contains
/// the possible encoding forms of an instruction.
///
///----------------------------------------------------------------------------
Forms
EncoderMD::GetInstrForm(IR::Instr *instr)
{
return OpcodeForms[instr->m_opcode - (Js::OpCode::MDStart + 1)];
}
///----------------------------------------------------------------------------
///
/// EncoderMD::GetFormTemplate
///
/// Get the form list of the given instruction. The form list contains
/// the possible encoding forms of an instruction.
///
///----------------------------------------------------------------------------
const BYTE *
EncoderMD::GetFormTemplate(IR::Instr *instr)
{
return OpcodeFormTemplate[instr->m_opcode - (Js::OpCode::MDStart + 1)].form;
}
///----------------------------------------------------------------------------
///
/// EncoderMD::GetOpbyte
///
/// Get the first byte opcode of an instr.
///
///----------------------------------------------------------------------------
const BYTE *
EncoderMD::GetOpbyte(IR::Instr *instr)
{
return Opbyte[instr->m_opcode - (Js::OpCode::MDStart+1)].opbyte;
}
///----------------------------------------------------------------------------
///
/// EncoderMD::GetRegEncode
///
/// Get the amd64 encoding of a given register.
///
///----------------------------------------------------------------------------
const BYTE
EncoderMD::GetRegEncode(IR::RegOpnd *regOpnd)
{
return this->GetRegEncode(regOpnd->GetReg());
}
const BYTE
EncoderMD::GetRegEncode(RegNum reg)
{
AssertMsg(reg != RegNOREG, "should have valid reg in encoder");
//
// Instead of lookup. We can also AND with 0x7.
//
return (BYTE)(OpEncoding[1 + reg - RegRAX]);
}
///----------------------------------------------------------------------------
///
/// EncoderMD::GetOpdope
///
/// Get the dope vector of a particular instr. The dope vector describes
/// certain properties of an instr.
///
///----------------------------------------------------------------------------
const uint32
EncoderMD::GetOpdope(IR::Instr *instr)
{
return Opdope[instr->m_opcode - (Js::OpCode::MDStart+1)];
}
///----------------------------------------------------------------------------
///
/// EncoderMD::GetLeadIn
///
/// Get the leadin of a particular instr.
///
///----------------------------------------------------------------------------
const uint32
EncoderMD::GetLeadIn(IR::Instr * instr)
{
return OpcodeLeadIn[instr->m_opcode - (Js::OpCode::MDStart+1)];
}
///----------------------------------------------------------------------------
///
/// EncoderMD::FitsInByte
///
/// Can we encode this value into a signed extended byte?
///
///----------------------------------------------------------------------------
bool
EncoderMD::FitsInByte(size_t value)
{
return ((size_t)(signed char)(value & 0xFF) == value);
}
///----------------------------------------------------------------------------
///
/// EncoderMD::GetMod
///
/// Get the "MOD" part of a MODRM encoding for a given operand.
///
///----------------------------------------------------------------------------
BYTE
EncoderMD::GetMod(IR::IndirOpnd * indirOpnd, int* pDispSize)
{
RegNum reg = indirOpnd->GetBaseOpnd()->GetReg();
return GetMod(indirOpnd->GetOffset(), (reg == RegR13 || reg == RegRBP), pDispSize);
}
BYTE
EncoderMD::GetMod(IR::SymOpnd * symOpnd, int * pDispSize, RegNum& rmReg)
{
StackSym * stackSym = symOpnd->m_sym->AsStackSym();
int32 offset = stackSym->m_offset;
rmReg = RegRBP;
if (stackSym->IsArgSlotSym() && !stackSym->m_isOrphanedArg)
{
if (stackSym->m_isInlinedArgSlot)
{
Assert(offset >= 0);
offset -= this->m_func->m_localStackHeight;
stackSym->m_offset = offset;
stackSym->m_allocated = true;
}
else
{
rmReg = RegRSP;
}
}
else
{
Assert(offset != 0);
}
return GetMod((size_t)offset + (size_t)symOpnd->m_offset, rmReg == RegRBP, pDispSize);
}
BYTE
EncoderMD::GetMod(size_t offset, bool regIsRbpOrR13, int * pDispSize)
{
if (offset == 0 && !regIsRbpOrR13)
{
*(pDispSize) = 0;
return Mod00;
}
else if (this->FitsInByte(offset))
{
*(pDispSize) = 1;
return Mod01;
}
else if(Math::FitsInDWord(offset))
{
*(pDispSize) = 4;
return Mod10;
}
else
{
AssertMsg(0, "Cannot encode offsets more than 32 bits in MODRM");
return 0xff;
}
}
///----------------------------------------------------------------------------
///
/// EncoderMD::EmitModRM
///
/// Emit an effective address using the MODRM byte.
///
///----------------------------------------------------------------------------
BYTE
EncoderMD::EmitModRM(IR::Instr * instr, IR::Opnd *opnd, BYTE reg1)
{
int dispSize = -1; // Initialize to suppress C4701 false positive
IR::IndirOpnd *indirOpnd;
IR::RegOpnd *regOpnd;
IR::RegOpnd *baseOpnd;
IR::RegOpnd *indexOpnd;
BYTE reg;
BYTE regBase;
BYTE regIndex;
BYTE baseRegEncode;
BYTE rexEncoding = 0;
AssertMsg( (reg1 & 7) == reg1, "Invalid reg1");
reg1 = (reg1 & 7) << 3; // mask and put in reg field
switch (opnd->GetKind())
{
case IR::OpndKindReg:
regOpnd = opnd->AsRegOpnd();
AssertMsg(regOpnd->GetReg() != RegNOREG, "All regOpnd should have a valid reg set during encoder");
reg = this->GetRegEncode(regOpnd);
this->EmitConst((Mod11 | reg1 | reg), 1);
if (this->IsExtendedRegister(regOpnd->GetReg()))
{
return REXB;
}
else
{
return 0;
}
case IR::OpndKindSym:
AssertMsg(opnd->AsSymOpnd()->m_sym->IsStackSym(), "Should only see stackSym syms in encoder.");
BYTE byte;
RegNum rmReg;
BYTE mod;
mod = this->GetMod(opnd->AsSymOpnd(), &dispSize, rmReg);
baseRegEncode = this->GetRegEncode(rmReg);
byte = (BYTE)(mod | reg1 | baseRegEncode);
*(m_pc++) = byte;
if (rmReg == RegRSP)
{
byte = (BYTE)(((baseRegEncode & 7) << 3) | (baseRegEncode & 7));
*(m_pc++) = byte;
}
else
{
AssertMsg(opnd->AsSymOpnd()->m_sym->AsStackSym()->m_offset, "Expected stackSym offset to be set.");
}
break;
case IR::OpndKindIndir:
indirOpnd = opnd->AsIndirOpnd();
baseOpnd = indirOpnd->GetBaseOpnd();
indexOpnd = indirOpnd->GetIndexOpnd();
AssertMsg(!indexOpnd || indexOpnd->GetReg() != RegRSP, "ESP cannot be the index of an indir.");
if (baseOpnd == nullptr)
{
Assert(indexOpnd != nullptr);
regIndex = this->GetRegEncode(indexOpnd);
dispSize = 4;
*(m_pc++) = ( Mod00 | reg1 | 0x4);
*(m_pc++) = (((indirOpnd->GetScale() & 3) << 6) | ((regIndex & 7) << 3) | 0x5);
rexEncoding |= this->GetRexByte(this->REXX, indexOpnd);
}
else
{
regBase = this->GetRegEncode(baseOpnd);
if (indexOpnd != nullptr)
{
regIndex = this->GetRegEncode(indexOpnd);
*(m_pc++) = (this->GetMod(indirOpnd, &dispSize) | reg1 | 0x4);
*(m_pc++) = (((indirOpnd->GetScale() & 3) << 6) | ((regIndex & 7) << 3) | (regBase & 7));
rexEncoding |= this->GetRexByte(this->REXX, indexOpnd);
rexEncoding |= this->GetRexByte(this->REXB, baseOpnd);
}
else if (baseOpnd->GetReg() == RegR12 || baseOpnd->GetReg() == RegRSP)
{
//
// Using RSP/R12 as base requires the SIB byte even where there is no index.
//
*(m_pc++) = (this->GetMod(indirOpnd, &dispSize) | reg1 | regBase);
*(m_pc++) = (BYTE)(((regBase & 7) << 3) | (regBase & 7));
rexEncoding |= this->GetRexByte(this->REXB, baseOpnd);
}
else
{
*(m_pc++) = (this->GetMod(indirOpnd, &dispSize) | reg1 | regBase);
rexEncoding |= this->GetRexByte(this->REXB, baseOpnd);
}
}
break;
case IR::OpndKindMemRef:
*(m_pc++) = (char)(reg1 | 0x4);
*(m_pc++) = 0x25; // SIB displacement
AssertMsg(Math::FitsInDWord((size_t)opnd->AsMemRefOpnd()->GetMemLoc()), "Size overflow");
dispSize = 4;
break;
default:
AssertMsg(UNREACHED, "Unexpected operand kind");
break;
}
AssertMsg(dispSize != -1, "Uninitialized dispSize");
BYTE retval = this->EmitImmed(opnd, dispSize, 0);
AssertMsg(retval == 0, "Not possible.");
return rexEncoding;
}
///----------------------------------------------------------------------------
///
/// EncoderMD::EmitConst
///
/// Emit a constant of the given size.
///
///----------------------------------------------------------------------------
void
EncoderMD::EmitConst(size_t val, int size, bool allowImm64 /* = false */)
{
AssertMsg(allowImm64 || size != 8, "Invalid size of immediate. It can only be 8 for certain instructions, MOV being the most popular");
switch (size)
{
case 0:
return;
case 1:
*(uint8*)m_pc = (char)val;
break;
case 2:
*(uint16*)m_pc = (short)val;
break;
case 4:
*(uint32*)m_pc = (uint32)val;
break;
case 8:
*(uint64*)m_pc = (uint64)val;
break;
default:
AssertMsg(UNREACHED, "Unexpected size");
}
m_pc += size;
}
///----------------------------------------------------------------------------
///
/// EncoderMD::EmitImmed
///
/// Emit the immediate value of the given operand. It returns 0x2 for a
/// sbit encoding, 0 otherwise.
///
///----------------------------------------------------------------------------
BYTE
EncoderMD::EmitImmed(IR::Opnd * opnd, int opSize, int sbit, bool allow64Immediates)
{
StackSym *stackSym = nullptr;
BYTE retval = 0;
size_t value = 0;
switch (opnd->GetKind())
{
case IR::OpndKindInt64Const:
value = (size_t)opnd->AsInt64ConstOpnd()->GetValue();
goto intConst;
case IR::OpndKindAddr:
value = (size_t)opnd->AsAddrOpnd()->m_address;
goto intConst;
case IR::OpndKindIntConst:
value = opnd->AsIntConstOpnd()->GetValue();
intConst:
if (sbit && opSize > 1 && this->FitsInByte(value))
{
opSize = 1;
retval = 0x2; /* set S bit */
}
break;
case IR::OpndKindSym:
AssertMsg(opnd->AsSymOpnd()->m_sym->IsStackSym(), "Should only see stackSym here");
stackSym = opnd->AsSymOpnd()->m_sym->AsStackSym();
value = stackSym->m_offset + opnd->AsSymOpnd()->m_offset;
break;
case IR::OpndKindHelperCall:
AssertMsg(this->GetOpndSize(opnd) == 8, "HelperCall opnd must be 64 bit");
value = (size_t)IR::GetMethodAddress(m_func->GetThreadContextInfo(), opnd->AsHelperCallOpnd());
break;
case IR::OpndKindIndir:
value = opnd->AsIndirOpnd()->GetOffset();
break;
case IR::OpndKindMemRef:
value = (size_t)opnd->AsMemRefOpnd()->GetMemLoc();
break;
case IR::OpndKindLabel:
value = 0;
AppendRelocEntry(RelocTypeLabelUse, (void*) m_pc, opnd->AsLabelOpnd()->GetLabel());
break;
default:
AssertMsg(UNREACHED, "Unimplemented...");
}
if (!allow64Immediates && opSize == 8)
{
Assert(Math::FitsInDWord(value));
opSize = 4;
}
this->EmitConst(value, opSize, allow64Immediates);
return retval;
}
int
EncoderMD::GetOpndSize(IR::Opnd * opnd)
{
return TySize[opnd->GetType()];
}
///----------------------------------------------------------------------------
///
/// EncoderMD::Encode
///
/// Emit the x64 encoding for the given instruction in the passed in
/// buffer ptr.
///
///----------------------------------------------------------------------------
ptrdiff_t
EncoderMD::Encode(IR::Instr *instr, BYTE *pc, BYTE* beginCodeAddress)
{
BYTE *popcodeByte = nullptr,
*prexByte = nullptr,
*instrStart = nullptr,
*instrRestart = nullptr;
IR::Opnd *dst = instr->GetDst();
IR::Opnd *src1 = instr->GetSrc1();
IR::Opnd *src2 = instr->GetSrc2();
IR::Opnd *opr1 = nullptr;
IR::Opnd *opr2 = nullptr;
int instrSize = -1;
bool skipRexByte = false;
m_pc = pc;
if (instr->IsLowered() == false)
{
if (instr->IsLabelInstr())
{
IR::LabelInstr *labelInstr = instr->AsLabelInstr();
labelInstr->SetPC(m_pc);
if (!labelInstr->IsUnreferenced())
{
int relocEntryPosition = AppendRelocEntry(RelocTypeLabel, labelInstr);
if (!PHASE_OFF(Js::LoopAlignPhase, m_func))
{
// we record position of last loop-top label (leaf loops) for loop alignment
if (labelInstr->m_isLoopTop && labelInstr->GetLoop()->isLeaf)
{
m_relocList->Item(relocEntryPosition).m_type = RelocType::RelocTypeAlignedLabel;
}
}
}
}
#if DBG_DUMP
if (instr->IsEntryInstr() && (
Js::Configuration::Global.flags.DebugBreak.Contains(m_func->GetFunctionNumber()) ||
PHASE_ON(Js::DebugBreakPhase, m_func)
))
{
IR::Instr *int3 = IR::Instr::New(Js::OpCode::INT, m_func);
int3->SetSrc1(IR::IntConstOpnd::New(3, TyInt32, m_func));
return this->Encode(int3, m_pc);
}
#endif
return 0;
}
const BYTE *form = EncoderMD::GetFormTemplate(instr);
const BYTE *opcodeTemplate = EncoderMD::GetOpbyte(instr);
const uint32 leadIn = EncoderMD::GetLeadIn(instr);
uint32 opdope = EncoderMD::GetOpdope(instr);
//
// Canonicalize operands.
//
if (opdope & DDST)
{
opr1 = dst;
opr2 = src1;
}
else
{
opr1 = src1;
opr2 = src2;
}
if (opr1)
{
instrSize = this->GetOpndSize(opdope & DREXSRC ? opr2 : opr1);
#if DBG
switch (instr->m_opcode)
{
case Js::OpCode::MOVSXD:
if (8 != this->GetOpndSize(opr1) || 4 != this->GetOpndSize(opr2))
{
AssertMsg(0, "Invalid Operand size ");
}
break;
case Js::OpCode::IMUL2:
// If this is IMUL3 then make sure a 32 bit encoding is possible.
Assert((!opr2) || (!opr2->IsImmediateOpnd()) || (GetOpndSize(opr1) < 8));
break;
case Js::OpCode::SHL:
case Js::OpCode::SHR:
case Js::OpCode::SAR:
//
// TODO_JIT64:
// These should be MachInt instead of Int32. Just ignore for now.
//
//if (instrSize != TySize[TyInt32])
//{
// AssertMsg(0, "Operand size mismatch");
//}
break;
default:
if (opr2)
{
//
// review: this should eventually be enabled. Currently our type system does not
// allow copying from DWORD to QWORD. hence the problem. This is required to read
// JavascriptArray::length which is 4 bytes but the length needs to be operated as
// normal Machine Integer.
//
//
// TODO_JIT64:
// These should be MachInt instead of Int32. Just ignore for now.
//
/*
if (instrSize != this->GetOpndSize(opr2))
{
AssertMsg(0, "Operand size mismatch");
}
*/
}
}
#endif
}
instrRestart = instrStart = m_pc;
// put out 16bit override if any
if (instrSize == 2 && (opdope & (DNO16 | DFLT)) == 0)
{
*instrRestart++ = 0x66;
}
//
// Instruction format is REX [0xF] OP_BYTE ...
//
// Emit the leading byte(s) of multibyte instructions.
if (opdope & D66EX)
{
Assert((opdope & (D66EX | D66 | DF2 | DF3 )) == D66EX);
if (opr1->IsFloat64() || opr2->IsFloat64())
{
*instrRestart++ = 0x66;
}
}
else if (opdope & D66)
{
Assert((opdope & (D66 | DF2 | DF3)) == D66);
Assert(leadIn == OLB_0F || leadIn == OLB_0F3A);
*instrRestart++ = 0x66;
}
else if (opdope & DF2)
{
Assert((opdope & (DF2 | DF3)) == DF2);
Assert(leadIn == OLB_0F);
*instrRestart++ = 0xf2;
}
else if (opdope & DF3)
{
Assert(leadIn == OLB_0F);
*instrRestart++ = 0xf3;
}
// REX byte.
prexByte = instrRestart;
// This is a heuristic to determine whether we really need to have the Rex bytes
// This heuristics is always correct for instrSize == 8
// For instrSize < 8, we might use extended registers and we will have to adjust in EmitRexByte
bool reservedRexByte = (instrSize == 8);
if (reservedRexByte)
{
instrRestart++;
}
switch(leadIn)
{
case OLB_NONE:
break;
case OLB_0F:
*instrRestart++ = 0x0f;
break;
case OLB_0F3A:
*instrRestart++ = 0x0f;
*instrRestart++ = 0x3a;
break;
default:
Assert(UNREACHED);
__assume(UNREACHED);
}
// Actual opcode byte.
popcodeByte = instrRestart++;
//
// Try each form 1 by 1, until we find the one appropriate for this instruction
// and its operands.
//
for (;; opcodeTemplate++, form++)
{
m_pc = instrRestart;
AssertMsg(m_pc - instrStart <= MachMaxInstrSize, "MachMaxInstrSize not set correctly");
// Setup default values.
BYTE opcodeByte = *opcodeTemplate;
BYTE rexByte = REXOVERRIDE | REXW;
switch ((*form) & FORM_MASK)
{
//
// This would only be required for mov rax, [64bit memory address].
//
case AX_MEM:
if (!opr2 || !opr2->IsMemRefOpnd())
{
continue;
}
AnalysisAssert(opr1);
if (opr1->IsRegOpnd() && RegRAX == opr1->AsRegOpnd()->GetReg())
{
AssertMsg(opr2, "Operand 2 must be present in AX_MEM mode");
if (TyInt64 == instrSize)
{
this->EmitImmed(opr2, instrSize, 0);
}
else
{
//
// we don't have a 64 bit opnd. Hence we can will have to use
// the normal MODRM/SIB encoding.
//
continue;
}
}
// NYI
case AX_IM:
continue;
// General immediate case. Special cases have already been checked.
case IMM:
if (!opr2->IsImmediateOpnd())
{
continue;
}
rexByte |= this->EmitModRM(instr, opr1, this->GetOpcodeByte2(instr) >> 3);
opcodeByte |= this->EmitImmed(opr2, instrSize, *form & SBIT);
break;
case NO:
{
Assert(instr->m_opcode == Js::OpCode::CQO || instr->m_opcode == Js::OpCode::CDQ);
instrSize = instr->m_opcode == Js::OpCode::CQO ? 8 : 4;
BYTE byte2 = this->GetOpcodeByte2(instr);
if (byte2)
{
*(m_pc)++ = byte2;
}
}
break;
// Short immediate/reg.
case SHIMR:
AnalysisAssert(opr1);
if (!opr1->IsRegOpnd())
{
continue;
}
if (opr2->IsImmediateOpnd())
{
Assert(EncoderMD::IsMOVEncoding(instr));
if (instrSize == 8 && !instr->isInlineeEntryInstr && Math::FitsInDWord(opr2->GetImmediateValue(instr->m_func)))
{
// Better off using the C7 encoding as it will sign extend
continue;
}
}
else if (!opr2->IsLabelOpnd())
{
continue;
}
opcodeByte |= this->GetRegEncode(opr1->AsRegOpnd());
rexByte |= this->GetRexByte(this->REXB, opr1);
if (instrSize > 1)
{
opcodeByte |= 0x8; /* set the W bit */
}
this->EmitImmed(opr2, instrSize, 0, true); /* S bit known to be 0 */
break;
case AXMODRM:
AssertMsg(opr1->AsRegOpnd()->GetReg() == RegRAX, "Expected src1 of IMUL/IDIV to be RAX");
opr1 = opr2;
opr2 = nullptr;
// FALLTHROUGH
case MODRM:
modrm:
AnalysisAssert(opr1);
if (opr2 == nullptr)
{
BYTE byte2 = (this->GetOpcodeByte2(instr) >> 3);
rexByte |= this->EmitModRM(instr, opr1, byte2);
}
else if (opr1->IsRegOpnd())
{
rexByte |= this->GetRexByte(this->REXR, opr1);
rexByte |= this->EmitModRM(instr, opr2, this->GetRegEncode(opr1->AsRegOpnd()));
if ((*form) & DBIT)
{
opcodeByte |= 0x2; // set D bit
}
}
else
{
AssertMsg(opr2->IsRegOpnd(), "Expected opr2 to be a valid reg");
AssertMsg(instrSize == this->GetOpndSize(opr2), "sf");
AssertMsg(instrSize == this->GetOpndSize(opr1), "Opnd Size mismatch");
rexByte |= this->GetRexByte(this->REXR, opr2);
rexByte |= this->EmitModRM(instr, opr1, this->GetRegEncode(opr2->AsRegOpnd()));
}
break;
// reg in opbyte. Only whole register allowed .
case SH_REG:
AnalysisAssert(opr1);
if (!opr1->IsRegOpnd())
{
continue;
}
opcodeByte |= this->GetRegEncode(opr1->AsRegOpnd());
rexByte |= this->GetRexByte(this->REXB, opr1);
break;
// short form immed. (must be unary)
case SH_IM:
AnalysisAssert(opr1);
if (!opr1->IsIntConstOpnd() && !opr1->IsAddrOpnd())
{
continue;
}
instrSize = this->GetOpndSize(opr1);
opcodeByte |= this->EmitImmed(opr1, instrSize, 1);
break;
case SHFT:
rexByte |= this->EmitModRM(instr, opr1, this->GetOpcodeByte2(instr) >> 3);
if (opr2->IsRegOpnd())
{
AssertMsg(opr2->AsRegOpnd()->GetReg() == RegRCX, "Expected ECX as opr2 of variable shift");
opcodeByte |= *(opcodeTemplate + 1);
}
else
{
IntConstType value;
AssertMsg(opr2->IsIntConstOpnd(), "Expected register or constant as shift amount opnd");
value = opr2->AsIntConstOpnd()->GetValue();
if (value == 1)
{
opcodeByte |= 0x10;
}
else
{
this->EmitConst(value, 1);
}
}
break;
// NYI
case LABREL1:
continue;
// jmp, call with full relative disp.
case LABREL2:
if (opr1 == nullptr)
{
AssertMsg(sizeof(size_t) == sizeof(void*), "Sizes of void* assumed to be 64-bits");
AssertMsg(instr->IsBranchInstr(), "Invalid LABREL2 form");
AppendRelocEntry(RelocTypeBranch, (void*)m_pc, instr->AsBranchInstr()->GetTarget());
this->EmitConst(0 , 4);
}
else if (opr1->IsIntConstOpnd())
{
// pc relative calls are not supported on x64.
AssertMsg(UNREACHED, "Pc relative calls are not supported on x64");
}
else
{
continue;
}
break;
// Special form which doesn't fit any existing patterns.
case SPECIAL:
switch (instr->m_opcode)
{
case Js::OpCode::RET:
{
AssertMsg(opr1->IsIntConstOpnd(), "RET should have intConst as src");
IntConstType value = opr1->AsIntConstOpnd()->GetValue();
if (value==0)
{
opcodeByte |= 0x1; // no imm16 follows
}
else
{
this->EmitConst(value, 2);
}
break;
}
case Js::OpCode::BT:
case Js::OpCode::BTR:
case Js::OpCode::BTS:
/*
* 0F A3 /r BT r/m32, r32
* REX.W 0F A3 /r BT r/m64, r64
* 0F BA /6 ib BT r/m32, imm8
* REX.W 0F BA /6 ib BT r/m64, imm8
* or
* 0F B3 /r BTR r/m32, r32
* REX.W 0F B3 /r BTR r/m64, r64
* 0F BA /6 ib BTR r/m32, imm8
* REX.W 0F BA /6 ib BTR r/m64, imm8
* or
* 0F AB /r BTS r/m32, r32
* REX.W 0F AB /r BTS r/m64, r64
* 0F BA /5 ib BTS r/m32, imm8
* REX.W 0F BA /5 ib BTS r/m64, imm8
*/
Assert(instr->m_opcode != Js::OpCode::BT || dst == nullptr);
AssertMsg(instr->m_opcode == Js::OpCode::BT ||
dst && (dst->IsRegOpnd() || dst->IsMemRefOpnd() || dst->IsIndirOpnd()), "Invalid dst type on BTR/BTS instruction.");
if (src2->IsImmediateOpnd())
{
rexByte |= this->GetRexByte(this->REXR, src1);
rexByte |= this->EmitModRM(instr, src1, this->GetOpcodeByte2(instr) >> 3);
Assert(src2->IsIntConstOpnd() && src2->GetType() == TyInt8);
opcodeByte |= EmitImmed(src2, 1, 0);
}
else
{
/* this is special dbit modrm in which opr1 can be a reg*/
Assert(src2->IsRegOpnd());
form++;
opcodeTemplate++;
Assert(((*form) & FORM_MASK) == MODRM);
opcodeByte = *opcodeTemplate;
rexByte |= this->GetRexByte(this->REXR, src2);
rexByte |= this->EmitModRM(instr, src1, this->GetRegEncode(src2->AsRegOpnd()));
}
break;
case Js::OpCode::SHLD:
/*
* 0F A4 SHLD r/m32, r32, imm8
* REX.W 0F A4 SHLD r/m64, r64, imm8
*/
AssertMsg(dst && (dst->IsRegOpnd() || dst->IsMemRefOpnd() || dst->IsIndirOpnd()), "Invalid dst type on SHLD instruction.");
AssertMsg(src1 && src1->IsRegOpnd(), "Expected SHLD's second operand to be a register.");
AssertMsg(src2 && src2->IsImmediateOpnd(), "Expected SHLD's third operand to be an immediate.");
rexByte |= this->GetRexByte(this->REXR, opr2);
rexByte |= EmitModRM(instr, opr1, GetRegEncode(opr2->AsRegOpnd()));
opcodeByte |= EmitImmed(src2, 1, 1);
break;
case Js::OpCode::IMUL2:
AssertMsg(opr1->IsRegOpnd() && instrSize != 1, "Illegal IMUL2");
if (!opr2->IsImmediateOpnd())
{
continue;
}
Assert(instrSize < 8);
// turn an 'imul2 reg, immed' into an 'imul3 reg, reg, immed'.
// Reset pointers
prexByte = instrStart;
reservedRexByte = true;
// imul3 uses a one-byte opcode, so no need to set the first byte of the opcode to 0xf
popcodeByte = instrStart + 1;
m_pc = instrStart + 2;
// Set up default values
rexByte = REXOVERRIDE;
opcodeByte = *opcodeTemplate; // hammer prefix
rexByte |= this->GetRexByte(REXR, opr1);
rexByte |= this->EmitModRM(instr, opr1, this->GetRegEncode(opr1->AsRegOpnd()));
// Currently, we only support 32-bit operands (Assert(instrSize < 8) above), so REXW is not set
Assert(!(rexByte & REXW));
opcodeByte |= this->EmitImmed(opr2, instrSize, 1);
break;
case Js::OpCode::INT:
if (opr1->AsIntConstOpnd()->GetValue() != 3)
{
opcodeByte |= 1;
*(m_pc)++ = (char)opr1->AsIntConstOpnd()->GetValue();
}
break;
case Js::OpCode::MOVQ:
{
// The usual mechanism for encoding SSE/SSE2 instructions
// doesn't work quite right for this one, so we must fixup
// the encoding.
if (REGNUM_ISXMMXREG(opr1->AsRegOpnd()->GetReg())) {
if (opr2->IsRegOpnd() && this->GetOpndSize(opr2) == 8) { // QWORD_SIZE
// Encoded as 66 REX.W 0F 6E ModRM
opdope &= ~(DF2 | DF3);
opdope |= D66;
opcodeByte = 0x6E;
goto modrm;
}
else {
// Encoded as F3 0F 7E ModRM
opdope &= ~(D66 | D66EX | DF2);
opdope |= DF3;
opcodeByte = 0x7E;
goto modrm;
}
}
//else if (TU_ISXMMXREG(opr2)) {
else if (REGNUM_ISXMMXREG(opr2->AsRegOpnd()->GetReg())) {
//if (TU_ISREG(opr1) && (TU_SIZE(opr1) == QWORD_SIZE)) {
if (opr1->IsRegOpnd() && this->GetOpndSize(opr1) == 8) {// QWORD_SIZE
// Encoded as 66 REX.W 0F 7E ModRM
opdope &= ~(DF2 | DF3);
opdope |= D66;
opcodeByte = 0x7E;
// Swap operands to get right behavior from modrm encode.
IR::Opnd* tmp = opr1;
opr1 = opr2;
opr2 = tmp;
goto modrm;
}
else {
// Encoded as 66 0F D6
opdope &= ~(DF2 | DF3);
opdope |= D66;
opcodeByte = 0xD6;
goto modrm;
}
}
}
// FALL-THROUGH to MOVD
case Js::OpCode::MOVD:
if (opr2->IsRegOpnd() && REGNUM_ISXMMXREG(opr2->AsRegOpnd()->GetReg()))
{
// If the second operand is an XMM register, use the "store" form.
if (opr1->IsRegOpnd())
{
// Swap operands to get right behavior from MODRM.
IR::Opnd *tmp = opr1;
opr1 = opr2;
opr2 = tmp;
}
opcodeByte |= 0x10;
}
Assert(opr1->IsRegOpnd() && REGNUM_ISXMMXREG(opr1->AsRegOpnd()->GetReg()));
goto modrm;
case Js::OpCode::MOVLHPS:
case Js::OpCode::MOVHLPS:
Assert(opr1->IsRegOpnd() && REGNUM_ISXMMXREG(opr1->AsRegOpnd()->GetReg()));
Assert(opr2->IsRegOpnd() && REGNUM_ISXMMXREG(opr2->AsRegOpnd()->GetReg()));
goto modrm;
case Js::OpCode::MOVMSKPD:
case Js::OpCode::MOVMSKPS:
case Js::OpCode::PMOVMSKB:
/* Instruction form is "MOVMSKP[S/D] r32, xmm" */
Assert(opr1->IsRegOpnd() && opr2->IsRegOpnd() && REGNUM_ISXMMXREG(opr2->AsRegOpnd()->GetReg()));
goto modrm;
case Js::OpCode::MOVSS:
case Js::OpCode::MOVSD:
case Js::OpCode::MOVAPD:
case Js::OpCode::MOVAPS:
case Js::OpCode::MOVUPS:
case Js::OpCode::MOVHPD:
if (!opr1->IsRegOpnd())
{
Assert(opr1->IsIndirOpnd() || opr1->IsMemRefOpnd() || opr1->IsSymOpnd());
Assert(opr2->IsRegOpnd());
Assert(REGNUM_ISXMMXREG(opr2->AsRegOpnd()->GetReg()));
opcodeByte |= 0x01;
}
goto modrm;
case Js::OpCode::NOP:
if (instr->GetSrc1())
{
// Multibyte NOP.
Assert(instr->GetSrc1()->IsIntConstOpnd() && instr->GetSrc1()->GetType() == TyInt8);
unsigned nopSize = instr->GetSrc1()->AsIntConstOpnd()->AsUint32();
Assert(nopSize >= 2 && nopSize <= 4);
nopSize = max(2u, min(4u, nopSize)); // satisfy oacr
const BYTE *nopEncoding = Nop[nopSize - 1];
opcodeByte = nopEncoding[0];
for (unsigned i = 1; i < nopSize; i++)
{
*(m_pc)++ = nopEncoding[i];
}
}
skipRexByte = true;
break;
case Js::OpCode::XCHG:
if (instrSize == 1)
continue;
if (opr1->IsRegOpnd() && opr1->AsRegOpnd()->GetReg() == RegRAX
&& opr2->IsRegOpnd())
{
uint8 reg = this->GetRegEncode(opr2->AsRegOpnd());
rexByte |= this->GetRexByte(REXR, opr2);
opcodeByte |= reg;
}
else if (opr2->IsRegOpnd() && opr2->AsRegOpnd()->GetReg() == RegRAX
&& opr1->IsRegOpnd())
{
uint8 reg = this->GetRegEncode(opr1->AsRegOpnd());
rexByte |= this->GetRexByte(REXB, opr1);
opcodeByte |= reg;
}
else
{
continue;
}
break;
case Js::OpCode::PSLLW:
case Js::OpCode::PSLLD:
case Js::OpCode::PSRLW:
case Js::OpCode::PSRLD:
case Js::OpCode::PSRAW:
case Js::OpCode::PSRAD:
case Js::OpCode::PSLLDQ:
case Js::OpCode::PSRLDQ:
Assert(opr1->IsRegOpnd());
if (src2 &&src2->IsIntConstOpnd())
{
// SSE shift with IMM
rexByte |= this->EmitModRM(instr, opr1, this->GetOpcodeByte2(instr) >> 3);
break;
}
else
{
// Variable shift amount
// fix opcode byte
switch (instr->m_opcode)
{
case Js::OpCode::PSLLW:
opcodeByte = 0xF1;
break;
case Js::OpCode::PSLLD:
opcodeByte = 0xF2;
break;
case Js::OpCode::PSRLW:
opcodeByte = 0xD1;
break;
case Js::OpCode::PSRLD:
opcodeByte = 0xD2;
break;
case Js::OpCode::PSRAW:
opcodeByte = 0xE1;
break;
case Js::OpCode::PSRAD:
opcodeByte = 0xE2;
break;
default:
Assert(UNREACHED);
}
goto modrm;
}
default:
AssertMsg(UNREACHED, "Unhandled opcode in SPECIAL form");
}
break;
case INVALID:
AssertMsg(UNREACHED, "No encoder form found");
return m_pc - instrStart;
default:
AssertMsg(UNREACHED, "Unhandled encoder form");
}
// If instr has W bit, set it appropriately.
if ((*form & WBIT) && !(opdope & DFLT) && instrSize != 1)
{
opcodeByte |= 0x1; // set WBIT
}
*popcodeByte = opcodeByte;
AssertMsg(m_pc - instrStart <= MachMaxInstrSize, "MachMaxInstrSize not set correctly");
// Near JMPs, all instructions that reference RSP implicitly and
// instructions that operate on registers smaller than 64 bits don't
// get a REX prefix.
EmitRexByte(prexByte, rexByte, skipRexByte || (instrSize < 8), reservedRexByte);
if (opdope & DSSE)
{
// extra imm8 byte for SSE instructions.
uint valueImm = 0;
bool writeImm = true;
if (src2 &&src2->IsIntConstOpnd())
{
valueImm = (uint)src2->AsIntConstOpnd()->GetImmediateValue(instr->m_func);
}
else
{
// Variable src2, we are either encoding a CMP op, or don't need an Imm.
// src2(comparison byte) is missing in CMP instructions and is part of the opcode instead.
switch (instr->m_opcode)
{
case Js::OpCode::CMPLTPS:
case Js::OpCode::CMPLTPD:
valueImm = CMP_IMM8::LT;
break;
case Js::OpCode::CMPLEPS:
case Js::OpCode::CMPLEPD:
valueImm = CMP_IMM8::LE;
break;
case Js::OpCode::CMPEQPS:
case Js::OpCode::CMPEQPD:
valueImm = CMP_IMM8::EQ;
break;
case Js::OpCode::CMPNEQPS:
case Js::OpCode::CMPNEQPD:
valueImm = CMP_IMM8::NEQ;
break;
case Js::OpCode::CMPUNORDPS:
valueImm = CMP_IMM8::UNORD;
break;
default:
// none comparison op, should have non-constant src2 already encoded as MODRM reg.
Assert(src2 && !src2->IsIntConstOpnd());
writeImm = false;
}
}
if (writeImm)
{
*(m_pc++) = (valueImm & 0xff);
}
}
#if DEBUG
// Verify that the disassembly code for out-of-bounds typedArray handling can decode all the MOVs we emit.
// Call it on every MOV
if (LowererMD::IsAssign(instr) && (instr->GetDst()->IsIndirOpnd() || instr->GetSrc1()->IsIndirOpnd()))
{
CONTEXT context = { 0 };
EXCEPTION_POINTERS exceptionInfo;
exceptionInfo.ContextRecord = &context;
Js::ArrayAccessDecoder::InstructionData instrData;
BYTE *tempPc = instrStart;
instrData = Js::ArrayAccessDecoder::CheckValidInstr(tempPc, &exceptionInfo);
// Make sure we can decode the instr
Assert(!instrData.isInvalidInstr);
// Verify the instruction size matches
Assert(instrData.instrSizeInByte == m_pc - instrStart);
// Note: We could verify other properties if deemed useful, like types, register values, etc...
}
#endif
return m_pc - instrStart;
}
}
void
EncoderMD::EmitRexByte(BYTE * prexByte, BYTE rexByte, bool skipRexByte, bool reservedRexByte)
{
if (skipRexByte)
{
// REX byte is not needed - let's remove it and move everything else by 1 byte
if (((rexByte)& 0x0F) == 0x8)
{
// If we didn't reserve the rex byte, we don't have to do anything
if (reservedRexByte)
{
Assert(m_pc > prexByte);
BYTE* current = prexByte;
while (current < m_pc)
{
*current = *(current + 1);
current++;
}
if (m_relocList != nullptr)
{
// if a reloc record was added as part of encoding this instruction - fix the pc in the reloc
EncodeRelocAndLabels &lastRelocEntry = m_relocList->Item(m_relocList->Count() - 1);
if (lastRelocEntry.m_ptr > prexByte && lastRelocEntry.m_ptr < m_pc)
{
Assert(lastRelocEntry.m_type != RelocTypeLabel);
lastRelocEntry.m_ptr = (BYTE*)lastRelocEntry.m_ptr - 1;
lastRelocEntry.m_origPtr = (BYTE*)lastRelocEntry.m_origPtr - 1;
}
}
}
return;
}
rexByte = rexByte & 0xF7;
}
// If we didn't reserve the rex byte, we need to move everything by 1 and make
// room for it.
if (!reservedRexByte)
{
Assert(m_pc > prexByte);
BYTE* current = m_pc;
while (current > prexByte)
{
*current = *(current - 1);
current--;
}
if (m_relocList != nullptr)
{
// if a reloc record was added as part of encoding this instruction - fix the pc in the reloc
EncodeRelocAndLabels &lastRelocEntry = m_relocList->Item(m_relocList->Count() - 1);
if (lastRelocEntry.m_ptr > prexByte && lastRelocEntry.m_ptr < m_pc)
{
Assert(lastRelocEntry.m_type != RelocTypeLabel);
lastRelocEntry.m_ptr = (BYTE*)lastRelocEntry.m_ptr + 1;
lastRelocEntry.m_origPtr = (BYTE*)lastRelocEntry.m_origPtr + 1;
}
}
m_pc++;
}
// Emit the rex byte
*prexByte = rexByte;
}
bool
EncoderMD::IsExtendedRegister(RegNum reg)
{
return REGNUM_ISXMMXREG(reg) ? (reg >= RegXMM8) : (reg >= RegR8);
}
int
EncoderMD::AppendRelocEntry(RelocType type, void *ptr, IR::LabelInstr *label)
{
if (m_relocList == nullptr)
m_relocList = Anew(m_encoder->m_tempAlloc, RelocList, m_encoder->m_tempAlloc);
EncodeRelocAndLabels reloc;
reloc.init(type, ptr, label);
return m_relocList->Add(reloc);
}
int
EncoderMD::FixRelocListEntry(uint32 index, int totalBytesSaved, BYTE *buffStart, BYTE* buffEnd)
{
BYTE* currentPc;
EncodeRelocAndLabels &relocRecord = m_relocList->Item(index);
int result = totalBytesSaved;
// LabelInstr ?
if (relocRecord.isLabel())
{
BYTE* newPC;
currentPc = relocRecord.getLabelCurrPC();
AssertMsg(currentPc >= buffStart && currentPc < buffEnd, "LabelInstr offset has to be within buffer.");
newPC = currentPc - totalBytesSaved;
// find the number of nops needed to align this loop top
if (relocRecord.isAlignedLabel() && !PHASE_OFF(Js::LoopAlignPhase, m_func))
{
ptrdiff_t diff = newPC - buffStart;
Assert(diff >= 0 && diff <= UINT_MAX);
uint32 offset = (uint32)diff;
// Since the final code buffer is page aligned, it is enough to align the offset of the label.
BYTE nopCount = (16 - (BYTE)(offset & 0xf)) % 16;
if (nopCount <= Js::Configuration::Global.flags.LoopAlignNopLimit)
{
// new label pc
newPC += nopCount;
relocRecord.setLabelNopCount(nopCount);
// adjust bytes saved
result -= nopCount;
}
}
relocRecord.setLabelCurrPC(newPC);
}
else
{
currentPc = (BYTE*) relocRecord.m_origPtr;
// ignore outside buffer offsets (e.g. JumpTable entries)
if (currentPc >= buffStart && currentPc < buffEnd)
{
if (relocRecord.m_type == RelocTypeInlineeEntryOffset)
{
// ptr points to imm32 offset of the instruction that needs to be adjusted
// offset is in top 28-bits, arg count in bottom 4
size_t field = *((size_t*) relocRecord.m_origPtr);
size_t offset = field >> 4;
uint32 count = field & 0xf;
AssertMsg(offset < (size_t)(buffEnd - buffStart), "Inlinee entry offset out of range");
relocRecord.SetInlineOffset(((offset - totalBytesSaved) << 4) | count);
}
// adjust the ptr to the buffer itself
relocRecord.m_ptr = (BYTE*) relocRecord.m_ptr - totalBytesSaved;
}
}
return result;
}
void EncoderMD::AddLabelReloc(BYTE* relocAddress)
{
AppendRelocEntry(RelocTypeLabel, relocAddress);
}
///----------------------------------------------------------------------------
///
/// EncoderMD::FixMaps
/// Fixes the inlinee frame records and map based on BR shortening
///
///----------------------------------------------------------------------------
void
EncoderMD::FixMaps(uint32 brOffset, uint32 bytesSaved, uint32 *inlineeFrameRecordsIndex, uint32 *inlineeFrameMapIndex, uint32 *pragmaInstToRecordOffsetIndex, uint32 *offsetBuffIndex)
{
InlineeFrameRecords *recList = m_encoder->m_inlineeFrameRecords;
InlineeFrameMap *mapList = m_encoder->m_inlineeFrameMap;
PragmaInstrList *pInstrList = m_encoder->m_pragmaInstrToRecordOffset;
int32 i;
for (i = *inlineeFrameRecordsIndex; i < recList->Count() && recList->Item(i)->inlineeStartOffset <= brOffset; i++)
recList->Item(i)->inlineeStartOffset -= bytesSaved;
*inlineeFrameRecordsIndex = i;
for (i = *inlineeFrameMapIndex; i < mapList->Count() && mapList->Item(i).offset <= brOffset; i++)
mapList->Item(i).offset -= bytesSaved;
*inlineeFrameMapIndex = i;
for (i = *pragmaInstToRecordOffsetIndex; i < pInstrList->Count() && pInstrList->Item(i)->m_offsetInBuffer <= brOffset; i++)
pInstrList->Item(i)->m_offsetInBuffer -= bytesSaved;
*pragmaInstToRecordOffsetIndex = i;
#if DBG_DUMP
for (i = *offsetBuffIndex; (uint)i < m_encoder->m_instrNumber && m_encoder->m_offsetBuffer[i] <= brOffset; i++)
m_encoder->m_offsetBuffer[i] -= bytesSaved;
*offsetBuffIndex = i;
#endif
}
///----------------------------------------------------------------------------
///
/// EncoderMD::ApplyRelocs
///
///----------------------------------------------------------------------------
void
EncoderMD::ApplyRelocs(size_t codeBufferAddress_, size_t codeSize, uint * bufferCRC, BOOL isBrShorteningSucceeded, bool isFinalBufferValidation)
{
if (m_relocList == nullptr)
{
return;
}
for (int32 i = 0; i < m_relocList->Count(); i++)
{
EncodeRelocAndLabels *reloc = &m_relocList->Item(i);
BYTE * relocAddress = (BYTE*)reloc->m_ptr;
uint32 pcrel;
switch (reloc->m_type)
{
case RelocTypeCallPcrel:
AssertMsg(UNREACHED, "PC relative calls not yet supported on amd64");
#if 0
{
pcrel = (uint32)(codeBufferAddress + (BYTE*)reloc->m_ptr - m_encoder->m_encodeBuffer + 4);
*(uint32 *)relocAddress -= pcrel;
break;
}
#endif
case RelocTypeBranch:
{
// The address of the target LabelInstr is saved at the reloc address.
IR::LabelInstr * labelInstr = reloc->getBrTargetLabel();
AssertMsg(labelInstr->GetPC() != nullptr, "Branch to unemitted label?");
if (reloc->isShortBr() )
{
// short branch
pcrel = (uint32)(labelInstr->GetPC() - ((BYTE*)reloc->m_ptr + 1));
AssertMsg((int32)pcrel >= -128 && (int32)pcrel <= 127, "Offset doesn't fit in imm8.");
if (!isFinalBufferValidation)
{
Assert(*(BYTE*)relocAddress == 0);
*(BYTE*)relocAddress = (BYTE)pcrel;
}
else
{
Encoder::EnsureRelocEntryIntegrity(codeBufferAddress_, codeSize, (size_t)m_encoder->m_encodeBuffer, (size_t)relocAddress, sizeof(BYTE), (ptrdiff_t)labelInstr->GetPC() - ((ptrdiff_t)reloc->m_ptr + 1));
}
}
else
{
pcrel = (uint32)(labelInstr->GetPC() - ((BYTE*)reloc->m_ptr + 4));
if (!isFinalBufferValidation)
{
Assert(*(uint32*)relocAddress == 0);
*(uint32 *)relocAddress = pcrel;
}
else
{
Encoder::EnsureRelocEntryIntegrity(codeBufferAddress_, codeSize, (size_t)m_encoder->m_encodeBuffer, (size_t)relocAddress, sizeof(uint32), (ptrdiff_t)labelInstr->GetPC() - ((ptrdiff_t)reloc->m_ptr + 4));
}
}
*bufferCRC = Encoder::CalculateCRC(*bufferCRC, pcrel);
break;
}
case RelocTypeLabelUse:
{
IR::LabelInstr *labelInstr = reloc->getBrTargetLabel();
AssertMsg(labelInstr->GetPC() != nullptr, "Branch to unemitted label?");
size_t offset = (size_t)(labelInstr->GetPC() - m_encoder->m_encodeBuffer);
size_t targetAddress = (size_t)(offset + codeBufferAddress_);
if (!isFinalBufferValidation)
{
Assert(*(size_t *)relocAddress == 0);
*(size_t *)relocAddress = targetAddress;
}
else
{
Encoder::EnsureRelocEntryIntegrity(codeBufferAddress_, codeSize, (size_t)m_encoder->m_encodeBuffer, (size_t)relocAddress, sizeof(size_t), targetAddress, false);
}
*bufferCRC = Encoder::CalculateCRC(*bufferCRC, offset);
break;
}
case RelocTypeLabel:
case RelocTypeAlignedLabel:
case RelocTypeInlineeEntryOffset:
break;
default:
AssertMsg(UNREACHED, "Unknown reloc type");
}
}
}
uint
EncoderMD::GetRelocDataSize(EncodeRelocAndLabels *reloc)
{
switch (reloc->m_type)
{
case RelocTypeBranch:
{
if (reloc->isShortBr())
{
return sizeof(BYTE);
}
else
{
return sizeof(uint);
}
}
case RelocTypeLabelUse:
{
return sizeof(size_t);
}
default:
{
return 0;
}
}
}
BYTE *
EncoderMD::GetRelocBufferAddress(EncodeRelocAndLabels * reloc)
{
return (BYTE*)reloc->m_ptr;
}
#ifdef DBG
///----------------------------------------------------------------------------
///
/// EncodeRelocAndLabels::VerifyRelocList
/// Verify that the list of offsets within the encoder buffer range are in ascending order.
/// This includes offsets of immediate fields in the code and offsets of LabelInstrs
///----------------------------------------------------------------------------
void
EncoderMD::VerifyRelocList(BYTE *buffStart, BYTE *buffEnd)
{
BYTE *last_pc = 0, *pc;
if (m_relocList == nullptr)
{
return;
}
for (int32 i = 0; i < m_relocList->Count(); i ++)
{
EncodeRelocAndLabels &p = m_relocList->Item(i);
// LabelInstr ?
if (p.isLabel())
{
AssertMsg(p.m_ptr < buffStart || p.m_ptr >= buffEnd, "Invalid label instruction pointer.");
pc = ((IR::LabelInstr*)p.m_ptr)->GetPC();
AssertMsg(pc >= buffStart && pc < buffEnd, "LabelInstr offset has to be within buffer.");
}
else
pc = (BYTE*)p.m_ptr;
// The list is partially sorted, out of bound ptrs (JumpTable entries) don't follow.
if (pc >= buffStart && pc < buffEnd)
{
if (last_pc)
AssertMsg(pc >= last_pc, "Unordered reloc list.");
last_pc = pc;
}
}
}
#endif
BYTE
EncoderMD::GetRexByte(BYTE rexCode, IR::Opnd * opnd)
{
return this->GetRexByte(rexCode, opnd->AsRegOpnd()->GetReg());
}
BYTE
EncoderMD::GetRexByte(BYTE rexCode, RegNum reg)
{
if (this->IsExtendedRegister(reg))
{
return rexCode;
}
else
{
return 0;
}
}
void
EncoderMD::EncodeInlineeCallInfo(IR::Instr *instr, uint32 codeOffset)
{
Assert(instr->GetSrc1() &&
instr->GetSrc1()->IsIntConstOpnd() &&
(instr->GetSrc1()->AsIntConstOpnd()->GetValue() == (instr->GetSrc1()->AsIntConstOpnd()->GetValue() & 0xF)));
intptr_t inlineeCallInfo = 0;
// 60 (AMD64) bits on the InlineeCallInfo to store the
// offset of the start of the inlinee. We shouldn't have gotten here with more arguments
// than can fit in as many bits.
const bool encodeResult = Js::InlineeCallInfo::Encode(inlineeCallInfo,
instr->GetSrc1()->AsIntConstOpnd()->GetValue(), codeOffset);
Assert(encodeResult);
instr->GetSrc1()->AsIntConstOpnd()->SetValue(inlineeCallInfo);
}
bool EncoderMD::TryConstFold(IR::Instr *instr, IR::RegOpnd *regOpnd)
{
Assert(regOpnd->m_sym->IsConst());
if (regOpnd->m_sym->IsFloatConst() || regOpnd->m_sym->IsInt64Const())
{
return false;
}
bool isNotLargeConstant = Math::FitsInDWord(regOpnd->m_sym->GetLiteralConstValue_PostGlobOpt());
if (!isNotLargeConstant && (!EncoderMD::IsMOVEncoding(instr) || !instr->GetDst()->IsRegOpnd()))
{
return false;
}
switch (GetInstrForm(instr))
{
case FORM_MOV:
if (!instr->GetSrc1()->IsRegOpnd())
{
return false;
}
break;
case FORM_PSHPOP:
if (instr->m_opcode != Js::OpCode::PUSH)
{
return false;
}
if (!instr->GetSrc1()->IsRegOpnd())
{
return false;
}
break;
case FORM_BINOP:
case FORM_SHIFT:
if (regOpnd != instr->GetSrc2())
{
return false;
}
break;
default:
return false;
}
if (regOpnd != instr->GetSrc1() && regOpnd != instr->GetSrc2())
{
if (!regOpnd->m_sym->IsConst() || regOpnd->m_sym->IsFloatConst())
{
return false;
}
// Check if it's the index opnd inside an indir
bool foundUse = false;
bool foldedAllUses = true;
IR::Opnd *const srcs[] = { instr->GetSrc1(), instr->GetSrc2(), instr->GetDst() };
for (int i = 0; i < sizeof(srcs) / sizeof(srcs[0]); ++i)
{
const auto src = srcs[i];
if (!src || !src->IsIndirOpnd())
{
continue;
}
const auto indir = src->AsIndirOpnd();
if (regOpnd == indir->GetBaseOpnd())
{
// Can't const-fold into the base opnd
foundUse = true;
foldedAllUses = false;
continue;
}
if (regOpnd != indir->GetIndexOpnd())
{
continue;
}
foundUse = true;
if (!regOpnd->m_sym->IsIntConst())
{
foldedAllUses = false;
continue;
}
// offset = indir.offset + (index << scale)
int32 offset = regOpnd->m_sym->GetIntConstValue();
if ((indir->GetScale() != 0 && Int32Math::Shl(offset, indir->GetScale(), &offset)) ||
(indir->GetOffset() != 0 && Int32Math::Add(indir->GetOffset(), offset, &offset)))
{
foldedAllUses = false;
continue;
}
indir->SetOffset(offset);
indir->SetIndexOpnd(nullptr);
}
return foundUse && foldedAllUses;
}
instr->ReplaceSrc(regOpnd, regOpnd->m_sym->GetConstOpnd());
return true;
}
bool EncoderMD::TryFold(IR::Instr *instr, IR::RegOpnd *regOpnd)
{
IR::Opnd *src1 = instr->GetSrc1();
IR::Opnd *src2 = instr->GetSrc2();
if (IRType_IsSimd128(regOpnd->GetType()))
{
// No folding for SIMD values. Alignment is not guaranteed.
return false;
}
switch (GetInstrForm(instr))
{
case FORM_MOV:
if (!instr->GetDst()->IsRegOpnd() || regOpnd != src1)
{
return false;
}
break;
case FORM_BINOP:
if (regOpnd == src1 && instr->m_opcode == Js::OpCode::CMP && (src2->IsRegOpnd() || src1->IsImmediateOpnd()))
{
IR::Instr *instrNext = instr->GetNextRealInstrOrLabel();
if (instrNext->IsBranchInstr() && instrNext->AsBranchInstr()->IsConditional() && !instrNext->AsBranchInstr()->m_areCmpRegisterFlagsUsedLater)
{
// Swap src and reverse branch
src2 = instr->UnlinkSrc1();
src1 = instr->UnlinkSrc2();
instr->SetSrc1(src1);
instr->SetSrc2(src2);
LowererMD::ReverseBranch(instrNext->AsBranchInstr());
}
else
{
return false;
}
}
if (regOpnd != src2 || !src1->IsRegOpnd())
{
return false;
}
break;
case FORM_MODRM:
if (src2 == nullptr)
{
if (!instr->GetDst()->IsRegOpnd() || regOpnd != src1 || EncoderMD::IsOPEQ(instr))
{
return false;
}
}
else
{
if (regOpnd != src2 || !src1->IsRegOpnd())
{
return false;
}
}
break;
case FORM_PSHPOP:
if (instr->m_opcode != Js::OpCode::PUSH)
{
return false;
}
if (!instr->GetSrc1()->IsRegOpnd())
{
return false;
}
break;
case FORM_TEST:
if (regOpnd == src1)
{
if (!src2->IsRegOpnd() && !src2->IsIntConstOpnd())
{
return false;
}
}
else if (src1->IsRegOpnd())
{
instr->SwapOpnds();
}
else
{
return false;
}
break;
default:
return false;
}
IR::SymOpnd *symOpnd = IR::SymOpnd::New(regOpnd->m_sym, regOpnd->GetType(), instr->m_func);
instr->ReplaceSrc(regOpnd, symOpnd);
return true;
}
bool EncoderMD::SetsConditionCode(IR::Instr *instr)
{
return instr->IsLowered() && (EncoderMD::GetOpdope(instr) & DSETCC);
}
bool EncoderMD::UsesConditionCode(IR::Instr *instr)
{
return instr->IsLowered() && (EncoderMD::GetOpdope(instr) & DUSECC);
}
bool EncoderMD::IsOPEQ(IR::Instr *instr)
{
return instr->IsLowered() && (EncoderMD::GetOpdope(instr) & DOPEQ);
}
bool EncoderMD::IsSHIFT(IR::Instr *instr)
{
return (instr->IsLowered() && EncoderMD::GetInstrForm(instr) == FORM_SHIFT) ||
instr->m_opcode == Js::OpCode::PSLLDQ || instr->m_opcode == Js::OpCode::PSRLDQ ||
instr->m_opcode == Js::OpCode::PSLLW || instr->m_opcode == Js::OpCode::PSRLW ||
instr->m_opcode == Js::OpCode::PSLLD;
}
bool EncoderMD::IsMOVEncoding(IR::Instr *instr)
{
return instr->IsLowered() && (EncoderMD::GetOpdope(instr) & DMOV);
}
void EncoderMD::UpdateRelocListWithNewBuffer(RelocList * relocList, BYTE * newBuffer, BYTE * oldBufferStart, BYTE * oldBufferEnd)
{
for (int32 i = 0; i < relocList->Count(); i++)
{
EncodeRelocAndLabels &reloc = relocList->Item(i);
if (reloc.isLabel())
{
IR::LabelInstr* label = reloc.getLabel();
BYTE* labelPC = label->GetPC();
Assert((BYTE*) reloc.m_origPtr >= oldBufferStart && (BYTE*) reloc.m_origPtr < oldBufferEnd);
label->SetPC(labelPC - oldBufferStart + newBuffer);
// nothing more to be done for a label
continue;
}
else if (reloc.m_type >= RelocTypeBranch && reloc.m_type <= RelocTypeLabelUse &&
(BYTE*) reloc.m_origPtr >= oldBufferStart && (BYTE*) reloc.m_origPtr < oldBufferEnd)
{
// we need to relocate all new offset that were originally within buffer
reloc.m_ptr = (BYTE*) reloc.m_ptr - oldBufferStart + newBuffer;
}
}
}