blob: 8b1cd18b3321fce1072b87060a9bff9f0770d89d [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/AliasAnalysis.h"
#include <stdio.h>
#include "jit/Ion.h"
#include "jit/IonBuilder.h"
#include "jit/JitSpewer.h"
#include "jit/MIR.h"
#include "jit/MIRGraph.h"
#include "vm/Printer.h"
using namespace js;
using namespace js::jit;
using mozilla::Array;
namespace js {
namespace jit {
class LoopAliasInfo : public TempObject
{
private:
LoopAliasInfo* outer_;
MBasicBlock* loopHeader_;
MInstructionVector invariantLoads_;
public:
LoopAliasInfo(TempAllocator& alloc, LoopAliasInfo* outer, MBasicBlock* loopHeader)
: outer_(outer), loopHeader_(loopHeader), invariantLoads_(alloc)
{ }
MBasicBlock* loopHeader() const {
return loopHeader_;
}
LoopAliasInfo* outer() const {
return outer_;
}
bool addInvariantLoad(MInstruction* ins) {
return invariantLoads_.append(ins);
}
const MInstructionVector& invariantLoads() const {
return invariantLoads_;
}
MInstruction* firstInstruction() const {
return *loopHeader_->begin();
}
};
} // namespace jit
} // namespace js
namespace {
// Iterates over the flags in an AliasSet.
class AliasSetIterator
{
private:
uint32_t flags;
unsigned pos;
public:
explicit AliasSetIterator(AliasSet set)
: flags(set.flags()), pos(0)
{
while (flags && (flags & 1) == 0) {
flags >>= 1;
pos++;
}
}
AliasSetIterator& operator ++(int) {
do {
flags >>= 1;
pos++;
} while (flags && (flags & 1) == 0);
return *this;
}
explicit operator bool() const {
return !!flags;
}
unsigned operator*() const {
MOZ_ASSERT(pos < AliasSet::NumCategories);
return pos;
}
};
} /* anonymous namespace */
AliasAnalysis::AliasAnalysis(MIRGenerator* mir, MIRGraph& graph)
: mir(mir),
graph_(graph),
loop_(nullptr)
{
}
// Whether there might be a path from src to dest, excluding loop backedges. This is
// approximate and really ought to depend on precomputed reachability information.
static inline bool
BlockMightReach(MBasicBlock* src, MBasicBlock* dest)
{
while (src->id() <= dest->id()) {
if (src == dest)
return true;
switch (src->numSuccessors()) {
case 0:
return false;
case 1: {
MBasicBlock* successor = src->getSuccessor(0);
if (successor->id() <= src->id())
return true; // Don't iloop.
src = successor;
break;
}
default:
return true;
}
}
return false;
}
static void
IonSpewDependency(MInstruction* load, MInstruction* store, const char* verb, const char* reason)
{
if (!JitSpewEnabled(JitSpew_Alias))
return;
Fprinter& out = JitSpewPrinter();
out.printf("Load ");
load->printName(out);
out.printf(" %s on store ", verb);
store->printName(out);
out.printf(" (%s)\n", reason);
}
static void
IonSpewAliasInfo(const char* pre, MInstruction* ins, const char* post)
{
if (!JitSpewEnabled(JitSpew_Alias))
return;
Fprinter& out = JitSpewPrinter();
out.printf("%s ", pre);
ins->printName(out);
out.printf(" %s\n", post);
}
// This pass annotates every load instruction with the last store instruction
// on which it depends. The algorithm is optimistic in that it ignores explicit
// dependencies and only considers loads and stores.
//
// Loads inside loops only have an implicit dependency on a store before the
// loop header if no instruction inside the loop body aliases it. To calculate
// this efficiently, we maintain a list of maybe-invariant loads and the combined
// alias set for all stores inside the loop. When we see the loop's backedge, this
// information is used to mark every load we wrongly assumed to be loop invariant as
// having an implicit dependency on the last instruction of the loop header, so that
// it's never moved before the loop header.
//
// The algorithm depends on the invariant that both control instructions and effectful
// instructions (stores) are never hoisted.
bool
AliasAnalysis::analyze()
{
Vector<MInstructionVector, AliasSet::NumCategories, JitAllocPolicy> stores(alloc());
// Initialize to the first instruction.
MInstruction* firstIns = *graph_.entryBlock()->begin();
for (unsigned i = 0; i < AliasSet::NumCategories; i++) {
MInstructionVector defs(alloc());
if (!defs.append(firstIns))
return false;
if (!stores.append(Move(defs)))
return false;
}
// Type analysis may have inserted new instructions. Since this pass depends
// on the instruction number ordering, all instructions are renumbered.
uint32_t newId = 0;
for (ReversePostorderIterator block(graph_.rpoBegin()); block != graph_.rpoEnd(); block++) {
if (mir->shouldCancel("Alias Analysis (main loop)"))
return false;
if (block->isLoopHeader()) {
JitSpew(JitSpew_Alias, "Processing loop header %d", block->id());
loop_ = new(alloc()) LoopAliasInfo(alloc(), loop_, *block);
}
for (MPhiIterator def(block->phisBegin()), end(block->phisEnd()); def != end; ++def)
def->setId(newId++);
for (MInstructionIterator def(block->begin()), end(block->begin(block->lastIns()));
def != end;
++def)
{
def->setId(newId++);
AliasSet set = def->getAliasSet();
if (set.isNone())
continue;
// For the purposes of alias analysis, all recoverable operations
// are treated as effect free as the memory represented by these
// operations cannot be aliased by others.
if (def->canRecoverOnBailout())
continue;
if (set.isStore()) {
for (AliasSetIterator iter(set); iter; iter++) {
if (!stores[*iter].append(*def))
return false;
}
if (JitSpewEnabled(JitSpew_Alias)) {
Fprinter& out = JitSpewPrinter();
out.printf("Processing store ");
def->printName(out);
out.printf(" (flags %x)\n", set.flags());
}
} else {
// Find the most recent store on which this instruction depends.
MInstruction* lastStore = firstIns;
for (AliasSetIterator iter(set); iter; iter++) {
MInstructionVector& aliasedStores = stores[*iter];
for (int i = aliasedStores.length() - 1; i >= 0; i--) {
MInstruction* store = aliasedStores[i];
if (def->mightAlias(store) && BlockMightReach(store->block(), *block)) {
if (lastStore->id() < store->id())
lastStore = store;
break;
}
}
}
def->setDependency(lastStore);
IonSpewDependency(*def, lastStore, "depends", "");
// If the last store was before the current loop, we assume this load
// is loop invariant. If a later instruction writes to the same location,
// we will fix this at the end of the loop.
if (loop_ && lastStore->id() < loop_->firstInstruction()->id()) {
if (!loop_->addInvariantLoad(*def))
return false;
}
}
}
// Renumber the last instruction, as the analysis depends on this and the order.
block->lastIns()->setId(newId++);
if (block->isLoopBackedge()) {
MOZ_ASSERT(loop_->loopHeader() == block->loopHeaderOfBackedge());
JitSpew(JitSpew_Alias, "Processing loop backedge %d (header %d)", block->id(),
loop_->loopHeader()->id());
LoopAliasInfo* outerLoop = loop_->outer();
MInstruction* firstLoopIns = *loop_->loopHeader()->begin();
const MInstructionVector& invariant = loop_->invariantLoads();
for (unsigned i = 0; i < invariant.length(); i++) {
MInstruction* ins = invariant[i];
AliasSet set = ins->getAliasSet();
MOZ_ASSERT(set.isLoad());
bool hasAlias = false;
for (AliasSetIterator iter(set); iter; iter++) {
MInstructionVector& aliasedStores = stores[*iter];
for (int i = aliasedStores.length() - 1;; i--) {
MInstruction* store = aliasedStores[i];
if (store->id() < firstLoopIns->id())
break;
if (ins->mightAlias(store)) {
hasAlias = true;
IonSpewDependency(ins, store, "aliases", "store in loop body");
break;
}
}
if (hasAlias)
break;
}
if (hasAlias) {
// This instruction depends on stores inside the loop body. Mark it as having a
// dependency on the last instruction of the loop header. The last instruction is a
// control instruction and these are never hoisted.
MControlInstruction* controlIns = loop_->loopHeader()->lastIns();
IonSpewDependency(ins, controlIns, "depends", "due to stores in loop body");
ins->setDependency(controlIns);
} else {
IonSpewAliasInfo("Load", ins, "does not depend on any stores in this loop");
if (outerLoop && ins->dependency()->id() < outerLoop->firstInstruction()->id()) {
IonSpewAliasInfo("Load", ins, "may be invariant in outer loop");
if (!outerLoop->addInvariantLoad(ins))
return false;
}
}
}
loop_ = loop_->outer();
}
}
MOZ_ASSERT(loop_ == nullptr);
return true;
}