Hoist CheckType instructions above generic IncRef/LdMem @override-unit-failures

Because we normally find out about a type prediction at hhbc
bounderies, sometimes we generate type checks after we've already done
some generic operations on Cells.  This optimizes a specific pattern
of this that occurs in vector translations.  The code is set up as if
there is more than one since I have another case partially
implemented, but it currently hits issues in both linearscan and
codegen that need to be addressed first, so I split the diff for now.
Esse commit está contido em:
Jordan DeLong
2013-05-18 12:34:54 -07:00
commit de sgolemon
commit ecc6a44af6
17 arquivos alterados com 463 adições e 30 exclusões
+9 -4
Ver Arquivo
@@ -331,6 +331,10 @@ D:T = CheckType<T> S0:Gen -> L
Check that the type of the src S0 is T, and if so copy it to D. If
S0 is not type T, branch to the label L.
CheckTypeMem<T> S0:PtrToGen -> L
If the value pointed to by S0 is not type T, branch to the label L.
GuardLoc<T,localId> S0:FramePtr
Guard that type of the given localId on the frame S0 is a subtype of
@@ -644,11 +648,12 @@ D:T* = LdLocAddr<T, localId> S0:FramePtr
Loads the address of the local slot localId from the frame S0 into D.
D:T = LdMem<T> S0:PtrToGen [ -> L ]
D:T = LdMem<T> S0:PtrToGen S1:ConstInt [ -> L ]
Loads from S0 and puts the value in D. If the optional label L is
specified and the loaded value's type does not match T, this
instruction does not load into D and transfers control to L.
Loads from S0 + S1 (in bytes) and puts the value in D. If the
optional label L is specified and the loaded value's type does not
match T, this instruction does not load into D and transfers control
to L.
D:T = LdProp<T> S0:Obj S1:ConstInt [ -> L ]
+1
Ver Arquivo
@@ -438,6 +438,7 @@ public:
F(bool, HHIRGenerateAsserts, debug) \
F(bool, HHIRDirectExit, true) \
F(bool, HHIRDeadCodeElim, true) \
F(bool, HHIRPredictionOpts, true) \
/* DumpBytecode =1 dumps user php, =2 dumps systemlib & user php */ \
F(int32_t, DumpBytecode, 0) \
F(bool, DumpTC, false) \
+2 -2
Ver Arquivo
@@ -164,8 +164,8 @@ struct Block : boost::noncopyable {
template <class Predicate> void remove_if(Predicate p) {
m_instrs.remove_if(p);
}
void erase(iterator pos) {
m_instrs.erase(pos);
iterator erase(iterator pos) {
return m_instrs.erase(pos);
}
private:
+1 -1
Ver Arquivo
@@ -167,7 +167,7 @@ bool checkTmpsSpanningCalls(Trace* trace, const IRFactory& irFactory) {
/*
* Php calls kill all live temporaries. We can't keep them
* alive across the call because we currently have no
* caller-saved registers in our abi, and all translations
* callee-saved registers in our abi, and all translations
* share the same spill slots.
*/
if (isCall(inst.op())) state.reset();
+1 -1
Ver Arquivo
@@ -34,7 +34,7 @@ bool checkCfg(Trace*, const IRFactory&);
* We can't have SSATmps spanning php-level calls, except for frame
* pointers and constant values.
*
* We have no caller-saved registers in php, and there'd be nowhere to
* We have no callee-saved registers in php, and there'd be nowhere to
* spill these because all translations share the spill space.
*/
bool checkTmpsSpanningCalls(Trace*, const IRFactory&);
+8 -2
Ver Arquivo
@@ -318,8 +318,9 @@ Address CodeGenerator::cgInst(IRInstruction* inst) {
m_rScratch = selectScratchReg(inst);
switch (opc) {
#define O(name, dsts, srcs, flags) \
case name: cg ## name (inst); \
return m_as.code.frontier == start ? nullptr : start;
case name: FTRACE(7, "cg" #name "\n"); \
cg ## name (inst); \
return m_as.code.frontier == start ? nullptr : start;
IR_OPCODES
#undef O
@@ -4025,6 +4026,11 @@ void CodeGenerator::cgCheckType(IRInstruction* inst) {
}
}
void CodeGenerator::cgCheckTypeMem(IRInstruction* inst) {
auto const reg = m_regs[inst->getSrc(0)].getReg();
emitTypeCheck(inst->getTypeParam(), reg[TVOFF(m_type)], inst->getTaken());
}
void CodeGenerator::cgGuardRefs(IRInstruction* inst) {
assert(inst->getNumSrcs() == 6);
-1
Ver Arquivo
@@ -753,7 +753,6 @@ void IRInstruction::convertToMov() {
void IRInstruction::become(IRFactory* factory, IRInstruction* other) {
assert(other->isTransient() || m_numDsts == other->m_numDsts);
assert(!canCSE());
auto& arena = factory->arena();
// Copy all but m_id, m_block, m_listNode, and don't clone
+1
Ver Arquivo
@@ -154,6 +154,7 @@ class FailedIRGen : public std::exception {
#define IR_OPCODES \
/* name dstinfo srcinfo flags */ \
O(CheckType, DParam, S(Gen), C|E|CRc|PRc|P) \
O(CheckTypeMem, ND, S(PtrToGen), E) \
O(GuardLoc, ND, S(FramePtr), E) \
O(GuardStk, D(StkPtr), S(StkPtr), E) \
O(CheckLoc, ND, S(FramePtr), E) \
@@ -161,12 +161,7 @@ struct IRInstruction {
* The target instruction may be transient---we'll clone anything we
* need to keep, using factory for any needed memory.
*
* Note: if you want to use this to replace a CSE-able instruction
* you're probably going to have a bad time. For now it's a
* precondition that the current instruction can't CSE.
*
* Pre: other->isTransient() || numDsts() == other->numDsts()
* Pre: !canCSE()
*/
void become(IRFactory*, IRInstruction* other);
+1 -1
Ver Arquivo
@@ -22,7 +22,7 @@
#include "hphp/runtime/vm/translator/hopt/opt.h"
#include "hphp/runtime/vm/translator/hopt/irfactory.h"
namespace HPHP { namespace JIT {
namespace HPHP { namespace JIT {
TRACE_SET_MOD(hhir);
+143
Ver Arquivo
@@ -0,0 +1,143 @@
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- 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 "hphp/runtime/vm/translator/hopt/mutation.h"
#include "hphp/runtime/vm/translator/hopt/state_vector.h"
namespace HPHP { namespace JIT {
TRACE_SET_MOD(hhir);
//////////////////////////////////////////////////////////////////////
void cloneToBlock(const BlockList& rpoBlocks,
IRFactory* const irFactory,
Block::iterator const first,
Block::iterator const last,
Block* const target) {
assert(isRPOSorted(rpoBlocks));
StateVector<SSATmp,SSATmp*> rewriteMap(irFactory, nullptr);
auto rewriteSources = [&] (IRInstruction* inst) {
for (int i = 0; i < inst->getNumSrcs(); ++i) {
if (auto newTmp = rewriteMap[inst->getSrc(i)]) {
FTRACE(5, " rewrite: {} -> {}\n",
inst->getSrc(i)->toString(),
newTmp->toString());
inst->setSrc(i, newTmp);
}
}
};
auto targetIt = target->skipLabel();
for (auto it = first; it != last; ++it) {
assert(!it->isControlFlowInstruction());
FTRACE(5, "cloneToBlock({}): {}\n", target->getId(), it->toString());
auto const newInst = irFactory->cloneInstruction(&*it);
if (auto const numDests = newInst->getNumDsts()) {
for (int i = 0; i < numDests; ++i) {
FTRACE(5, " add rewrite: {} -> {}\n",
it->getDst(i)->toString(),
newInst->getDst(i)->toString());
rewriteMap[it->getDst(i)] = newInst->getDst(i);
}
}
target->insert(targetIt, newInst);
targetIt = ++target->iteratorTo(newInst);
}
// TODO(#2424504): don't use linear search
auto it = std::find(rpoBlocks.begin(), rpoBlocks.end(), target);
for (; it != rpoBlocks.end(); ++it) {
FTRACE(5, "cloneToBlock: rewriting block {}\n", (*it)->getId());
for (auto& inst : **it) {
FTRACE(5, " rewriting {}\n", inst.toString());
rewriteSources(&inst);
}
}
}
void moveToBlock(Block::iterator const first,
Block::iterator const last,
Block* const target) {
if (first == last) return;
auto const srcBlock = first->getBlock();
auto targetIt = target->skipLabel();
for (auto it = first; it != last;) {
auto const inst = &*it;
assert(!inst->isControlFlowInstruction());
FTRACE(5, "moveToBlock({}): {}\n",
target->getId(),
inst->toString());
it = srcBlock->erase(it);
target->insert(targetIt, inst);
targetIt = ++target->iteratorTo(inst);
}
}
void reflowTypes(Block* const changed, const BlockList& blocks) {
assert(isRPOSorted(blocks));
auto retypeDst = [&] (IRInstruction* inst, int num) {
auto ssa = inst->getDst(num);
/*
* The type of a tmp defined by DefLabel is the union of the
* types of the tmps at each incoming Jmp.
*/
if (inst->op() == DefLabel) {
Type type = Type::Bottom;
inst->getBlock()->forEachSrc(num, [&](IRInstruction*, SSATmp* tmp) {
type = Type::unionOf(type, tmp->type());
});
ssa->setType(type);
return;
}
ssa->setType(outputType(inst, num));
};
auto visit = [&] (IRInstruction* inst) {
for (int i = 0; i < inst->getNumDsts(); ++i) {
auto const ssa = inst->getDst(i);
auto const oldType = ssa->type();
retypeDst(inst, i);
if (ssa->type() != oldType) {
FTRACE(5, "reflowTypes: retyped {} in {}\n", oldType.toString(),
inst->toString());
}
}
};
// TODO(#2424504): don't use linear search
auto it = std::find(blocks.begin(), blocks.end(), changed);
assert(it != blocks.end());
for (; it != blocks.end(); ++it) {
FTRACE(5, "reflowTypes: visiting block {}\n", (*it)->getId());
for (auto& inst : **it) visit(&inst);
}
}
//////////////////////////////////////////////////////////////////////
}}
+84
Ver Arquivo
@@ -0,0 +1,84 @@
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- 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. |
+----------------------------------------------------------------------+
*/
#ifndef incl_HPHP_JIT_MUTATION_H_
#define incl_HPHP_JIT_MUTATION_H_
#include "hphp/runtime/vm/translator/hopt/ir.h"
#include "hphp/runtime/vm/translator/hopt/cfg.h"
namespace HPHP { namespace JIT {
//////////////////////////////////////////////////////////////////////
/*
* Utility routines for mutating the IR during optimization passes.
*/
//////////////////////////////////////////////////////////////////////
/*
* Clone a range of IRInstructions into the front of a target block
* (immediately after its DefLabel, but before its Marker).
*
* Then, for any block reachable from `target', rewrite the sources of
* any instructions that referred to the old destinations of the
* cloned instructions so that they refer to the new destinations this
* function created.
*
* Does not unlink the instructions from the source block.
*
* Pre: The range [first,last) may not contain control flow
* instructions.
* Pre: isRPOSorted(blocks)
*/
void cloneToBlock(const BlockList& blocks,
IRFactory* irFactory,
Block::iterator first,
Block::iterator last,
Block* dst);
/*
* Move a range of IRInstructions to the front of a target block
* (immediately after its DefLabel, but before its Marker).
*
* The instructions are unlinked from their current source block.
*
* Pre: The range [first,last) may not contain control flow
* instructions.
*/
void moveToBlock(Block::iterator first,
Block::iterator last,
Block* dst);
/*
* Given a block where some SSATmps have changed type, walk the
* sub-cfg reachable from that block and recompute the types of any
* tmps that depend on it.
*
* The new types of any changed SSATmps must be related to their old
* types.
*
* Pre: isRPOSorted(blocks)
*/
void reflowTypes(Block*, const BlockList& blocks);
//////////////////////////////////////////////////////////////////////
}}
#endif
+17 -12
Ver Arquivo
@@ -105,24 +105,31 @@ void optimizeTrace(Trace* trace, TraceBuilder* traceBuilder) {
IRFactory* irFactory = traceBuilder->getIrFactory();
auto finishPass = [&](const char* msg) {
dumpTrace(6, trace, msg);
dumpTrace(6, trace, folly::format("after {}", msg).str().c_str());
assert(checkCfg(trace, *irFactory));
assert(checkTmpsSpanningCalls(trace, *irFactory));
if (debug) forEachTraceInst(trace, assertOperandTypes);
};
auto dcePass = [&](const char* which) {
auto doPass = [&](void (*fn)(Trace*, IRFactory*),
const char* msg) {
fn(trace, irFactory);
finishPass(msg);
};
auto dce = [&](const char* which) {
if (!RuntimeOption::EvalHHIRDeadCodeElim) return;
eliminateDeadCode(trace, irFactory);
finishPass(folly::format("after {} DCE", which).str().c_str());
};
if (false && RuntimeOption::EvalHHIRMemOpt) {
optimizeMemoryAccesses(trace, irFactory);
finishPass("after MemeLim");
doPass(optimizeMemoryAccesses, "MemeLim");
}
dce("initial");
if (RuntimeOption::EvalHHIRPredictionOpts) {
doPass(optimizePredictions, "prediction opts");
}
dcePass("initial");
if (RuntimeOption::EvalHHIRExtraOptPass
&& (RuntimeOption::EvalHHIRCse
@@ -132,18 +139,16 @@ void optimizeTrace(Trace* trace, TraceBuilder* traceBuilder) {
// Cleanup any dead code left around by CSE/Simplification
// Ideally, this would be controlled by a flag returned
// by optimzeTrace indicating whether DCE is necessary
dcePass("reoptimize");
dce("reoptimize");
}
if (RuntimeOption::EvalHHIRJumpOpts) {
optimizeJumps(trace, irFactory);
finishPass("jump opts");
dcePass("jump opts");
doPass(optimizeJumps, "jumpopts");
dce("jump opts");
}
if (RuntimeOption::EvalHHIRGenerateAsserts) {
insertAsserts(trace, irFactory);
finishPass("RefCnt asserts");
doPass(insertAsserts, "RefCnt asserts");
}
}
+7
Ver Arquivo
@@ -18,15 +18,20 @@
namespace HPHP { namespace JIT {
//////////////////////////////////////////////////////////////////////
class Trace;
class TraceBuilder;
class IRFactory;
class IRInstruction;
//////////////////////////////////////////////////////////////////////
/*
* The main optimization passes, in the order they run.
*/
void optimizeMemoryAccesses(Trace*, IRFactory*);
void optimizePredictions(Trace*, IRFactory*);
void eliminateDeadCode(Trace*, IRFactory*);
void optimizeJumps(Trace*, IRFactory*);
@@ -35,6 +40,8 @@ void optimizeJumps(Trace*, IRFactory*);
*/
void optimizeTrace(Trace*, TraceBuilder*);
//////////////////////////////////////////////////////////////////////
}}
#endif
@@ -0,0 +1,186 @@
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- 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 <boost/next_prior.hpp>
#include "folly/Optional.h"
#include "folly/Lazy.h"
#include "hphp/runtime/vm/translator/hopt/irfactory.h"
#include "hphp/runtime/vm/translator/hopt/state_vector.h"
#include "hphp/runtime/vm/translator/hopt/ir.h"
#include "hphp/runtime/vm/translator/hopt/cfg.h"
#include "hphp/runtime/vm/translator/hopt/mutation.h"
namespace HPHP { namespace JIT {
TRACE_SET_MOD(hhir);
//////////////////////////////////////////////////////////////////////
namespace {
template<class InputIterator>
bool instructionsAreSinkable(InputIterator first, InputIterator last) {
for (; first != last; ++first) {
switch (first->op()) {
case ReDefSP:
case ReDefGeneratorSP:
case DecRef:
case DecRefNZ:
case Marker:
case IncRef:
case LdMem:
return true;
default:
FTRACE(5, "unsinkable: {}\n", first->toString());
return false;
}
}
not_reached();
}
}
//////////////////////////////////////////////////////////////////////
/*
* Optimizations that try to hoist CheckType instructions so that we
* can specialize code earlier and avoid generic operations.
*/
void optimizePredictions(Trace* const trace, IRFactory* const irFactory) {
FTRACE(5, "PredOpts:vvvvvvvvvvvvvvvvvvvvv\n");
SCOPE_EXIT { FTRACE(5, "PredOpts:^^^^^^^^^^^^^^^^^^^^^\n"); };
auto const sortedBlocks = folly::lazy([&]{
return sortCfg(trace, *irFactory);
});
auto const predecessors = folly::lazy([&]{
return computePredecessors(sortedBlocks());
});
/*
* We frequently generate a generic LdMem, followed by a generic
* IncRef, followed by a CheckType that refines that temporary or
* otherwise branches to a block that's going to do a ReqBindJmp.
* (This happens for e.g. in the case of object property accesses.)
*
* As long as the intervening instructions can be sunk into the exit
* block this optimization will change these sequences to do the
* type check before loading the value. If it fails, we'll do the
* generic LdMem/IncRef on the exit block, otherwise we do
* type-specialized versions.
*/
auto optLdMem = [&] (IRInstruction* checkType, IRInstruction* lastMarker) {
auto const incRef = checkType->getSrc(0)->inst();
if (incRef->op() != IncRef) return;
auto const ldMem = incRef->getSrc(0)->inst();
if (ldMem->op() != LdMem) return;
if (ldMem->getSrc(1)->getValInt() != 0) return;
if (!ldMem->getTypeParam().equals(Type::Cell)) return;
FTRACE(5, "candidate: {}\n", ldMem->toString());
auto const mainBlock = ldMem->getBlock();
auto const exit = checkType->getTaken();
auto const specialized = checkType->getBlock()->getNext();
if (mainBlock != checkType->getBlock()) return;
if (predecessors()[exit->postId()].size() != 1) return;
if (exit->isMain()) return;
auto const sinkFirst = mainBlock->iteratorTo(ldMem);
auto const sinkLast = mainBlock->iteratorTo(checkType);
if (!instructionsAreSinkable(sinkFirst, sinkLast)) return;
FTRACE(5, "all sinkable\n");
auto const& rpoSort = sortedBlocks();
/*
* We are going to add a new CheckTypeMem instruction in front of
* the LdMem. Since CheckTypeMem is a control flow instruction,
* it needs to end the block, so all the code after it has to move
* to either the taken block (exit) or the fallthrough block
* (specialized).
*/
auto const newCheckType = irFactory->gen(
CheckTypeMem,
checkType->getTypeParam(),
checkType->getTaken(),
ldMem->getSrc(0)
);
mainBlock->insert(mainBlock->iteratorTo(ldMem), newCheckType);
// Clone the instructions to the exit before specializing.
cloneToBlock(rpoSort, irFactory, sinkFirst, sinkLast, exit);
exit->insert(exit->skipLabel(), irFactory->cloneInstruction(lastMarker));
/*
* Specialize the LdMem left on the main trace after cloning the
* generic version to the exit. We'll reflowTypes in a sec to get
* everything downstream specialized.
*/
ldMem->setTypeParam(checkType->getTypeParam());
/*
* Replace the old CheckType with a Mov from the result of the
* IncRef so that any uses of its dest point to the correct new
* value. We'll copyProp and get rid of this in a later pass.
*/
irFactory->replace(
checkType,
Mov,
incRef->getDst()
);
// Move the fallthrough case to specialized.
moveToBlock(sinkFirst, boost::next(sinkLast), specialized);
specialized->insert(specialized->skipLabel(),
irFactory->cloneInstruction(lastMarker));
reflowTypes(specialized, rpoSort);
};
/*
* The main loop for this pass.
*
* The individual optimizations called here are expected to change
* block boundaries and potentially add new blocks. They may not
* unlink the block containing the CheckType instruction they are
* visiting.
*/
if (!trace->isMain()) return;
for (Block* b : trace->getBlocks()) {
IRInstruction* lastMarker = nullptr;
for (auto& inst : *b) {
if (inst.op() == Marker) {
lastMarker = &inst;
continue;
}
if (inst.op() == CheckType &&
inst.getSrc(0)->type().equals(Type::Cell)) {
assert(lastMarker);
optLdMem(&inst, lastMarker);
break;
}
}
}
}
//////////////////////////////////////////////////////////////////////
}}
@@ -850,7 +850,7 @@ void TraceBuilder::reoptimize() {
Block* block = blocks.front();
blocks.pop_front();
assert(block->getTrace() == m_trace.get());
FTRACE(5, "Block: {}\n", block->postId());
FTRACE(5, "Block: {}\n", block->getId());
m_trace->push_back(block);
if (m_snapshots[block]) {
+1
Ver Arquivo
@@ -31,6 +31,7 @@ void assert_fail_log(const char* title, const std::string& msg) {
if (s_logger) {
s_logger(title, msg);
}
fprintf(stderr, "Assertion failure: %s\n", msg.c_str());
}
void register_assert_fail_logger(AssertFailLogger l) {