a9e58686ab
This diff creates IRTranslator, which creates and uses an HhbcTranslator to implement the translate* methods. It can be used independently of Translator or TranslatorX64 (it isn't yet but my next region compiler diff uses it). I also moved a bunch of methods out of inappropriate classes and changed the type guard/assert methods in HhbcTranslator to use RegionDesc::Location instead of Transl::Location and fixes a local tracking issue in translateRegion.
1082 linhas
32 KiB
C++
1082 linhas
32 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 "hphp/runtime/vm/jit/trace-builder.h"
|
|
|
|
#include "folly/ScopeGuard.h"
|
|
|
|
#include "hphp/util/trace.h"
|
|
#include "hphp/runtime/vm/jit/target-cache.h"
|
|
#include "hphp/runtime/vm/jit/ir-factory.h"
|
|
|
|
namespace HPHP { namespace JIT {
|
|
|
|
TRACE_SET_MOD(hhir);
|
|
|
|
TraceBuilder::TraceBuilder(Offset initialBcOffset,
|
|
Offset initialSpOffsetFromFp,
|
|
IRFactory& irFactory,
|
|
const Func* func)
|
|
: m_irFactory(irFactory)
|
|
, m_simplifier(this)
|
|
, m_mainTrace(makeTrace(func, initialBcOffset))
|
|
, m_curTrace(m_mainTrace.get())
|
|
, m_curBlock(nullptr)
|
|
, m_enableCse(false)
|
|
, m_enableSimplification(false)
|
|
, m_snapshots(&irFactory, nullptr)
|
|
, m_spValue(nullptr)
|
|
, m_fpValue(nullptr)
|
|
, m_spOffset(initialSpOffsetFromFp)
|
|
, m_thisIsAvailable(false)
|
|
, m_refCountedMemValue(nullptr)
|
|
, m_localValues(func->numLocals(), nullptr)
|
|
, m_localTypes(func->numLocals(), Type::None)
|
|
{
|
|
m_curFunc = m_irFactory.cns(func);
|
|
if (RuntimeOption::EvalHHIRGenOpts) {
|
|
m_enableCse = RuntimeOption::EvalHHIRCse;
|
|
m_enableSimplification = RuntimeOption::EvalHHIRSimplification;
|
|
}
|
|
}
|
|
|
|
TraceBuilder::~TraceBuilder() {
|
|
for (State* state : m_snapshots) delete state;
|
|
}
|
|
|
|
/**
|
|
* Checks if the given SSATmp, or any of its aliases, is available in
|
|
* any VM location, including locals and the This pointer.
|
|
*/
|
|
bool TraceBuilder::isValueAvailable(SSATmp* tmp) const {
|
|
while (true) {
|
|
if (m_refCountedMemValue == tmp) return true;
|
|
if (anyLocalHasValue(tmp)) return true;
|
|
if (callerHasValueAvailable(tmp)) return true;
|
|
|
|
IRInstruction* srcInstr = tmp->inst();
|
|
Opcode srcOpcode = srcInstr->op();
|
|
|
|
if (srcOpcode == LdThis) return true;
|
|
|
|
if (srcInstr->isPassthrough()) {
|
|
tmp = srcInstr->getPassthroughValue();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
SSATmp* TraceBuilder::genDefUninit() {
|
|
return gen(DefConst, Type::Uninit, ConstData(0));
|
|
}
|
|
|
|
SSATmp* TraceBuilder::genDefInitNull() {
|
|
return gen(DefConst, Type::InitNull, ConstData(0));
|
|
}
|
|
|
|
SSATmp* TraceBuilder::genDefNull() {
|
|
return gen(DefConst, Type::Null, ConstData(0));
|
|
}
|
|
|
|
SSATmp* TraceBuilder::genPtrToInitNull() {
|
|
return gen(DefConst, Type::PtrToUninit, ConstData(&null_variant));
|
|
}
|
|
|
|
SSATmp* TraceBuilder::genPtrToUninit() {
|
|
return gen(DefConst, Type::PtrToInitNull, ConstData(&init_null_variant));
|
|
}
|
|
|
|
SSATmp* TraceBuilder::genDefNone() {
|
|
return gen(DefConst, Type::None, ConstData(0));
|
|
}
|
|
|
|
void TraceBuilder::trackDefInlineFP(IRInstruction* inst) {
|
|
auto const target = inst->extra<DefInlineFP>()->target;
|
|
auto const savedSPOff = inst->extra<DefInlineFP>()->retSPOff;
|
|
auto const calleeFP = inst->dst();
|
|
auto const calleeSP = inst->src(0);
|
|
auto const savedSP = inst->src(1);
|
|
|
|
// Saved tracebuilder state will include the "return" fp/sp.
|
|
// Whatever the current fpValue is is good enough, but we have to be
|
|
// passed in the StkPtr that represents the stack prior to the
|
|
// ActRec being allocated.
|
|
m_spOffset = savedSPOff;
|
|
m_spValue = savedSP;
|
|
|
|
auto const stackValues = collectStackValues(m_spValue, m_spOffset);
|
|
for (DEBUG_ONLY auto& val : stackValues) {
|
|
FTRACE(4, " marking caller stack value available: {}\n",
|
|
val->toString());
|
|
}
|
|
|
|
m_inlineSavedStates.push_back(createState());
|
|
|
|
/*
|
|
* Set up the callee state.
|
|
*
|
|
* We set m_thisIsAvailable to true on any object method, because we
|
|
* just don't inline calls to object methods with a null $this.
|
|
*/
|
|
m_fpValue = calleeFP;
|
|
m_spValue = calleeSP;
|
|
m_thisIsAvailable = target->cls() != nullptr && !target->isStatic();
|
|
m_curFunc = cns(target);
|
|
|
|
/*
|
|
* Keep the outer locals somewhere for isValueAvailable() to know
|
|
* about their liveness, to help with incref/decref elimination.
|
|
*/
|
|
m_callerAvailableValues.insert(m_callerAvailableValues.end(),
|
|
m_localValues.begin(),
|
|
m_localValues.end());
|
|
m_callerAvailableValues.insert(m_callerAvailableValues.end(),
|
|
stackValues.begin(),
|
|
stackValues.end());
|
|
|
|
m_localValues.clear();
|
|
m_localTypes.clear();
|
|
m_localValues.resize(target->numLocals(), nullptr);
|
|
m_localTypes.resize(target->numLocals(), Type::None);
|
|
}
|
|
|
|
void TraceBuilder::trackInlineReturn(IRInstruction* inst) {
|
|
useState(std::move(m_inlineSavedStates.back()));
|
|
m_inlineSavedStates.pop_back();
|
|
}
|
|
|
|
void TraceBuilder::updateTrackedState(IRInstruction* inst) {
|
|
// We don't track state for any trace other than the main trace.
|
|
if (m_savedTraces.size() > 0) return;
|
|
|
|
Opcode opc = inst->op();
|
|
// Update tracked state of local values/types, stack/frame pointer, CSE, etc.
|
|
|
|
// kill tracked memory values
|
|
if (inst->mayModifyRefs()) {
|
|
m_refCountedMemValue = nullptr;
|
|
}
|
|
|
|
switch (opc) {
|
|
case DefInlineFP: trackDefInlineFP(inst); break;
|
|
case InlineReturn: trackInlineReturn(inst); break;
|
|
|
|
case Call:
|
|
m_spValue = inst->dst();
|
|
// A call pops the ActRec and pushes a return value.
|
|
m_spOffset -= kNumActRecCells;
|
|
m_spOffset += 1;
|
|
assert(m_spOffset >= 0);
|
|
killCse();
|
|
killLocals();
|
|
break;
|
|
|
|
case CallArray:
|
|
m_spValue = inst->dst();
|
|
// A CallArray pops the ActRec an array arg and pushes a return value.
|
|
m_spOffset -= kNumActRecCells;
|
|
assert(m_spOffset >= 0);
|
|
killCse();
|
|
killLocals();
|
|
break;
|
|
|
|
case ContEnter:
|
|
killCse();
|
|
killLocals();
|
|
break;
|
|
|
|
case DefFP:
|
|
case FreeActRec:
|
|
m_fpValue = inst->dst();
|
|
break;
|
|
|
|
case ReDefGeneratorSP:
|
|
case DefSP:
|
|
case ReDefSP:
|
|
m_spValue = inst->dst();
|
|
m_spOffset = inst->extra<StackOffset>()->offset;
|
|
break;
|
|
|
|
case AssertStk:
|
|
case AssertStkVal:
|
|
case CastStk:
|
|
case CoerceStk:
|
|
case CheckStk:
|
|
case GuardStk:
|
|
case ExceptionBarrier:
|
|
m_spValue = inst->dst();
|
|
break;
|
|
|
|
case SpillStack: {
|
|
m_spValue = inst->dst();
|
|
// Push the spilled values but adjust for the popped values
|
|
int64_t stackAdjustment = inst->src(1)->getValInt();
|
|
m_spOffset -= stackAdjustment;
|
|
m_spOffset += spillValueCells(inst);
|
|
break;
|
|
}
|
|
|
|
case SpillFrame:
|
|
case CufIterSpillFrame:
|
|
m_spValue = inst->dst();
|
|
m_spOffset += kNumActRecCells;
|
|
break;
|
|
|
|
case InterpOne: {
|
|
m_spValue = inst->dst();
|
|
auto const& extra = *inst->extra<InterpOne>();
|
|
int64_t stackAdjustment = extra.cellsPopped - extra.cellsPushed;
|
|
// push the return value if any and adjust for the popped values
|
|
m_spOffset -= stackAdjustment;
|
|
break;
|
|
}
|
|
|
|
case StProp:
|
|
case StPropNT:
|
|
// fall through to StMem; stored value is the same arg number (2)
|
|
case StMem:
|
|
case StMemNT:
|
|
m_refCountedMemValue = inst->src(2);
|
|
break;
|
|
|
|
case LdMem:
|
|
case LdProp:
|
|
case LdRef:
|
|
m_refCountedMemValue = inst->dst();
|
|
break;
|
|
|
|
case StRefNT:
|
|
case StRef: {
|
|
m_refCountedMemValue = inst->src(2);
|
|
SSATmp* newRef = inst->dst();
|
|
SSATmp* prevRef = inst->src(0);
|
|
// update other tracked locals that also contain prevRef
|
|
updateLocalRefValues(prevRef, newRef);
|
|
break;
|
|
}
|
|
|
|
case StLocNT:
|
|
case StLoc:
|
|
setLocalValue(inst->extra<LocalId>()->locId, inst->src(1));
|
|
break;
|
|
|
|
case LdLoc:
|
|
setLocalValue(inst->extra<LdLoc>()->locId, inst->dst());
|
|
break;
|
|
|
|
case OverrideLoc:
|
|
// If changing the inner type of a boxed local, also drop the
|
|
// information about inner types for any other boxed locals.
|
|
if (inst->typeParam().isBoxed()) {
|
|
dropLocalRefsInnerTypes();
|
|
}
|
|
// fallthrough
|
|
case AssertLoc:
|
|
case GuardLoc:
|
|
setLocalType(inst->extra<LocalId>()->locId,
|
|
inst->typeParam());
|
|
break;
|
|
|
|
case OverrideLocVal:
|
|
setLocalValue(inst->extra<LocalId>()->locId, inst->src(1));
|
|
break;
|
|
|
|
case SmashLocals:
|
|
clearLocals();
|
|
break;
|
|
|
|
case IterInitK:
|
|
case WIterInitK:
|
|
// kill the locals to which this instruction stores iter's key and value
|
|
killLocalValue(inst->src(3)->getValInt());
|
|
killLocalValue(inst->src(4)->getValInt());
|
|
break;
|
|
|
|
case IterInit:
|
|
case WIterInit:
|
|
// kill the local to which this instruction stores iter's value
|
|
killLocalValue(inst->src(3)->getValInt());
|
|
break;
|
|
|
|
case IterNextK:
|
|
case WIterNextK:
|
|
// kill the locals to which this instruction stores iter's key and value
|
|
killLocalValue(inst->src(2)->getValInt());
|
|
killLocalValue(inst->src(3)->getValInt());
|
|
break;
|
|
|
|
case IterNext:
|
|
case WIterNext:
|
|
// kill the local to which this instruction stores iter's value
|
|
killLocalValue(inst->src(2)->getValInt());
|
|
break;
|
|
|
|
case LdThis:
|
|
m_thisIsAvailable = true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (VectorEffects::supported(inst)) {
|
|
VectorEffects::get(inst,
|
|
[&](uint32_t id, SSATmp* val) { // storeLocalValue
|
|
setLocalValue(id, val);
|
|
},
|
|
[&](uint32_t id, Type t) { // setLocalType
|
|
setLocalType(id, t);
|
|
});
|
|
}
|
|
|
|
if (inst->modifiesStack()) {
|
|
m_spValue = inst->modifiedStkPtr();
|
|
}
|
|
|
|
// update the CSE table
|
|
if (m_enableCse && inst->canCSE()) {
|
|
cseInsert(inst);
|
|
}
|
|
|
|
// if the instruction kills any of its sources, remove them from the
|
|
// CSE table
|
|
if (inst->killsSources()) {
|
|
for (int i = 0; i < inst->numSrcs(); ++i) {
|
|
if (inst->killsSource(i)) {
|
|
cseKill(inst->src(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
// save a copy of the current state for each successor.
|
|
if (Block* target = inst->taken()) saveState(target);
|
|
}
|
|
|
|
std::unique_ptr<TraceBuilder::State> TraceBuilder::createState() const {
|
|
std::unique_ptr<State> state(new State);
|
|
state->spValue = m_spValue;
|
|
state->fpValue = m_fpValue;
|
|
state->curFunc = m_curFunc;
|
|
state->spOffset = m_spOffset;
|
|
state->thisAvailable = m_thisIsAvailable;
|
|
state->localValues = m_localValues;
|
|
state->localTypes = m_localTypes;
|
|
state->callerAvailableValues = m_callerAvailableValues;
|
|
state->refCountedMemValue = m_refCountedMemValue;
|
|
state->curMarker = m_curMarker;
|
|
assert(state->curMarker.valid());
|
|
return state;
|
|
}
|
|
|
|
/*
|
|
* Save current state for block. If this is the first time saving state
|
|
* for block, create a new snapshot. Otherwise merge the current state into
|
|
* the existing snapshot.
|
|
*/
|
|
void TraceBuilder::saveState(Block* block) {
|
|
if (State* state = m_snapshots[block]) {
|
|
mergeState(state);
|
|
} else {
|
|
m_snapshots[block] = createState().release();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Merge current state into state. Frame pointers and stack depth must match.
|
|
* If the stack pointer tmps are different, clear the tracked value (we can
|
|
* make a new one, given fp and spOffset).
|
|
*
|
|
* thisIsAvailable remains true if it's true in both states.
|
|
* local variable values are preserved if the match in both states.
|
|
* types are combined using Type::unionOf.
|
|
*/
|
|
void TraceBuilder::mergeState(State* state) {
|
|
// cannot merge fp or spOffset state, so assert they match
|
|
assert(state->fpValue == m_fpValue);
|
|
assert(state->spOffset == m_spOffset);
|
|
assert(state->curFunc == m_curFunc);
|
|
if (state->spValue != m_spValue) {
|
|
// we have two different sp definitions but we know they're equal
|
|
// because spOffset matched.
|
|
state->spValue = nullptr;
|
|
}
|
|
// this is available iff it's available in both states
|
|
state->thisAvailable &= m_thisIsAvailable;
|
|
for (unsigned i = 0, n = state->localValues.size(); i < n; ++i) {
|
|
// preserve local values if they're the same in both states,
|
|
// This would be the place to insert phi nodes (jmps+deflabels) if we want
|
|
// to avoid clearing state, which triggers a downstream reload.
|
|
if (state->localValues[i] != m_localValues[i]) {
|
|
state->localValues[i] = nullptr;
|
|
}
|
|
}
|
|
for (unsigned i = 0, n = state->localTypes.size(); i < n; ++i) {
|
|
// combine types using Type::unionOf(), but handle Type::None here (t2135185)
|
|
Type t1 = state->localTypes[i];
|
|
Type t2 = m_localTypes[i];
|
|
state->localTypes[i] = (t1 == Type::None || t2 == Type::None) ? Type::None :
|
|
Type::unionOf(t1, t2);
|
|
}
|
|
// Reference counted memory value is available only if it is available on both
|
|
// paths
|
|
if (state->refCountedMemValue != m_refCountedMemValue) {
|
|
state->refCountedMemValue = nullptr;
|
|
}
|
|
|
|
// Don't attempt to continue tracking caller's available values.
|
|
state->callerAvailableValues.clear();
|
|
|
|
// We should not be merging states that have different hhbc bytecode
|
|
// boundaries.
|
|
assert(m_curMarker.valid() && state->curMarker == m_curMarker);
|
|
}
|
|
|
|
void TraceBuilder::useState(std::unique_ptr<State> state) {
|
|
m_spValue = state->spValue;
|
|
m_fpValue = state->fpValue;
|
|
m_spOffset = state->spOffset;
|
|
m_curFunc = state->curFunc;
|
|
m_thisIsAvailable = state->thisAvailable;
|
|
m_refCountedMemValue = state->refCountedMemValue;
|
|
m_localValues = std::move(state->localValues);
|
|
m_localTypes = std::move(state->localTypes);
|
|
m_callerAvailableValues = std::move(state->callerAvailableValues);
|
|
m_curMarker = state->curMarker;
|
|
// If spValue is null, we merged two different but equivalent values.
|
|
// Define a new sp using the known-good spOffset.
|
|
if (!m_spValue) {
|
|
gen(DefSP, StackOffset(m_spOffset), m_fpValue);
|
|
}
|
|
}
|
|
|
|
void TraceBuilder::useState(Block* block) {
|
|
assert(m_snapshots[block]);
|
|
std::unique_ptr<State> state(m_snapshots[block]);
|
|
m_snapshots[block] = nullptr;
|
|
useState(std::move(state));
|
|
}
|
|
|
|
void TraceBuilder::clearLocals() {
|
|
for (uint32_t i = 0; i < m_localValues.size(); i++) {
|
|
m_localValues[i] = nullptr;
|
|
}
|
|
for (uint32_t i = 0; i < m_localTypes.size(); i++) {
|
|
m_localTypes[i] = Type::None;
|
|
}
|
|
}
|
|
|
|
void TraceBuilder::clearTrackedState() {
|
|
killCse(); // clears m_cseHash
|
|
clearLocals();
|
|
m_callerAvailableValues.clear();
|
|
m_spValue = m_fpValue = nullptr;
|
|
m_spOffset = 0;
|
|
m_thisIsAvailable = false;
|
|
m_refCountedMemValue = nullptr;
|
|
for (auto i = m_snapshots.begin(), end = m_snapshots.end(); i != end; ++i) {
|
|
delete *i;
|
|
*i = nullptr;
|
|
}
|
|
m_curMarker = BCMarker();
|
|
}
|
|
|
|
void TraceBuilder::appendInstruction(IRInstruction* inst, Block* block) {
|
|
assert(inst->marker().valid());
|
|
Opcode opc = inst->op();
|
|
if (opc != Nop && opc != DefConst) {
|
|
block->push_back(inst);
|
|
}
|
|
}
|
|
|
|
void TraceBuilder::appendInstruction(IRInstruction* inst) {
|
|
if (m_curWhere) {
|
|
// We have a specific position to insert instructions.
|
|
assert(!inst->isBlockEnd());
|
|
auto& it = m_curWhere.get();
|
|
it = m_curBlock->insert(it, inst);
|
|
++it;
|
|
return;
|
|
}
|
|
|
|
Block* block = m_curTrace->back();
|
|
if (!block->empty()) {
|
|
IRInstruction* prev = block->back();
|
|
if (prev->isBlockEnd()) {
|
|
// start a new block
|
|
Block* next = m_irFactory.defBlock(m_curFunc->getValFunc());
|
|
m_curTrace->push_back(next);
|
|
if (!prev->isTerminal()) {
|
|
// new block is reachable from old block so link it.
|
|
block->setNext(next);
|
|
}
|
|
block = next;
|
|
}
|
|
}
|
|
appendInstruction(inst, block);
|
|
updateTrackedState(inst);
|
|
}
|
|
|
|
void TraceBuilder::appendBlock(Block* block) {
|
|
if (!m_curTrace->back()->back()->isTerminal()) {
|
|
// previous instruction falls through; merge current state with block.
|
|
saveState(block);
|
|
}
|
|
m_curTrace->push_back(block);
|
|
useState(block);
|
|
}
|
|
|
|
CSEHash* TraceBuilder::cseHashTable(IRInstruction* inst) {
|
|
return inst->op() == DefConst ? &m_irFactory.constTable() :
|
|
&m_cseHash;
|
|
}
|
|
|
|
void TraceBuilder::cseInsert(IRInstruction* inst) {
|
|
cseHashTable(inst)->insert(inst->dst());
|
|
}
|
|
|
|
void TraceBuilder::cseKill(SSATmp* src) {
|
|
if (src->inst()->canCSE()) {
|
|
cseHashTable(src->inst())->erase(src);
|
|
}
|
|
}
|
|
|
|
SSATmp* TraceBuilder::cseLookup(IRInstruction* inst,
|
|
const folly::Optional<IdomVector>& idoms) {
|
|
auto tmp = cseHashTable(inst)->lookup(inst);
|
|
if (tmp && idoms) {
|
|
// During a reoptimize pass, we need to make sure that any values
|
|
// we want to reuse for CSE are only reused in blocks dominated by
|
|
// the block that defines it.
|
|
if (!dominates(tmp->inst()->block(), inst->block(), *idoms)) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
SSATmp* TraceBuilder::preOptimizeCheckLoc(IRInstruction* inst) {
|
|
auto const locId = inst->extra<CheckLoc>()->locId;
|
|
|
|
if (auto const prevValue = getLocalValue(locId)) {
|
|
always_assert(false && "WTF");
|
|
return gen(
|
|
CheckType, inst->typeParam(), inst->taken(), prevValue
|
|
);
|
|
}
|
|
|
|
auto const prevType = getLocalType(locId);
|
|
if (prevType != Type::None) {
|
|
always_assert(false && "WTF2");
|
|
// It doesn't make sense to be checking something that's deemed to
|
|
// fail.
|
|
assert(prevType == inst->typeParam());
|
|
inst->convertToNop();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* TraceBuilder::preOptimizeAssertLoc(IRInstruction* inst) {
|
|
auto const locId = inst->extra<AssertLoc>()->locId;
|
|
auto const prevType = getLocalType(locId);
|
|
auto const typeParam = inst->typeParam();
|
|
|
|
if (!prevType.equals(Type::None) && !typeParam.strictSubtypeOf(prevType)) {
|
|
assert(prevType.subtypeOf(typeParam));
|
|
inst->convertToNop();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* TraceBuilder::preOptimizeLdThis(IRInstruction* inst) {
|
|
if (isThisAvailable()) inst->setTaken(nullptr);
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* TraceBuilder::preOptimizeLdCtx(IRInstruction* inst) {
|
|
if (isThisAvailable()) return gen(LdThis, m_fpValue);
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* TraceBuilder::preOptimizeDecRef(IRInstruction* inst) {
|
|
/*
|
|
* Refcount optimization:
|
|
*
|
|
* If the decref'ed value is guaranteed to be available after the decref,
|
|
* generate DecRefNZ instead of DecRef.
|
|
*
|
|
* This is safe WRT copy-on-write because all the instructions that
|
|
* could cause a COW return a new SSATmp that will replace the
|
|
* tracked state that we are using to determine the value is still
|
|
* available. I.e. by the time they get to the DecRef we won't see
|
|
* it in isValueAvailable anymore and won't convert to DecRefNZ.
|
|
*/
|
|
auto const srcInst = inst->src(0)->inst();
|
|
if (srcInst->op() == IncRef) {
|
|
if (isValueAvailable(srcInst->src(0))) {
|
|
inst->setOpcode(DecRefNZ);
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* TraceBuilder::preOptimizeDecRefThis(IRInstruction* inst) {
|
|
/*
|
|
* If $this is available, convert to an instruction sequence that
|
|
* doesn't need to test if it's already live.
|
|
*/
|
|
if (isThisAvailable()) {
|
|
auto const thiss = gen(LdThis, m_fpValue);
|
|
auto const thisInst = thiss->inst();
|
|
|
|
/*
|
|
* DecRef optimization for $this in an inlined frame: if a caller
|
|
* local contains the $this, we know it can't go to zero and can
|
|
* switch DecRef to DecRefNZ.
|
|
*
|
|
* It's ok not to do DecRefThis (which normally nulls out the ActRec
|
|
* $this), because there is still a reference to it in the caller
|
|
* frame, so debug_backtrace() can't see a non-live pointer value.
|
|
*/
|
|
if (thisInst->op() == IncRef &&
|
|
callerHasValueAvailable(thisInst->src(0))) {
|
|
gen(DecRefNZ, thiss);
|
|
inst->convertToNop();
|
|
return nullptr;
|
|
}
|
|
|
|
assert(inst->src(0) == m_fpValue);
|
|
gen(DecRef, thiss);
|
|
inst->convertToNop();
|
|
return nullptr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* TraceBuilder::preOptimizeDecRefLoc(IRInstruction* inst) {
|
|
auto const locId = inst->extra<DecRefLoc>()->locId;
|
|
|
|
/*
|
|
* Refine the type if we can.
|
|
*
|
|
* We can't really rely on the types held in the boxed values since
|
|
* aliasing stores may change them, and we only guard during LdRef.
|
|
* So we have to change any boxed type to BoxedCell.
|
|
*/
|
|
auto knownType = getLocalType(locId);
|
|
if (knownType.isBoxed()) {
|
|
knownType = Type::BoxedCell;
|
|
}
|
|
if (knownType != Type::None) { // TODO(#2135185)
|
|
inst->setTypeParam(
|
|
Type::mostRefined(knownType, inst->typeParam())
|
|
);
|
|
}
|
|
|
|
/*
|
|
* If we have the local value in flight, use a DecRef on it instead
|
|
* of doing it in memory.
|
|
*/
|
|
if (auto tmp = getLocalValue(locId)) {
|
|
gen(DecRef, tmp);
|
|
inst->convertToNop();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* TraceBuilder::preOptimizeLdLoc(IRInstruction* inst) {
|
|
auto const locId = inst->extra<LdLoc>()->locId;
|
|
if (auto tmp = getLocalValue(locId)) {
|
|
return tmp;
|
|
}
|
|
if (getLocalType(locId) != Type::None) { // TODO(#2135185)
|
|
inst->setTypeParam(
|
|
Type::mostRefined(getLocalType(locId), inst->typeParam())
|
|
);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* TraceBuilder::preOptimizeLdLocAddr(IRInstruction* inst) {
|
|
auto const locId = inst->extra<LdLocAddr>()->locId;
|
|
if (getLocalType(locId) != Type::None) { // TODO(#2135185)
|
|
inst->setTypeParam(
|
|
Type::mostRefined(getLocalType(locId).ptr(), inst->typeParam())
|
|
);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* TraceBuilder::preOptimizeStLoc(IRInstruction* inst) {
|
|
auto const curType = getLocalType(inst->extra<StLoc>()->locId);
|
|
auto const newType = inst->src(1)->type();
|
|
|
|
assert(inst->typeParam().equals(Type::None));
|
|
|
|
// There's no need to store the type if it's going to be the same
|
|
// KindOfFoo. We still have to store string types because we don't
|
|
// guard on KindOfStaticString vs. KindOfString.
|
|
auto const bothBoxed = curType.isBoxed() && newType.isBoxed();
|
|
auto const sameUnboxed = curType != Type::None && // TODO(#2135185)
|
|
curType.isKnownDataType() &&
|
|
curType.equals(newType) && !curType.isString();
|
|
if (bothBoxed || sameUnboxed) {
|
|
inst->setOpcode(StLocNT);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* TraceBuilder::preOptimize(IRInstruction* inst) {
|
|
#define X(op) case op: return preOptimize##op(inst)
|
|
switch (inst->op()) {
|
|
X(CheckLoc);
|
|
X(AssertLoc);
|
|
X(LdThis);
|
|
X(LdCtx);
|
|
X(DecRef);
|
|
X(DecRefThis);
|
|
X(DecRefLoc);
|
|
X(LdLoc);
|
|
X(LdLocAddr);
|
|
X(StLoc);
|
|
default:
|
|
break;
|
|
}
|
|
#undef X
|
|
return nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
SSATmp* TraceBuilder::optimizeWork(IRInstruction* inst,
|
|
const folly::Optional<IdomVector>& idoms) {
|
|
// Since some of these optimizations inspect tracked state, we don't
|
|
// perform any of them on non-main traces.
|
|
if (m_savedTraces.size() > 0) return nullptr;
|
|
|
|
static DEBUG_ONLY __thread int instNest = 0;
|
|
if (debug) ++instNest;
|
|
SCOPE_EXIT { if (debug) --instNest; };
|
|
DEBUG_ONLY auto indent = [&] { return std::string(instNest * 2, ' '); };
|
|
|
|
FTRACE(1, "{}{}\n", indent(), inst->toString());
|
|
|
|
// First pass of tracebuilder optimizations try to replace an
|
|
// instruction based on tracked state before we do anything else.
|
|
// May mutate the IRInstruction in place (and return nullptr) or
|
|
// return an SSATmp*.
|
|
if (SSATmp* preOpt = preOptimize(inst)) {
|
|
FTRACE(1, " {}preOptimize returned: {}\n",
|
|
indent(), preOpt->inst()->toString());
|
|
return preOpt;
|
|
}
|
|
if (inst->op() == Nop) return nullptr;
|
|
|
|
// copy propagation on inst source operands
|
|
copyProp(inst);
|
|
|
|
SSATmp* result = nullptr;
|
|
if (m_enableCse && inst->canCSE()) {
|
|
result = cseLookup(inst, idoms);
|
|
if (result) {
|
|
// Found a dominating instruction that can be used instead of inst
|
|
FTRACE(1, " {}cse found: {}\n",
|
|
indent(), result->inst()->toString());
|
|
if (inst->producesReference()) {
|
|
// Replace with an IncRef
|
|
FTRACE(1, " {}cse of refcount-producing instruction\n", indent());
|
|
return gen(IncRef, result);
|
|
} else {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_enableSimplification) {
|
|
result = m_simplifier.simplify(inst);
|
|
if (result) {
|
|
// Found a simpler instruction that can be used instead of inst
|
|
FTRACE(1, " {}simplification returned: {}\n",
|
|
indent(), result->inst()->toString());
|
|
assert(inst->hasDst());
|
|
return result;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* TraceBuilder::optimizeInst(IRInstruction* inst, CloneFlag doClone) {
|
|
if (auto const tmp = optimizeWork(inst, folly::none)) {
|
|
return tmp;
|
|
}
|
|
// Couldn't CSE or simplify the instruction; clone it and append.
|
|
if (inst->op() != Nop) {
|
|
if (doClone == CloneFlag::Yes) inst = inst->clone(&m_irFactory);
|
|
appendInstruction(inst);
|
|
// returns nullptr if instruction has no dest, returns the first
|
|
// (possibly only) dest otherwise
|
|
return inst->dst(0);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/*
|
|
* reoptimize() runs a trace through a second pass of TraceBuilder
|
|
* optimizations, like this:
|
|
*
|
|
* reset state.
|
|
* move all blocks to a temporary list.
|
|
* compute immediate dominators.
|
|
* for each block in trace order:
|
|
* if we have a snapshot state for this block:
|
|
* clear cse entries that don't dominate this block.
|
|
* use snapshot state.
|
|
* move all instructions to a temporary list.
|
|
* for each instruction:
|
|
* optimizeWork - do CSE and simplify again
|
|
* if not simplified:
|
|
* append existing instruction and update state.
|
|
* else:
|
|
* if the instruction has a result, insert a mov from the
|
|
* simplified tmp to the original tmp and discard the instruction.
|
|
* if the last conditional branch was turned into a jump, remove the
|
|
* fall-through edge to the next block.
|
|
*/
|
|
void TraceBuilder::reoptimize() {
|
|
FTRACE(5, "ReOptimize:vvvvvvvvvvvvvvvvvvvv\n");
|
|
SCOPE_EXIT { FTRACE(5, "ReOptimize:^^^^^^^^^^^^^^^^^^^^\n"); };
|
|
assert(m_curTrace == m_mainTrace.get());
|
|
assert(m_savedTraces.size() == 0);
|
|
|
|
m_enableCse = RuntimeOption::EvalHHIRCse;
|
|
m_enableSimplification = RuntimeOption::EvalHHIRSimplification;
|
|
if (!m_enableCse && !m_enableSimplification) return;
|
|
if (m_mainTrace->blocks().size() >
|
|
RuntimeOption::EvalHHIRSimplificationMaxBlocks) {
|
|
// TODO CSEHash::filter is very slow for large block sizes
|
|
// t2135219 should address that
|
|
return;
|
|
}
|
|
|
|
BlockList sortedBlocks = rpoSortCfg(m_mainTrace.get(), m_irFactory);
|
|
auto const idoms = findDominators(sortedBlocks);
|
|
clearTrackedState();
|
|
|
|
auto blocks = std::move(m_mainTrace->blocks());
|
|
assert(m_mainTrace->blocks().empty());
|
|
while (!blocks.empty()) {
|
|
Block* block = blocks.front();
|
|
blocks.pop_front();
|
|
assert(block->trace() == m_mainTrace.get());
|
|
FTRACE(5, "Block: {}\n", block->id());
|
|
|
|
m_mainTrace->push_back(block);
|
|
if (m_snapshots[block]) {
|
|
useState(block);
|
|
}
|
|
|
|
auto instructions = std::move(block->instrs());
|
|
assert(block->empty());
|
|
while (!instructions.empty()) {
|
|
auto *inst = &instructions.front();
|
|
instructions.pop_front();
|
|
|
|
// merging state looks at the current marker, and optimizeWork
|
|
// below may create new instructions. Use the marker from this
|
|
// instruction.
|
|
assert(inst->marker().valid());
|
|
setMarker(inst->marker());
|
|
|
|
auto const tmp = optimizeWork(inst, idoms); // Can generate new instrs!
|
|
if (!tmp) {
|
|
// Could not optimize; keep the old instruction
|
|
appendInstruction(inst, block);
|
|
updateTrackedState(inst);
|
|
continue;
|
|
}
|
|
SSATmp* dst = inst->dst();
|
|
if (dst->type() != Type::None && dst != tmp) {
|
|
// The result of optimization has a different destination than the inst.
|
|
// Generate a mov(tmp->dst) to get result into dst. If we get here then
|
|
// assume the last instruction in the block isn't a guard. If it was,
|
|
// we would have to insert the mov on the fall-through edge.
|
|
assert(block->empty() || !block->back()->isBlockEnd());
|
|
IRInstruction* mov = m_irFactory.mov(dst, tmp, inst->marker());
|
|
appendInstruction(mov, block);
|
|
updateTrackedState(mov);
|
|
}
|
|
// Not re-adding inst; remove the inst->taken edge
|
|
if (inst->taken()) inst->setTaken(nullptr);
|
|
}
|
|
if (block->back()->isTerminal()) {
|
|
// Could have converted a conditional branch to Jmp; clear next.
|
|
block->setNext(nullptr);
|
|
} else {
|
|
// if the last instruction was a branch, we already saved state
|
|
// for the target in updateTrackedState(). Now save state for
|
|
// the fall-through path.
|
|
saveState(block->next());
|
|
}
|
|
}
|
|
}
|
|
|
|
void TraceBuilder::killCse() {
|
|
m_cseHash.clear();
|
|
}
|
|
|
|
SSATmp* TraceBuilder::getLocalValue(unsigned id) const {
|
|
assert(id < m_localValues.size());
|
|
return m_localValues[id];
|
|
}
|
|
|
|
Type TraceBuilder::getLocalType(unsigned id) const {
|
|
assert(id < m_localTypes.size());
|
|
return m_localTypes[id];
|
|
}
|
|
|
|
void TraceBuilder::setLocalValue(unsigned id, SSATmp* value) {
|
|
assert(id < m_localValues.size() && id < m_localTypes.size());
|
|
m_localValues[id] = value;
|
|
m_localTypes[id] = value ? value->type() : Type::None;
|
|
}
|
|
|
|
void TraceBuilder::setLocalType(uint32_t id, Type type) {
|
|
assert(id < m_localValues.size() && id < m_localTypes.size());
|
|
m_localValues[id] = nullptr;
|
|
m_localTypes[id] = type;
|
|
}
|
|
|
|
// Needs to be called if a local escapes as a by-ref or
|
|
// otherwise set to an unknown value (e.g., by Iter[Init,Next][K])
|
|
void TraceBuilder::killLocalValue(uint32_t id) {
|
|
assert(id < m_localValues.size() && id < m_localTypes.size());
|
|
m_localValues[id] = nullptr;
|
|
m_localTypes[id] = Type::None;
|
|
}
|
|
|
|
bool TraceBuilder::anyLocalHasValue(SSATmp* tmp) const {
|
|
return std::find(m_localValues.begin(),
|
|
m_localValues.end(),
|
|
tmp) != m_localValues.end();
|
|
}
|
|
|
|
bool TraceBuilder::callerHasValueAvailable(SSATmp* tmp) const {
|
|
return std::find(m_callerAvailableValues.begin(),
|
|
m_callerAvailableValues.end(),
|
|
tmp) != m_callerAvailableValues.end();
|
|
}
|
|
|
|
//
|
|
// This method updates the tracked values and types of all locals that contain
|
|
// oldRef so that they now contain newRef.
|
|
// This should only be called for ref/boxed types.
|
|
//
|
|
void TraceBuilder::updateLocalRefValues(SSATmp* oldRef, SSATmp* newRef) {
|
|
assert(oldRef->type().isBoxed());
|
|
assert(newRef->type().isBoxed());
|
|
|
|
Type newRefType = newRef->type();
|
|
size_t nTrackedLocs = m_localValues.size();
|
|
for (size_t id = 0; id < nTrackedLocs; id++) {
|
|
if (m_localValues[id] == oldRef) {
|
|
m_localValues[id] = newRef;
|
|
m_localTypes[id] = newRefType;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method changes any boxed local into a BoxedCell type.
|
|
*/
|
|
void TraceBuilder::dropLocalRefsInnerTypes() {
|
|
for (size_t id = 0; id < m_localTypes.size(); id++) {
|
|
if (m_localTypes[id].isBoxed()) {
|
|
m_localTypes[id] = Type::BoxedCell;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called to clear out the tracked local values at a call site.
|
|
* Calls kill all registers, so we don't want to keep locals in
|
|
* registers across calls. We do continue tracking the types in
|
|
* locals, however.
|
|
*/
|
|
void TraceBuilder::killLocals() {
|
|
for (uint32_t i = 0; i < m_localValues.size(); i++) {
|
|
SSATmp* t = m_localValues[i];
|
|
// should not kill DefConst, and LdConst should be replaced by DefConst
|
|
if (!t || t->inst()->op() == DefConst) {
|
|
continue;
|
|
}
|
|
if (t->inst()->op() == LdConst) {
|
|
// make the new DefConst instruction
|
|
IRInstruction* clone = t->inst()->clone(&m_irFactory);
|
|
clone->setOpcode(DefConst);
|
|
m_localValues[i] = clone->dst();
|
|
continue;
|
|
}
|
|
assert(!t->isConst());
|
|
m_localValues[i] = nullptr;
|
|
}
|
|
}
|
|
|
|
void TraceBuilder::setMarker(BCMarker marker) {
|
|
if (m_curMarker == marker) return;
|
|
FTRACE(2, "TraceBuilder changing current marker from {} to {}\n",
|
|
m_curMarker.func ? m_curMarker.show() : "<invalid>", marker.show());
|
|
assert(marker.valid());
|
|
m_curMarker = marker;
|
|
}
|
|
|
|
void TraceBuilder::pushTrace(IRTrace* t, BCMarker marker, Block* b,
|
|
const boost::optional<Block::iterator>& where) {
|
|
FTRACE(2, "TraceBuilder saving {}@{} and using {}@{}\n",
|
|
m_curTrace, m_curMarker.show(), t, marker.show());
|
|
assert(t);
|
|
assert(bool(b) == bool(where));
|
|
assert(IMPLIES(b, b->trace() == t));
|
|
|
|
m_savedTraces.push(
|
|
TraceState{ m_curTrace, m_curBlock, m_curMarker, m_curWhere });
|
|
m_curTrace = t;
|
|
m_curBlock = b;
|
|
setMarker(marker);
|
|
m_curWhere = where;
|
|
}
|
|
|
|
void TraceBuilder::popTrace() {
|
|
assert(!m_savedTraces.empty());
|
|
|
|
auto const& top = m_savedTraces.top();
|
|
FTRACE(2, "TraceBuilder popping {}@{} to restore {}@{}\n",
|
|
m_curTrace, m_curMarker.show(), top.trace, top.marker.show());
|
|
m_curTrace = top.trace;
|
|
m_curBlock = top.block;
|
|
setMarker(top.marker);
|
|
m_curWhere = top.where;
|
|
m_savedTraces.pop();
|
|
}
|
|
|
|
}}
|