blob: 26eda578fe3e0d0a36a8c109a926414599696377 [file]
/*
* 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
*/
#include "Ia32CgUtils.h"
namespace Jitrino {
namespace Ia32 {
class PeepHoleOpt;
static const char* help =
"Performs simple local (per-BB) or per-Inst optimizations.\n"
"Some of them include:\n"
"\t Inlined F2I conversion\n"
"A better instructions selection:\n"
"\t Change 32bit immediate values to 8bit in ALU instructions\n"
"\t MOVSS/MOVSD replaced with MOVQ\n"
"\t MOVSS/MOVSD xmm, [memconst=0.] => PXOR xmm, xmm\n"
"It's recommended to have 2 passes of peephole: the first one before\n"
"a register allocator - to inline the conversions and provide more\n"
"opportunities for further optimization. And the second one - after\n"
"the register allocator to improve the instructions selection."
;
static ActionFactory<PeepHoleOpt> _staticAutoRegister("peephole", help);
class PeepHoleOpt :
public SessionAction,
protected OpndUtils,
protected InstUtils,
protected SubCfgBuilderUtils
{
private:
// Virtuals
U_32 getSideEffects(void) const
{
return m_bHadAnyChange ? (U_32)-1 : 0;
}
private:
enum Changed {
/// Nothing was changed
Changed_Nothing,
/**
* One or more Opnds were changed/added/removed - might need to
* update liveness info.
*/
Changed_Opnd,
/**
* One or more Insts were changed/added/removed - might need to
* update Insts list.
*/
Changed_Inst,
/**
* One or more Nodes were changed/added/removed - might need to
* update Nodes list.
*/
Changed_Node
};
//
// General machinery.
// TODO: It's better to separate the general CFG-walking machinery into
// a separate class.
//
void runImpl(void);
Changed handleBasicBlock(Node* node);
Changed handleInst(Inst* inst);
//
//
//
Changed handleInst_MOV(Inst* inst);
Changed handleInst_Call(Inst* inst);
Changed handleInst_InternalHelperCall(Inst* inst, const Opnd::RuntimeInfo* ri);
Changed handleInst_Convert_F2I_D2I(Inst* inst);
Changed handleInst_ALU(Inst* inst);
Changed handleInst_MUL(Inst* inst);
Changed handleInst_SSEMov(Inst* inst);
Changed handleInst_SSEXor(Inst* inst);
Changed handleInst_CMP(Inst* inst);
Changed handleInst_SETcc(Inst* inst);
//
// Helpers
//
//
bool m_bHadAnyChange;
// For local propagation from new generated MOV
StlHashMap<Opnd*, Opnd*> *copyMap;
StlSet<Opnd*> *tempSet;
}; // ~PeepHoleOpt
void PeepHoleOpt::runImpl(void)
{
MemoryManager mm("handleBasicBlock");
copyMap = new(mm) StlHashMap<Opnd*, Opnd*>(mm);
tempSet = new(mm) StlSet<Opnd*>(mm);
setIRManager(irManager);
irManager->calculateOpndStatistics();
m_bHadAnyChange = false;
// organize an infinity loop and keep spinning till we have any change.
// thought have a safety counter to prevent a really infinity in case
// anything goes wrong in runtime
bool keepGoing = true;
unsigned safetyCounter = 0;
do {
keepGoing = false;
const Nodes& nodes = irManager->getFlowGraph()->getNodes();
for (Nodes::const_iterator citer = nodes.begin();
citer != nodes.end(); ++citer) {
Node* node = *citer;
if (!node->isBlockNode()) {
continue;
}
Changed whatChanged = handleBasicBlock(node);
if (whatChanged != Changed_Nothing) {
m_bHadAnyChange = true;
keepGoing = true;
}
if (whatChanged == Changed_Node) {
break;
}
}
++safetyCounter;
if(safetyCounter > 100000) {
// I hardly believe in a method that has more than 100K
// opportunities to fix in peephole.
// Most probably self bug - assert() in debug mode, stop trying
// in release.
assert(false);
keepGoing = false;
}
} while(keepGoing);
}
PeepHoleOpt::Changed PeepHoleOpt::handleBasicBlock(Node* node)
{
copyMap->clear();
tempSet->clear();
Inst* inst = (Inst*)node->getFirstInst();
Changed changedInBB = Changed_Nothing;
while (inst != NULL) {
Inst* savePrev = inst->getPrevInst();
Changed whatChanged = handleInst(inst);
if (whatChanged == Changed_Node) {
// Need to scan the CFG again.
return Changed_Node;
}
Inst* next = NULL;
if (whatChanged == Changed_Inst) {
changedInBB = Changed_Inst;
// Inst was replaced, or deleted, or new Inst was added -
// proceed with this new or updated instruction(s) again
if (savePrev == NULL) {
next = (Inst*)node->getFirstInst();
}
else {
next = savePrev->getNextInst();
}
}
else {
assert(whatChanged == Changed_Nothing || whatChanged == Changed_Opnd);
if (changedInBB != Changed_Nothing) {
changedInBB = whatChanged;
}
next = inst->getNextInst();
}
inst = next;
}
return changedInBB;
}
static bool isTypeConversionAllowed(Opnd* fromOpnd, Opnd* toOpnd) {
Type * fromType = fromOpnd->getType();
Type * toType = toOpnd->getType();
bool fromIsGCType = fromType->isObject() || fromType->isManagedPtr();
bool toIsGCType = toType->isObject() || toType->isManagedPtr();
return fromIsGCType == toIsGCType;
}
PeepHoleOpt::Changed PeepHoleOpt::handleInst(Inst* inst)
{
PeepHoleOpt::Changed temp;
// Local propagation
Inst::Opnds opnds(inst, Inst::OpndRole_All);
for (Inst::Opnds::iterator it=opnds.begin();it != opnds.end();it = opnds.next(it)) {
Opnd * opnd=inst->getOpnd(it);
U_32 roles=inst->getOpndRoles(it);
if (roles & Inst::OpndRole_Use) {
if ((roles & Inst::OpndRole_All & Inst::OpndRole_FromEncoder)
&& (roles & Inst::OpndRole_All & Inst::OpndRole_ForIterator)
&& (roles & Inst::OpndRole_Changeable) && ((roles & Inst::OpndRole_Def) == 0)
&& copyMap->has(opnd)) {
if (opnd->getType()->isUnmanagedPtr() && (*copyMap)[opnd]->getType()->isInteger())
(*copyMap)[opnd]->setType(opnd->getType());
inst->setOpnd(it, (*copyMap)[opnd]);
}
}
}
for (Inst::Opnds::iterator it = opnds.begin();it != opnds.end();it = opnds.next(it)) {
Opnd * opnd=inst->getOpnd(it);
U_32 roles=inst->getOpndRoles(it);
if (roles & Inst::OpndRole_Def) {
if (copyMap->has(opnd)) {
if (Log::isEnabled()) Log::out()<<"copy relation DELETED: " << opnd->getFirstId() << "<=" << (*copyMap)[opnd]->getFirstId() <<std::endl;
copyMap->erase(opnd);
}
tempSet->clear();
for(StlHashMap<Opnd*, Opnd*>::iterator iter=copyMap->begin();
iter!=copyMap->end();++iter)
if (iter->second == opnd) {
if (Log::isEnabled()) Log::out()<<"copy relation DELETED: " << iter->first->getFirstId() << "<=" << iter->second->getFirstId() <<std::endl;
tempSet->insert(iter->first);
}
for(StlSet<Opnd*>::iterator iter=tempSet->begin();
iter!=tempSet->end();++iter)
copyMap->erase(*iter);
}
}
if (inst->getMnemonic() == Mnemonic_MOV) {
Inst::Opnds opnds(inst, Inst::OpndRole_All);
Opnd * dst = NULL;
Opnd * src = NULL;
U_32 counterDef = 0;
U_32 counterUse = 0;
for (Inst::Opnds::iterator it=opnds.begin();it!=opnds.end();it=opnds.next(it)) {
Opnd * opnd = inst->getOpnd(it);
U_32 roles = inst->getOpndRoles(it);
if (roles & Inst::OpndRole_Def) {
counterDef++;
dst = opnd;
} else if (roles & Inst::OpndRole_Use) {
counterUse++;
src = opnd;
}
}
if ((counterDef == 1) && (counterUse == 1) && (!dst->hasAssignedPhysicalLocation())) {
bool kindsAreOk = true;
if(src->canBePlacedIn(OpndKind_FPReg) || dst->canBePlacedIn(OpndKind_FPReg)) {
Constraint srcConstr = src->getConstraint(Opnd::ConstraintKind_Calculated);
Constraint dstConstr = dst->getConstraint(Opnd::ConstraintKind_Calculated);
kindsAreOk = ! (srcConstr&dstConstr).isNull();
}
bool typeConvOk = src->getSize() == dst->getSize() && isTypeConversionAllowed(src, dst);
if (typeConvOk && kindsAreOk && ! src->isPlacedIn(OpndKind_Reg)) {
if (copyMap->has(src)) {
(*copyMap)[dst] = (*copyMap)[src];
if (Log::isEnabled()) Log::out()<<"copy relation INSERTED: " << dst->getFirstId() << "<=" << (*copyMap)[src]->getFirstId() <<std::endl;
} else {
(*copyMap)[dst] = src;
if (Log::isEnabled()) Log::out()<<"copy relation INSERTED: " << dst->getFirstId() << "<=" << src->getFirstId() <<std::endl;
}
}
}
}
if (inst->hasKind(Inst::Kind_PseudoInst) && inst->getKind() != Inst::Kind_CopyPseudoInst) {
return Changed_Nothing;
}
Mnemonic mnemonic = inst->getMnemonic();
switch(mnemonic) {
case Mnemonic_MOV:
return handleInst_MOV(inst);
case Mnemonic_CALL:
return handleInst_Call(inst);
case Mnemonic_ADD:
case Mnemonic_ADC:
case Mnemonic_SUB:
case Mnemonic_SBB:
case Mnemonic_NOT:
case Mnemonic_AND:
case Mnemonic_OR:
case Mnemonic_XOR:
case Mnemonic_TEST:
return handleInst_ALU(inst);
case Mnemonic_CMP:
temp = handleInst_CMP(inst);
if ( temp == Changed_Nothing ) {
return handleInst_ALU(inst);
} else {
return temp;
}
case Mnemonic_SETG:
case Mnemonic_SETE:
case Mnemonic_SETNE:
case Mnemonic_SETL:
return handleInst_SETcc(inst);
case Mnemonic_IMUL:
case Mnemonic_MUL:
return handleInst_MUL(inst);
case Mnemonic_MOVSS:
case Mnemonic_MOVSD:
//return handleInst_SSEMov(inst);
case Mnemonic_XORPS:
case Mnemonic_XORPD:
//return handleInst_SSEXor(inst);
default:
break;
}
return Changed_Nothing;
}
PeepHoleOpt::Changed PeepHoleOpt::handleInst_Call(Inst* inst)
{
assert(inst->getMnemonic() == Mnemonic_CALL);
CallInst* callInst = (CallInst*)inst;
unsigned targetOpndIndex = callInst->getTargetOpndIndex();
Opnd* targetOpnd = callInst->getOpnd(targetOpndIndex);
Opnd::RuntimeInfo* ri = targetOpnd->getRuntimeInfo();
Opnd::RuntimeInfo::Kind rt_kind = Opnd::RuntimeInfo::Kind_Null;
if (ri != NULL) {
rt_kind = ri->getKind();
}
if (Opnd::RuntimeInfo::Kind_InternalHelperAddress == rt_kind) {
return handleInst_InternalHelperCall(inst, ri);
}
return Changed_Nothing;
}
PeepHoleOpt::Changed PeepHoleOpt::handleInst_InternalHelperCall(
Inst* inst,
const Opnd::RuntimeInfo* ri)
{
assert(Opnd::RuntimeInfo::Kind_InternalHelperAddress == ri->getKind());
/** The value of the operand is irManager.getInternalHelperInfo((const char*)[0]).pfn */
char* rt_data = (char*)(ri->getValue(0));
if (strcmp(rt_data,"convF4I4") == 0) {
return handleInst_Convert_F2I_D2I(inst);
} else if (strcmp(rt_data,"convF8I4") == 0) {
return handleInst_Convert_F2I_D2I(inst);
}
return Changed_Nothing;
}
PeepHoleOpt::Changed PeepHoleOpt::handleInst_Convert_F2I_D2I(Inst* inst)
{
//
// Inline 'int_value = (int)(float_value or double_value)'
//
Opnd* dst = inst->getOpnd(0);
Opnd* src = inst->getOpnd(2);
Type* srcType = src->getType();
assert(srcType->isSingle() || srcType->isDouble());
assert(dst->getType()->isInt4());
const bool is_dbl = srcType->isDouble();
// Here, we might have to deal with 3 cases with src (_value):
// 1. Unassigned operand - act as if were operating with XMM
// 2. Assigned to FPU - convert to FPU operations, to
// avoid long FPU->mem->XMM chain
// 3. Assigned to XMM - see #1
const bool xmm_way =
!(src->hasAssignedPhysicalLocation() && src->isPlacedIn(OpndKind_FPReg));
if (!xmm_way) {
//TODO: will add FPU later if measurements show it worths trying
return Changed_Nothing;
}
//
//
/*
movss xmm0, val
// presuming the corner cases (NaN, overflow)
// normally happen rare, do conversion first,
// and check for falls later
-- convertNode
cvttss2si eax, xmm0
-- ovfTestNode
// did overflow happen ?
cmp eax, 0x80000000
jne _done // no - go return result
-- testAgainstZeroNode
// test SRC against zero
comiss xmm0, [fp_zero]
// isNaN ?
jp _nan // yes - go load 0
-- testIfBelowNode
// xmm < 0 ?
jb _done // yes - go load MIN_INT. EAX already has it - simply return.
-- loadMaxIntNode
// ok. at this point, XMM is positive and > MAX_INT
// must load MAX_INT which is 0x7fffffff.
// As EAX has 0x80000000, then simply substract 1
sub eax, 1
jmp _done
-- loadZeroNode
_nan:
xor eax, eax
-- nodeNode
_done:
mov result, eax
}
*/
Opnd* fpZeroOpnd = getZeroConst(srcType);
Type* int32type = irManager->getTypeManager().getInt32Type();
Opnd* oneOpnd = irManager->newImmOpnd(int32type, 1);
Opnd* intZeroOpnd = getIntZeroConst();
// 0x8..0 here is not the INT_MIN, but comes from the COMISS
// opcode description instead.
Opnd* minIntOpnd = irManager->newImmOpnd(int32type, 0x80000000);
newSubGFG();
Node* entryNode = getSubCfgEntryNode();
Node* convertNode = newBB();
Node* ovfTestNode = newBB();
Node* testAgainstZeroNode = newBB();
Node* testIfBelowNode = newBB();
Node* loadMaxIntNode = newBB();
Node* loadZeroNode = newBB();
Node* doneNode = newBB();
//
// presuming the corner cases (NaN, overflow)
// normally happen rare, do conversion first,
// and check for falls later
//
connectNodes(entryNode, convertNode);
//
// convert
//
setCurrentNode(convertNode) ;
Mnemonic mn_cvt = is_dbl ? Mnemonic_CVTTSD2SI : Mnemonic_CVTTSS2SI;
/*cvttss2si r32, xmm*/ newInst(mn_cvt, 1, dst, src);
connectNodeTo(ovfTestNode);
setCurrentNode(NULL);
//
// check whether overflow happened
//
setCurrentNode(ovfTestNode);
/*cmp r32, MIN_INT*/ newInst(Mnemonic_CMP, dst, minIntOpnd);
/*jne _done */ newBranch(Mnemonic_JNE, doneNode, testAgainstZeroNode, 0.9, 0.1);
//
setCurrentNode(NULL);
// test SRC against zero
//
setCurrentNode(testAgainstZeroNode);
Mnemonic mn_cmp = is_dbl ? Mnemonic_UCOMISD : Mnemonic_UCOMISS;
/*comiss src, 0. */ newInst(mn_cmp, src, fpZeroOpnd);
/*jp _nan:result=0*/ newBranch(Mnemonic_JP, loadZeroNode, testIfBelowNode);
setCurrentNode(NULL);
//
//
//
setCurrentNode(loadZeroNode);
/*mov r32, 0*/ newInst(Mnemonic_MOV, dst, intZeroOpnd);
/*jmp _done*/ connectNodeTo(doneNode);
setCurrentNode(NULL);
//
// test if we have a huge negative in SRC
//
setCurrentNode(testIfBelowNode);
/*jb _done:*/ newBranch(Mnemonic_JB, doneNode, loadMaxIntNode);
setCurrentNode(NULL);
//
//
//
setCurrentNode(loadMaxIntNode);
/* sub dst, 1*/ newInst(Mnemonic_SUB, dst, oneOpnd);
connectNodeTo(doneNode);
setCurrentNode(NULL);
//
connectNodes(doneNode, getSubCfgReturnNode());
//
propagateSubCFG(inst);
return Changed_Node;
}
static int getMinBit(U_32 val) {
U_32 currentVal = val;
int i=0;
do {
if ((currentVal & 1)) {
return i;
}
currentVal = currentVal >> 1;
} while (++i<32);
return i;
}
static int getMaxBit(U_32 val) {
U_32 currentVal = val;
int i=31;
do {
if ((currentVal & (1<<31))) {
return i;
}
currentVal = currentVal << 1;
} while (--i>=0);
return i;
}
PeepHoleOpt::Changed PeepHoleOpt::handleInst_MOV(Inst* inst)
{
Node* node = inst->getNode();
if (((BasicBlock*)node)->getLayoutSucc() == NULL)
{
Inst *next = inst->getNextInst();
Node *currNode = node;
bool methodMarkerOccur = false;
MethodMarkerPseudoInst* methodMarker = NULL;
// ignoring instructions that have no effect and saving method markers to correct them during optimizations
while (next == NULL || next->getKind() == Inst::Kind_MethodEndPseudoInst || next->getMnemonic() == Mnemonic_JMP)
{
if (next == NULL)
{
currNode = currNode->getOutEdge(Edge::Kind_Unconditional)->getTargetNode();
if (currNode->getKind() == Node::Kind_Exit)
return Changed_Nothing;
next = (Inst*) currNode->getFirstInst();
}
else
{
if (next->getKind() == Inst::Kind_MethodEndPseudoInst)
{
//max 1 saved method marker
if (methodMarkerOccur)
{
return Changed_Nothing;
}
methodMarker = (MethodMarkerPseudoInst*)next;
methodMarkerOccur = true;
}
next = next->getNextInst();
}
}
Inst *jump = next->getNextInst();
bool step1 = true;
currNode = node;
while (currNode != next->getNode())
{
currNode = currNode->getOutEdge(Edge::Kind_Unconditional)->getTargetNode();
if (currNode->getInDegree()!=1)
{
step1 = false;
break;
}
}
// step1:
// ---------------------------------------------
// MOV opnd, opnd2 MOV opnd3, opnd2
// MOV opnd3, opnd ->
// ---------------------------------------------
// nb: applicable if opnd will not be used further
if (step1 && next->getMnemonic() == Mnemonic_MOV)
{
Opnd *movopnd1, *movopnd2, *nextmovopnd1, *nextmovopnd2;
bool isInstCopyPseudo = (inst->getKind() == Inst::Kind_CopyPseudoInst);
if (isInstCopyPseudo)
{
movopnd1 = inst->getOpnd(0);
movopnd2 = inst->getOpnd(1);
}
else
{
Inst::Opnds movuses(inst, Inst::OpndRole_Explicit|Inst::OpndRole_Use);
Inst::Opnds movdefs(inst, Inst::OpndRole_Explicit|Inst::OpndRole_Def);
movopnd1 = inst->getOpnd(movdefs.begin());
movopnd2 = inst->getOpnd(movuses.begin());
}
bool isNextCopyPseudo = (next->getKind() == Inst::Kind_CopyPseudoInst);
if (isNextCopyPseudo)
{
nextmovopnd1 = next->getOpnd(0);
nextmovopnd2 = next->getOpnd(1);
}
else
{
Inst::Opnds nextmovuses(next, Inst::OpndRole_Explicit|Inst::OpndRole_Use);
Inst::Opnds nextmovdefs(next, Inst::OpndRole_Explicit|Inst::OpndRole_Def);
nextmovopnd1 = next->getOpnd(nextmovdefs.begin());
nextmovopnd2 = next->getOpnd(nextmovuses.begin());
}
if (movopnd1->getId() == nextmovopnd2->getId() &&
!isMem(movopnd2) && !isMem(nextmovopnd1) &&
!isMem(movopnd1)
)
{
BitSet ls(irManager->getMemoryManager(), irManager->getOpndCount());
irManager->updateLivenessInfo();
irManager->getLiveAtExit(next->getNode(), ls);
for (Inst* i = (Inst*)next->getNode()->getLastInst(); i!=next; i = i->getPrevInst()) {
irManager->updateLiveness(i, ls);
}
bool dstNotUsed = !ls.getBit(movopnd1->getId());
if (dstNotUsed)
{
if (isInstCopyPseudo && isNextCopyPseudo)
irManager->newCopyPseudoInst(Mnemonic_MOV, nextmovopnd1, movopnd2)->insertAfter(inst);
else
irManager->newInst(Mnemonic_MOV, nextmovopnd1, movopnd2)->insertAfter(inst);
inst->unlink();
next->unlink();
return Changed_Node;
}
}
}
// step2:
// --------------------------------------------------------------
// MOV opnd, 0/1 Jmp smwh/BB1 Jmp smwh/BB1
// CMP opnd, 0 -> CMP opnd, 0 v
// Jcc smwh Jcc smwh
// BB1: BB1:
// --------------------------------------------------------------
// nb: applicable if opnd will not be used further
if (next->getMnemonic() == Mnemonic_CMP && jump!= NULL && (jump->getMnemonic() == Mnemonic_JE ||
jump->getMnemonic() == Mnemonic_JNE))
{
Opnd *movopnd1, *movopnd2;
if (inst->getKind() == Inst::Kind_CopyPseudoInst)
{
movopnd1 = inst->getOpnd(0);
movopnd2 = inst->getOpnd(1);
}
else
{
Inst::Opnds movuses(inst, Inst::OpndRole_Explicit|Inst::OpndRole_Use);
Inst::Opnds movdefs(inst, Inst::OpndRole_Explicit|Inst::OpndRole_Def);
movopnd1 = inst->getOpnd(movdefs.begin());
movopnd2 = inst->getOpnd(movuses.begin());
}
Inst::Opnds cmpuses(next, Inst::OpndRole_Explicit|Inst::OpndRole_Use);
Opnd* cmpopnd1 = next->getOpnd(cmpuses.begin());
Opnd* cmpopnd2 = next->getOpnd(cmpuses.next(cmpuses.begin()));
if (isImm(movopnd2) && (movopnd2->getImmValue() == 0 || movopnd2->getImmValue() == 1) &&
movopnd1->getId() == cmpopnd1->getId() &&
isImm(cmpopnd2) && cmpopnd2->getImmValue() == 0)
{
BitSet ls(irManager->getMemoryManager(), irManager->getOpndCount());
irManager->updateLivenessInfo();
irManager->getLiveAtExit(jump->getNode(), ls);
bool opndNotUsed = !ls.getBit(movopnd1->getId());
if (opndNotUsed)
{
ControlFlowGraph* cfg = irManager->getFlowGraph();
Node* destination = ((BranchInst*)jump)->getTrueTarget();
if ((jump->getMnemonic() == Mnemonic_JNE || movopnd2->getImmValue() == 1) && !(jump->getMnemonic() == Mnemonic_JNE && movopnd2->getImmValue() == 1))
{
destination = ((BranchInst*)jump)->getFalseTarget();
}
if (node->getId() != next->getNode()->getId())
{
if (methodMarkerOccur)
{
inst->getNode()->appendInst(irManager->newMethodEndPseudoInst(methodMarker->getMethodDesc()));
}
inst->unlink();
Edge *outEdge = node->getOutEdge(Edge::Kind_Unconditional);
cfg->replaceEdgeTarget(outEdge, destination, true);
cfg->purgeUnreachableNodes(); // previous successor may become unreachable
}
else
{
cfg->removeEdge(node->getOutEdge(Edge::Kind_True));
cfg->removeEdge(node->getOutEdge(Edge::Kind_False));
cfg->addEdge(node, destination);
inst->unlink();
next->unlink();
jump->unlink();
}
return Changed_Node;
}
}
}
}
return Changed_Nothing;
}
PeepHoleOpt::Changed PeepHoleOpt::handleInst_CMP(Inst* inst) {
assert(inst->getMnemonic()==Mnemonic_CMP);
Inst::Opnds uses(inst, Inst::OpndRole_Explicit | Inst::OpndRole_Use);
Opnd* src1 = inst->getOpnd(uses.begin());
Opnd* src2 = inst->getOpnd(uses.next(uses.begin()));
assert(src1!=NULL && src2!=NULL);
if (isImm(src1)) {
Opnd* tmp = src1; src1 = src2; src2 = tmp;
}
if (isImm(src2) && isReg(src1) && (int)src2->getImmValue() == 0) {
if (Log::isEnabled()) Log::out()<<"I"<<inst->getId()<<" -> CMP with 0"<<std::endl;
irManager->newInst(Mnemonic_TEST, src1, src1)->insertAfter(inst);
inst->unlink();
return Changed_Inst;
}
return Changed_Nothing;
}
PeepHoleOpt::Changed PeepHoleOpt::handleInst_MUL(Inst* inst) {
assert((inst->getMnemonic() == Mnemonic_IMUL) || (inst->getMnemonic() == Mnemonic_MUL));
if (inst->getForm() == Inst::Form_Native) {
return Changed_Nothing;
}
Inst::Opnds defs(inst, Inst::OpndRole_Explicit|Inst::OpndRole_Def);
Opnd* dst1 = inst->getOpnd(defs.begin());
Opnd* dst2 = NULL;
if ((inst->getMnemonic() == Mnemonic_IMUL) && (defs.next(defs.begin()) != defs.end())){
return Changed_Nothing;
}
else { //inst->getMnemonic() == Mnemonic_MUL
dst2 = inst->getOpnd(defs.next(defs.begin()));
if (defs.next(defs.next(defs.begin()))!=defs.end())
return Changed_Nothing;
}
Inst::Opnds uses(inst, Inst::OpndRole_Explicit|Inst::OpndRole_Use);
Opnd* src1= inst->getOpnd(uses.begin());
Opnd* src2= inst->getOpnd(uses.next(uses.begin()));
if (inst->getMnemonic() == Mnemonic_IMUL)
assert(src1!=NULL && src2!=NULL && dst1!=NULL);
else //inst->getMnemonic() == Mnemonic_MUL
assert(src1!=NULL && src2!=NULL && dst1!=NULL && dst2!=NULL);
if (isImm(src1)) {
Opnd* tmp = src1; src1 = src2; src2 = tmp;
}
if (isImm(src2) && irManager->getTypeSize(src2->getType()) <=32) {
int immVal = (int)src2->getImmValue();
if (immVal == 0) {
if (Log::isEnabled()) Log::out()<<"I"<<inst->getId()<<" -> MUL with 0"<<std::endl;
if (inst->getMnemonic() == Mnemonic_IMUL) {
irManager->newCopyPseudoInst(Mnemonic_MOV, dst1, src2)->insertAfter(inst);
} else { //inst->getMnemonic() == Mnemonic_MUL
irManager->newCopyPseudoInst(Mnemonic_MOV, dst1, src2)->insertAfter(inst);
irManager->newCopyPseudoInst(Mnemonic_MOV, dst2, src2)->insertAfter(inst);
}
inst->unlink();
return Changed_Inst;
} else if (immVal == 1) {
if (Log::isEnabled()) Log::out()<<"I"<<inst->getId()<<" -> MUL with 1"<<std::endl;
if (inst->getMnemonic() == Mnemonic_IMUL) {
irManager->newCopyPseudoInst(Mnemonic_MOV, dst1, src1)->insertAfter(inst);
} else { //inst->getMnemonic() == Mnemonic_MUL
Opnd* zero = irManager->newImmOpnd(dst1->getType(), 0);
irManager->newCopyPseudoInst(Mnemonic_MOV, dst1, zero)->insertAfter(inst);
irManager->newCopyPseudoInst(Mnemonic_MOV, dst2, src1)->insertAfter(inst);
}
inst->unlink();
return Changed_Inst;
} else if (immVal == 2) {
if (inst->getMnemonic() == Mnemonic_IMUL) {
if (Log::isEnabled()) Log::out()<<"I"<<inst->getId()<<" -> MUL with 2"<<std::endl;
irManager->newInstEx(Mnemonic_ADD, 1, dst1, src1, src1)->insertAfter(inst);
inst->unlink();
return Changed_Inst;
}
} else {
if (inst->getMnemonic() == Mnemonic_IMUL) {
int minBit=getMinBit(immVal);
int maxBit=getMaxBit(immVal);
if (minBit == maxBit) {
assert(minBit>=2);
if (Log::isEnabled()) Log::out()<<"I"<<inst->getId()<<" -> MUL with 2^"<<minBit<<std::endl;
Type* immType = irManager->getTypeManager().getUInt8Type();
irManager->newCopyPseudoInst(Mnemonic_MOV, dst1, src1)->insertBefore(inst);
irManager->newInst(Mnemonic_SHL, dst1, irManager->newImmOpnd(immType, minBit))->insertBefore(inst);
inst->unlink();
return Changed_Inst;
}
}
}
}
return Changed_Nothing;
}
PeepHoleOpt::Changed PeepHoleOpt::handleInst_ALU(Inst* inst)
{
// The normal form is 'OPERATION left opnd, right operand'
// except for NOT operation.
const Mnemonic mnemonic = inst->getMnemonic();
if (mnemonic == Mnemonic_NOT) {
// No optimizations this time
return Changed_Nothing;
}
// Only these mnemonics have the majestic name of ALUs.
assert(mnemonic == Mnemonic_ADD || mnemonic == Mnemonic_SUB ||
mnemonic == Mnemonic_ADC || mnemonic == Mnemonic_SBB ||
mnemonic == Mnemonic_OR || mnemonic == Mnemonic_XOR ||
mnemonic == Mnemonic_AND ||
mnemonic == Mnemonic_CMP || mnemonic == Mnemonic_TEST);
if (mnemonic == Mnemonic_AND)
{
Inst::Opnds defs(inst, Inst::OpndRole_Explicit|Inst::OpndRole_Def);
Opnd* dst = inst->getOpnd(defs.begin());
Inst::Opnds uses(inst, Inst::OpndRole_Explicit|Inst::OpndRole_Use);
Opnd* src1= inst->getOpnd(uses.begin());
Opnd* src2= inst->getOpnd(uses.next(uses.begin()));
Opnd *newopnd2;
// test can work only with operands having equal sizes
if (isImm(src2) && src2->getSize() != src1->getSize())
newopnd2 = irManager->newImmOpnd(src1->getType(), src2->getImmValue());
else
newopnd2 = src2;
if (!isMem(dst) && !isMem(src1) && !isMem(src2))
{
BitSet ls(irManager->getMemoryManager(), irManager->getOpndCount());
irManager->updateLivenessInfo();
irManager->getLiveAtExit(inst->getNode(), ls);
for (Inst* i = (Inst*)inst->getNode()->getLastInst(); i!=inst; i = i->getPrevInst()) {
irManager->updateLiveness(i, ls);
}
bool dstNotUsed = !ls.getBit(dst->getId());
if (dstNotUsed)
{
// what: AND opnd1, opnd2 => TEST opnd1, opnd2
// nb: applicable if opnd1 will not be used further
if (inst->getForm() == Inst::Form_Extended)
irManager->newInstEx(Mnemonic_TEST, 0, src1, newopnd2)->insertAfter(inst);
else
irManager->newInst(Mnemonic_TEST, src1, newopnd2)->insertAfter(inst);
inst->unlink();
return Changed_Inst;
}
}
} else if (mnemonic == Mnemonic_ADD) {
/* Change "dst=src+0" to "MOV dst, src" if there is another ADD inst followed in the same BB. */
Inst::Opnds defs(inst, Inst::OpndRole_Explicit|Inst::OpndRole_Def);
Opnd* dst = inst->getOpnd(defs.begin());
Inst::Opnds uses(inst, Inst::OpndRole_Explicit|Inst::OpndRole_Use);
Opnd* src1= inst->getOpnd(uses.begin());
Opnd* src2= inst->getOpnd(uses.next(uses.begin()));
bool src1IsZero = false;
bool src2IsZero = false;
if (src1->isPlacedIn(OpndKind_Imm) && (src1->getImmValue() == 0))
src1IsZero = true;
if (src2->isPlacedIn(OpndKind_Imm) && (src2->getImmValue() == 0))
src2IsZero = true;
bool anotherADD = false;
Inst *iter = inst->getNextInst();
while (iter != NULL) {
if (iter->getMnemonic() == Mnemonic_ADC)
break;
if (iter->getMnemonic() == Mnemonic_ADD) {
anotherADD = true;
break;
}
iter = iter->getNextInst();;
}
if (anotherADD) {
if (src1IsZero) {
irManager->newCopyPseudoInst(Mnemonic_MOV, dst, src2)->insertAfter(inst);
inst->unlink();
return Changed_Inst;
} else if (src2IsZero) {
irManager->newCopyPseudoInst(Mnemonic_MOV, dst, src1)->insertAfter(inst);
inst->unlink();
return Changed_Inst;
}
}
}
return Changed_Nothing;
}
PeepHoleOpt::Changed PeepHoleOpt::handleInst_SETcc(Inst* inst)
{
if (((BasicBlock*)inst->getNode())->getLayoutSucc() == NULL)
{
Mnemonic mn = inst->getMnemonic();
Inst* prev = inst->getPrevInst();
Inst *next = inst->getNextInst();
Node *currNode = inst->getNode();
bool methodMarkerOccur = false;
MethodMarkerPseudoInst* methodMarker = NULL;
// ignoring instructions that have no effect and saving method markers to correct them during optimizations
while (next == NULL || next->getKind() == Inst::Kind_MethodEndPseudoInst || next->getMnemonic() == Mnemonic_JMP)
{
if (next == NULL)
{
currNode = currNode->getOutEdge(Edge::Kind_Unconditional)->getTargetNode();
if (currNode->getKind() == Node::Kind_Exit)
return Changed_Nothing;
next = (Inst*) currNode->getFirstInst();
}
else
{
if (next->getKind() == Inst::Kind_MethodEndPseudoInst)
{
//max 1 saved method marker
if (methodMarkerOccur)
{
return Changed_Nothing;
}
methodMarker = (MethodMarkerPseudoInst*)next;
methodMarkerOccur = true;
}
next = next->getNextInst();
}
}
Inst *next2 = next->getNextInst();
bool step1 = true;
currNode = inst->getNode();
while (currNode != next->getNode())
{
currNode = currNode->getOutEdge(Edge::Kind_Unconditional)->getTargetNode();
if (currNode->getInDegree()!=1)
{
step1 = false;
break;
}
}
// step1:
// ------------------------------------------
// MOV opnd, 0 MOV opnd2, 0
// SETcc opnd -> SETcc opnd2
// MOV opnd2, opnd
// ------------------------------------------
// nb: applicable if opnd will not be used further
if (step1 && prev!= NULL && prev->getMnemonic() == Mnemonic_MOV &&
next!= NULL && next->getMnemonic() == Mnemonic_MOV)
{
Opnd *prevopnd1, *prevopnd2, *nextopnd1, *nextopnd2, *setopnd;
if (prev->getKind() == Inst::Kind_CopyPseudoInst)
{
prevopnd1 = prev->getOpnd(0);
prevopnd2 = prev->getOpnd(1);
}
else
{
Inst::Opnds prevuses(prev, Inst::OpndRole_Explicit|Inst::OpndRole_Use);
Inst::Opnds prevdefs(prev, Inst::OpndRole_Explicit|Inst::OpndRole_Def);
prevopnd1 = prev->getOpnd(prevdefs.begin());
prevopnd2 = prev->getOpnd(prevuses.begin());
}
if (next->getKind() == Inst::Kind_CopyPseudoInst)
{
nextopnd1 = next->getOpnd(0);
nextopnd2 = next->getOpnd(1);
}
else
{
Inst::Opnds nextuses(next, Inst::OpndRole_Explicit|Inst::OpndRole_Use);
Inst::Opnds nextdefs(next, Inst::OpndRole_Explicit|Inst::OpndRole_Def);
nextopnd1 = next->getOpnd(nextdefs.begin());
nextopnd2 = next->getOpnd(nextuses.begin());
}
Inst::Opnds setdefs(inst, Inst::OpndRole_Explicit|Inst::OpndRole_Def);
setopnd = inst->getOpnd(setdefs.begin());
if (isReg(nextopnd1) &&
prevopnd1->getId() == setopnd->getId() &&
setopnd->getId() == nextopnd2->getId() &&
isImm(prevopnd2) && prevopnd2->getImmValue() == 0
)
{
BitSet ls(irManager->getMemoryManager(), irManager->getOpndCount());
irManager->updateLivenessInfo();
irManager->getLiveAtExit(next->getNode(), ls);
for (Inst* i = (Inst*)next->getNode()->getLastInst(); i!=next; i = i->getPrevInst()) {
irManager->updateLiveness(i, ls);
}
bool opndNotUsed = !ls.getBit(setopnd->getId());
if (opndNotUsed)
{
if (nextopnd1->getRegName() != RegName_Null &&
Constraint::getAliasRegName(nextopnd1->getRegName(), OpndSize_8) == RegName_Null)
{
nextopnd1->assignRegName(setopnd->getRegName());
}
irManager->newInst(Mnemonic_MOV, nextopnd1, prevopnd2)->insertBefore(inst);
irManager->newInst(mn, nextopnd1)->insertBefore(inst);
prev->unlink();
inst->unlink();
next->unlink();
return Changed_Node;
}
}
}
// step2:
// --------------------------------------------------------------
// MOV opnd, 0 Jcc smwh Jcc smwh
// SETcc opnd -> BB1: v BB1:
// CMP opnd, 0 ...
// Jcc smwh CMP opnd, 0
// BB1: Jcc smwh
// --------------------------------------------------------------
// nb: applicable if opnd will not be used further
// nb: conditions of new jumps are calculated from conditions of old jump and set instructions
if (prev!= NULL && prev->getMnemonic() == Mnemonic_MOV &&
next!= NULL && (next->getMnemonic() == Mnemonic_CMP || next->getMnemonic() == Mnemonic_TEST) &&
next2!= NULL && (next2->getMnemonic() == Mnemonic_JG || next2->getMnemonic() == Mnemonic_JE || next2->getMnemonic() == Mnemonic_JNE) )
{
Opnd* movopnd1;
Opnd* movopnd2;
if (prev->getKind() == Inst::Kind_CopyPseudoInst)
{
movopnd1 = prev->getOpnd(0);
movopnd2 = prev->getOpnd(1);
}
else
{
Inst::Opnds movuses(prev, Inst::OpndRole_Explicit|Inst::OpndRole_Use);
Inst::Opnds movdefs(prev, Inst::OpndRole_Explicit|Inst::OpndRole_Def);
movopnd1 = prev->getOpnd(movdefs.begin());
movopnd2 = prev->getOpnd(movuses.begin());
}
Inst::Opnds cmpuses(next, Inst::OpndRole_Explicit|Inst::OpndRole_Use);
Opnd* cmpopnd1 = next->getOpnd(cmpuses.begin());
Opnd* cmpopnd2 = next->getOpnd(cmpuses.next(cmpuses.begin()));
if (
isImm(movopnd2) && movopnd2->getImmValue() == 0 &&
movopnd1->getId() == cmpopnd1->getId() &&
//case CMP:
(next->getMnemonic() != Mnemonic_CMP || (isImm(cmpopnd2) && cmpopnd2->getImmValue() == 0)) &&
//case TEST:
(next->getMnemonic() != Mnemonic_TEST || cmpopnd1->getId() == cmpopnd2->getId())
)
{
BitSet ls(irManager->getMemoryManager(), irManager->getOpndCount());
irManager->updateLivenessInfo();
irManager->getLiveAtExit(next2->getNode(), ls);
bool opndNotUsed = !ls.getBit(movopnd1->getId());
if (opndNotUsed)
{
BranchInst* br = (BranchInst*) next2;
Mnemonic newjumpmn = Mnemonic_JZ;
if (next2->getMnemonic() == Mnemonic_JE)
{
switch (mn)
{
case Mnemonic_SETG:
newjumpmn = Mnemonic_JLE; break;
case Mnemonic_SETE:
newjumpmn = Mnemonic_JNE; break;
case Mnemonic_SETL:
newjumpmn = Mnemonic_JGE; break;
case Mnemonic_SETNE:
newjumpmn = Mnemonic_JE; break;
default:
assert(0); break;
}
}
else
{
switch (mn)
{
case Mnemonic_SETG:
newjumpmn = Mnemonic_JG; break;
case Mnemonic_SETE:
newjumpmn = Mnemonic_JE; break;
case Mnemonic_SETL:
newjumpmn = Mnemonic_JL; break;
case Mnemonic_SETNE:
newjumpmn = Mnemonic_JNE; break;
default:
assert(0); break;
}
}
if (inst->getNode()->getId() != next->getNode()->getId())
{
ControlFlowGraph* cfg = irManager->getFlowGraph();
cfg->removeEdge(inst->getNode()->getOutEdge(Edge::Kind_Unconditional));
double trueEdgeProb = next2->getNode()->getOutEdge(Edge::Kind_True)->getEdgeProb();
double falseEdgeProb = next2->getNode()->getOutEdge(Edge::Kind_False)->getEdgeProb();
cfg->addEdge(inst->getNode(), br->getTrueTarget(), trueEdgeProb);
cfg->addEdge(inst->getNode(), br->getFalseTarget(), falseEdgeProb);
irManager->newBranchInst(newjumpmn, br->getTrueTarget(), br->getFalseTarget())->insertAfter(inst);
if (methodMarkerOccur)
{
inst->getNode()->appendInst(irManager->newMethodEndPseudoInst(methodMarker->getMethodDesc()));
}
prev->unlink();
inst->unlink();
cfg->purgeUnreachableNodes();
}
else
{
irManager->newBranchInst(newjumpmn, br->getTrueTarget(), br->getFalseTarget())->insertAfter(next2);
prev->unlink();
inst->unlink();
next->unlink();
next2->unlink();
}
return Changed_Node;
}// endif opndNotUsed
}
}
}
return Changed_Nothing;
}
PeepHoleOpt::Changed PeepHoleOpt::handleInst_SSEMov(Inst* inst)
{
assert(inst->getMnemonic() == Mnemonic_MOVSS ||
inst->getMnemonic() == Mnemonic_MOVSD);
const bool isDouble = inst->getMnemonic() == Mnemonic_MOVSD;
if (inst->getOpndCount() != 2) {
// Expected only MOVSS/SD a, b
assert(false);
return Changed_Nothing;
}
Opnd* dst = inst->getOpnd(0);
Opnd* src = inst->getOpnd(1);
//
//
if (isReg(dst) && equals(src, dst)) {
// what: same register moved around
// why: useless thing
inst->unlink();
return Changed_Inst;
}
//
//
if (isReg(dst) && isMem(src)) {
/* what: MOVSS/MOVSD xmmreg, [zero constant from memory] => PXOR xmmreg, xmmreg
why: shorter instruction; no memory access => faster
nb: only works with 64 XMMs
*/
bool isZeroConstant = false;
if (isDouble) {
isZeroConstant = isFPConst(src, (double)0);
}
else {
isZeroConstant = isFPConst(src, (float)0);
}
if (isZeroConstant) {
// PXOR only accepts double registers, convert dst
dst = convertToXmmReg64(dst);
Inst* ii = irManager->newInst(Mnemonic_PXOR, dst, dst);
replaceInst(inst, ii);
return Changed_Inst;
}
//
// fall through to process more
// ||
// vv
} // ~ movss xmm, 0 => pxor xmm,xmm
if (isReg(dst) && isReg(src)) {
/*what: MOVSS/MOVSD reg, reg => MOVQ reg, reg
why: MOVSD has latency=6, MOVSS has latency=4, MOVQ's latency=2
nb: MOVQ only works with 64 xmms
*/
dst = convertToXmmReg64(dst);
src = convertToXmmReg64(src);
Inst* ii = irManager->newInst(Mnemonic_MOVQ, dst, src);
replaceInst(inst, ii);
return Changed_Inst;
}
// We just handled 'both regs' case above, the only possible variant:
assert((isReg(dst)&&isMem(src)) || (isReg(src)&&isMem(dst)));
if (false && isDouble) {
//FIXME: MOVQ with memory gets encoded badly - need to fix in encoder
/*
what: MOVSD => MOVQ
why: faster (? actually, I hope so. Need to double check)
nb: only for xmm64
*/
Inst* ii = irManager->newInst(Mnemonic_MOVQ, dst, src);
replaceInst(inst, ii);
return Changed_Inst;
}
return Changed_Nothing;
}
PeepHoleOpt::Changed PeepHoleOpt::handleInst_SSEXor(Inst* inst)
{
assert(inst->getMnemonic() == Mnemonic_XORPS ||
inst->getMnemonic() == Mnemonic_XORPD);
if (inst->getOpndCount() != 2) {
// Expected only XORPS/PD a, b
assert(false);
return Changed_Nothing;
}
Opnd* dst = inst->getOpnd(0);
Opnd* src = inst->getOpnd(1);
if (isReg(dst) && isReg(src, dst->getRegName())) {
/*what: XORPS/XORPD regN, regN => PXOR regN, regN
why: XORPS/PD used for zero-ing register, but PXOR is faster
(2 ticks on PXOR vs 4 ticks for XORPS/XORPD)
*/
// FIXME: replacing operands on 1 instruction only
// will fail liveness verification if their refcount > 1
//dst = convertToXmmReg64(dst);
//src = convertToXmmReg64(src);
//Inst* ii = irManager->newInst(Mnemonic_PXOR, dst, src);
//replaceInst(inst, ii);
//return Changed_Inst;
}
return Changed_Nothing;
}
}}; // ~namespace Jitrino::Ia32