b7d178e674
This diff removes the Marker opcode, replacing it with a BCMarker struct in each IRInstruction. This gives us fewer redundant lines in IRTrace dumps and allows for more straightforward control of which IRInstructions are associated with which bytecodes. I took this opportunity to do some more cleanup of ir dumps as well, and it's now possible to interpOne every codegen punt.
227 linhas
7.2 KiB
C++
227 linhas
7.2 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include <utility>
|
|
|
|
#include <boost/next_prior.hpp>
|
|
|
|
#include "hphp/runtime/vm/jit/ir.h"
|
|
#include "hphp/runtime/vm/jit/opt.h"
|
|
#include "hphp/runtime/vm/jit/irfactory.h"
|
|
|
|
namespace HPHP { namespace JIT {
|
|
|
|
TRACE_SET_MOD(hhir);
|
|
|
|
namespace {
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
// If main trace ends with an unconditional jump, and the target is not
|
|
// reached by any other branch, then copy the target of the jump to the
|
|
// end of the trace
|
|
void elimUnconditionalJump(IRTrace* trace, IRFactory* irFactory) {
|
|
Block* lastBlock = trace->back();
|
|
auto lastInst = lastBlock->backIter(); // iterator to last instruction
|
|
IRInstruction& jmp = *lastInst;
|
|
if (jmp.op() == Jmp_ && jmp.taken()->numPreds() == 1) {
|
|
Block* target = jmp.taken();
|
|
lastBlock->splice(lastInst, target, target->skipHeader(), target->end(),
|
|
lastInst->marker());
|
|
jmp.convertToNop(); // unlink it from its Edge
|
|
lastBlock->erase(lastInst); // delete the jmp
|
|
}
|
|
}
|
|
|
|
Block* findMainExitBlock(IRTrace* trace, IRFactory* irFactory) {
|
|
assert(trace->isMain());
|
|
auto const back = trace->back();
|
|
|
|
/*
|
|
* We require the invariant that the main trace exit comes last in
|
|
* the main trace block list. Right now this is always the case,
|
|
* but this assertion is here in case we want to make changes that
|
|
* affect this ordering. (If we do want to change it, we could use
|
|
* something like the assert below to find the main exit.)
|
|
*/
|
|
if (debug) {
|
|
auto const sorted = rpoSortCfg(trace, *irFactory);
|
|
auto it = sorted.rbegin();
|
|
while (it != sorted.rend() && !(*it)->isMain()) {
|
|
++it;
|
|
}
|
|
assert(it != sorted.rend());
|
|
assert(*it == back && "jumpopts invariant violated");
|
|
}
|
|
|
|
return back;
|
|
}
|
|
|
|
/*
|
|
* Utility class for pattern matching the instructions in a Block,
|
|
* ignoring markers and the label.
|
|
*
|
|
* To use, create a BlockMatcher and call match with a variable-length
|
|
* list of opcode ids.
|
|
*/
|
|
struct BlockMatcher {
|
|
explicit BlockMatcher(Block* block)
|
|
: m_block(block)
|
|
, m_it(block->skipHeader())
|
|
{}
|
|
|
|
bool match() { return true; }
|
|
|
|
template<class... Opcodes>
|
|
bool match(Opcode op, Opcodes... opcs) {
|
|
if (m_it == m_block->end()) return false;
|
|
auto const cur = m_it->op();
|
|
++m_it;
|
|
return cur == op && match(opcs...);
|
|
}
|
|
|
|
private:
|
|
Block* m_block;
|
|
Block::const_iterator m_it;
|
|
};
|
|
|
|
/*
|
|
* Returns whether the supplied block is a "normal" trace exit.
|
|
*
|
|
* That is, it does nothing other than sync ABI registers and bind to
|
|
* the next tracelet.
|
|
*/
|
|
bool isNormalExit(Block* block) {
|
|
return BlockMatcher(block).match(SyncABIRegs, ReqBindJmp);
|
|
}
|
|
|
|
// Returns whether `opc' is a within-tracelet conditional jump that
|
|
// can be folded into a ReqBindJmpFoo instruction.
|
|
bool jccCanBeDirectExit(Opcode opc) {
|
|
return isQueryJmpOp(opc) && (opc != JmpIsType) && (opc != JmpIsNType);
|
|
// TODO(#2404341)
|
|
}
|
|
|
|
/*
|
|
* If main trace ends with a conditional jump with no side-effects on
|
|
* exit, followed by the normal ReqBindJmp sequence, convert the whole
|
|
* thing into a conditional ReqBindJmp.
|
|
*
|
|
* This leads to more efficient code because the service request stubs
|
|
* will patch jumps in the main trace instead of off-trace.
|
|
*/
|
|
void optimizeCondTraceExit(IRTrace* trace, IRFactory* irFactory) {
|
|
FTRACE(5, "CondExit:vvvvvvvvvvvvvvvvvvvvv\n");
|
|
SCOPE_EXIT { FTRACE(5, "CondExit:^^^^^^^^^^^^^^^^^^^^^\n"); };
|
|
|
|
auto const mainExit = findMainExitBlock(trace, irFactory);
|
|
if (!isNormalExit(mainExit)) return;
|
|
|
|
auto const& mainPreds = mainExit->preds();
|
|
if (mainPreds.size() != 1) return;
|
|
|
|
auto const jccBlock = mainPreds.front().from();
|
|
if (!jccCanBeDirectExit(jccBlock->back()->op())) return;
|
|
FTRACE(5, "previous block ends with jccCanBeDirectExit ({})\n",
|
|
opcodeName(jccBlock->back()->op()));
|
|
|
|
auto const jccInst = jccBlock->back();
|
|
auto const jccExitTrace = jccInst->taken();
|
|
if (!isNormalExit(jccExitTrace)) return;
|
|
FTRACE(5, "exit trace is side-effect free\n");
|
|
|
|
auto it = mainExit->backIter();
|
|
auto& reqBindJmp = *(it--);
|
|
auto& syncAbi = *it;
|
|
assert(syncAbi.op() == SyncABIRegs);
|
|
|
|
auto const newOpcode = jmpToReqBindJmp(jccBlock->back()->op());
|
|
ReqBindJccData data;
|
|
data.taken = jccExitTrace->back()->extra<ReqBindJmp>()->offset;
|
|
data.notTaken = reqBindJmp.extra<ReqBindJmp>()->offset;
|
|
|
|
FTRACE(5, "replacing {} with {}\n", jccInst->id(), opcodeName(newOpcode));
|
|
irFactory->replace(
|
|
&reqBindJmp,
|
|
newOpcode,
|
|
data,
|
|
std::make_pair(jccInst->numSrcs(), jccInst->srcs().begin())
|
|
);
|
|
|
|
syncAbi.setMarker(jccInst->marker());
|
|
reqBindJmp.setMarker(jccInst->marker());
|
|
jccInst->convertToNop();
|
|
}
|
|
|
|
/*
|
|
* Look for CheckStk/CheckLoc instructions in the main trace that
|
|
* branch to "normal exits". We can optimize these into the
|
|
* SideExitGuard* instructions that can be patched in place.
|
|
*/
|
|
void optimizeSideExits(IRTrace* trace, IRFactory* irFactory) {
|
|
FTRACE(5, "SideExit:vvvvvvvvvvvvvvvvvvvvv\n");
|
|
SCOPE_EXIT { FTRACE(5, "SideExit:^^^^^^^^^^^^^^^^^^^^^\n"); };
|
|
|
|
forEachInst(trace, [&] (IRInstruction* inst) {
|
|
if (inst->op() != CheckStk && inst->op() != CheckLoc) return;
|
|
auto const exitBlock = inst->taken();
|
|
if (!isNormalExit(exitBlock)) return;
|
|
|
|
auto const syncABI = &*boost::prior(exitBlock->backIter());
|
|
assert(syncABI->op() == SyncABIRegs);
|
|
|
|
FTRACE(5, "converting jump ({}) to side exit\n",
|
|
inst->id());
|
|
|
|
auto const isStack = inst->op() == CheckStk;
|
|
auto const fp = syncABI->src(0);
|
|
auto const sp = syncABI->src(1);
|
|
|
|
SideExitGuardData data;
|
|
data.checkedSlot = isStack
|
|
? inst->extra<CheckStk>()->offset
|
|
: inst->extra<CheckLoc>()->locId;
|
|
data.taken = exitBlock->back()->extra<ReqBindJmp>()->offset;
|
|
|
|
auto const block = inst->block();
|
|
block->insert(block->iteratorTo(inst),
|
|
irFactory->cloneInstruction(syncABI));
|
|
|
|
irFactory->replace(
|
|
inst,
|
|
isStack ? SideExitGuardStk : SideExitGuardLoc,
|
|
inst->typeParam(),
|
|
data,
|
|
isStack ? sp : fp
|
|
);
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
void optimizeJumps(IRTrace* trace, IRFactory* irFactory) {
|
|
elimUnconditionalJump(trace, irFactory);
|
|
|
|
if (RuntimeOption::EvalHHIRDirectExit) {
|
|
optimizeCondTraceExit(trace, irFactory);
|
|
optimizeSideExits(trace, irFactory);
|
|
}
|
|
}
|
|
|
|
}}
|