зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1310155 - IonMonkey, part 1.0: Split graph creation from IonBuilder, r=jandem
This commit is contained in:
Родитель
1daea96f16
Коммит
d889fa48d3
|
@ -13,6 +13,7 @@
|
|||
#include "jit/BaselineCompiler.h"
|
||||
#include "jit/BaselineIC.h"
|
||||
#include "jit/CompileInfo.h"
|
||||
#include "jit/IonControlFlow.h"
|
||||
#include "jit/JitCommon.h"
|
||||
#include "jit/JitSpewer.h"
|
||||
#include "vm/Debugger.h"
|
||||
|
@ -76,7 +77,8 @@ BaselineScript::BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset,
|
|||
flags_(0),
|
||||
inlinedBytecodeLength_(0),
|
||||
maxInliningDepth_(UINT8_MAX),
|
||||
pendingBuilder_(nullptr)
|
||||
pendingBuilder_(nullptr),
|
||||
controlFlowGraph_(nullptr)
|
||||
{ }
|
||||
|
||||
static const unsigned BASELINE_MAX_ARGS_LENGTH = 20000;
|
||||
|
@ -499,6 +501,8 @@ BaselineScript::Destroy(FreeOp* fop, BaselineScript* script)
|
|||
{
|
||||
|
||||
MOZ_ASSERT(!script->hasPendingIonBuilder());
|
||||
fop->delete_(script->controlFlowGraph_);
|
||||
script->controlFlowGraph_ = nullptr;
|
||||
|
||||
script->unlinkDependentWasmImports(fop);
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace jit {
|
|||
class StackValue;
|
||||
class BaselineICEntry;
|
||||
class ICStub;
|
||||
class ControlFlowGraph;
|
||||
|
||||
class PCMappingSlotInfo
|
||||
{
|
||||
|
@ -239,6 +240,8 @@ struct BaselineScript
|
|||
// An ion compilation that is ready, but isn't linked yet.
|
||||
IonBuilder *pendingBuilder_;
|
||||
|
||||
ControlFlowGraph* controlFlowGraph_;
|
||||
|
||||
public:
|
||||
// Do not call directly, use BaselineScript::New. This is public for cx->new_.
|
||||
BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset,
|
||||
|
@ -513,6 +516,14 @@ struct BaselineScript
|
|||
script->setIonScript(nullptr, nullptr);
|
||||
}
|
||||
|
||||
const ControlFlowGraph* controlFlowGraph() const {
|
||||
return controlFlowGraph_;
|
||||
}
|
||||
|
||||
void setControlFlowGraph(ControlFlowGraph* controlFlowGraph) {
|
||||
controlFlowGraph_ = controlFlowGraph;
|
||||
}
|
||||
|
||||
};
|
||||
static_assert(sizeof(BaselineScript) % sizeof(uintptr_t) == 0,
|
||||
"The data attached to the script must be aligned for fast JIT access.");
|
||||
|
|
|
@ -254,6 +254,10 @@ class InlineListNode : public InlineForwardListNode<T>
|
|||
InlineListNode(const InlineListNode<T>&) = delete;
|
||||
void operator=(const InlineListNode<T>&) = delete;
|
||||
|
||||
bool isInList() {
|
||||
return prev != nullptr && this->next != nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class InlineList<T>;
|
||||
friend class InlineListIterator<T>;
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -15,6 +15,7 @@
|
|||
#include "jit/BaselineInspector.h"
|
||||
#include "jit/BytecodeAnalysis.h"
|
||||
#include "jit/IonAnalysis.h"
|
||||
#include "jit/IonControlFlow.h"
|
||||
#include "jit/IonOptimizationLevels.h"
|
||||
#include "jit/MIR.h"
|
||||
#include "jit/MIRGenerator.h"
|
||||
|
@ -39,173 +40,6 @@ class IonBuilder
|
|||
: public MIRGenerator,
|
||||
public mozilla::LinkedListElement<IonBuilder>
|
||||
{
|
||||
enum ControlStatus {
|
||||
ControlStatus_Error,
|
||||
ControlStatus_Abort,
|
||||
ControlStatus_Ended, // There is no continuation/join point.
|
||||
ControlStatus_Joined, // Created a join node.
|
||||
ControlStatus_Jumped, // Parsing another branch at the same level.
|
||||
ControlStatus_None // No control flow.
|
||||
};
|
||||
|
||||
struct DeferredEdge : public TempObject
|
||||
{
|
||||
MBasicBlock* block;
|
||||
DeferredEdge* next;
|
||||
|
||||
DeferredEdge(MBasicBlock* block, DeferredEdge* next)
|
||||
: block(block), next(next)
|
||||
{ }
|
||||
};
|
||||
|
||||
struct ControlFlowInfo {
|
||||
// Entry in the cfgStack.
|
||||
uint32_t cfgEntry;
|
||||
|
||||
// Label that continues go to.
|
||||
jsbytecode* continuepc;
|
||||
|
||||
ControlFlowInfo(uint32_t cfgEntry, jsbytecode* continuepc)
|
||||
: cfgEntry(cfgEntry),
|
||||
continuepc(continuepc)
|
||||
{ }
|
||||
};
|
||||
|
||||
// To avoid recursion, the bytecode analyzer uses a stack where each entry
|
||||
// is a small state machine. As we encounter branches or jumps in the
|
||||
// bytecode, we push information about the edges on the stack so that the
|
||||
// CFG can be built in a tree-like fashion.
|
||||
struct CFGState {
|
||||
enum State {
|
||||
IF_TRUE, // if() { }, no else.
|
||||
IF_TRUE_EMPTY_ELSE, // if() { }, empty else
|
||||
IF_ELSE_TRUE, // if() { X } else { }
|
||||
IF_ELSE_FALSE, // if() { } else { X }
|
||||
DO_WHILE_LOOP_BODY, // do { x } while ()
|
||||
DO_WHILE_LOOP_COND, // do { } while (x)
|
||||
WHILE_LOOP_COND, // while (x) { }
|
||||
WHILE_LOOP_BODY, // while () { x }
|
||||
FOR_LOOP_COND, // for (; x;) { }
|
||||
FOR_LOOP_BODY, // for (; ;) { x }
|
||||
FOR_LOOP_UPDATE, // for (; ; x) { }
|
||||
TABLE_SWITCH, // switch() { x }
|
||||
COND_SWITCH_CASE, // switch() { case X: ... }
|
||||
COND_SWITCH_BODY, // switch() { case ...: X }
|
||||
AND_OR, // && x, || x
|
||||
LABEL, // label: x
|
||||
TRY // try { x } catch(e) { }
|
||||
};
|
||||
|
||||
State state; // Current state of this control structure.
|
||||
jsbytecode* stopAt; // Bytecode at which to stop the processing loop.
|
||||
|
||||
// For if structures, this contains branch information.
|
||||
union {
|
||||
struct {
|
||||
MBasicBlock* ifFalse;
|
||||
jsbytecode* falseEnd;
|
||||
MBasicBlock* ifTrue; // Set when the end of the true path is reached.
|
||||
MTest* test;
|
||||
} branch;
|
||||
struct {
|
||||
// Common entry point.
|
||||
MBasicBlock* entry;
|
||||
|
||||
// Whether OSR is being performed for this loop.
|
||||
bool osr;
|
||||
|
||||
// Position of where the loop body starts and ends.
|
||||
jsbytecode* bodyStart;
|
||||
jsbytecode* bodyEnd;
|
||||
|
||||
// pc immediately after the loop exits.
|
||||
jsbytecode* exitpc;
|
||||
|
||||
// pc for 'continue' jumps.
|
||||
jsbytecode* continuepc;
|
||||
|
||||
// Common exit point. Created lazily, so it may be nullptr.
|
||||
MBasicBlock* successor;
|
||||
|
||||
// Deferred break and continue targets.
|
||||
DeferredEdge* breaks;
|
||||
DeferredEdge* continues;
|
||||
|
||||
// Initial state, in case loop processing is restarted.
|
||||
State initialState;
|
||||
jsbytecode* initialPc;
|
||||
jsbytecode* initialStopAt;
|
||||
jsbytecode* loopHead;
|
||||
|
||||
// For-loops only.
|
||||
jsbytecode* condpc;
|
||||
jsbytecode* updatepc;
|
||||
jsbytecode* updateEnd;
|
||||
} loop;
|
||||
struct {
|
||||
// pc immediately after the switch.
|
||||
jsbytecode* exitpc;
|
||||
|
||||
// Deferred break and continue targets.
|
||||
DeferredEdge* breaks;
|
||||
|
||||
// MIR instruction
|
||||
MTableSwitch* ins;
|
||||
|
||||
// The number of current successor that get mapped into a block.
|
||||
uint32_t currentBlock;
|
||||
|
||||
} tableswitch;
|
||||
struct {
|
||||
// Vector of body blocks to process after the cases.
|
||||
FixedList<MBasicBlock*>* bodies;
|
||||
|
||||
// When processing case statements, this counter points at the
|
||||
// last uninitialized body. When processing bodies, this
|
||||
// counter targets the next body to process.
|
||||
uint32_t currentIdx;
|
||||
|
||||
// Remember the block index of the default case.
|
||||
jsbytecode* defaultTarget;
|
||||
uint32_t defaultIdx;
|
||||
|
||||
// Block immediately after the switch.
|
||||
jsbytecode* exitpc;
|
||||
DeferredEdge* breaks;
|
||||
} condswitch;
|
||||
struct {
|
||||
DeferredEdge* breaks;
|
||||
} label;
|
||||
struct {
|
||||
MBasicBlock* successor;
|
||||
} try_;
|
||||
};
|
||||
|
||||
inline bool isLoop() const {
|
||||
switch (state) {
|
||||
case DO_WHILE_LOOP_COND:
|
||||
case DO_WHILE_LOOP_BODY:
|
||||
case WHILE_LOOP_COND:
|
||||
case WHILE_LOOP_BODY:
|
||||
case FOR_LOOP_COND:
|
||||
case FOR_LOOP_BODY:
|
||||
case FOR_LOOP_UPDATE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static CFGState If(jsbytecode* join, MTest* test);
|
||||
static CFGState IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd, MTest* test);
|
||||
static CFGState AndOr(jsbytecode* join, MBasicBlock* lhs);
|
||||
static CFGState TableSwitch(jsbytecode* exitpc, MTableSwitch* ins);
|
||||
static CFGState CondSwitch(IonBuilder* builder, jsbytecode* exitpc, jsbytecode* defaultTarget);
|
||||
static CFGState Label(jsbytecode* exitpc);
|
||||
static CFGState Try(jsbytecode* exitpc, MBasicBlock* successor);
|
||||
};
|
||||
|
||||
static int CmpSuccessors(const void* a, const void* b);
|
||||
|
||||
public:
|
||||
IonBuilder(JSContext* analysisContext, CompileCompartment* comp,
|
||||
|
@ -224,7 +58,6 @@ class IonBuilder
|
|||
|
||||
private:
|
||||
MOZ_MUST_USE bool traverseBytecode();
|
||||
ControlStatus snoopControlFlow(JSOp op);
|
||||
MOZ_MUST_USE bool processIterators();
|
||||
MOZ_MUST_USE bool inspectOpcode(JSOp op);
|
||||
uint32_t readIndex(jsbytecode* pc);
|
||||
|
@ -237,44 +70,9 @@ class IonBuilder
|
|||
MOZ_MUST_USE bool getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing,
|
||||
ObjectVector& targets, uint32_t maxTargets);
|
||||
|
||||
void popCfgStack();
|
||||
DeferredEdge* filterDeadDeferredEdges(DeferredEdge* edge);
|
||||
MOZ_MUST_USE bool processDeferredContinues(CFGState& state);
|
||||
ControlStatus processControlEnd();
|
||||
ControlStatus processCfgStack();
|
||||
ControlStatus processCfgEntry(CFGState& state);
|
||||
ControlStatus processIfEnd(CFGState& state);
|
||||
ControlStatus processIfElseTrueEnd(CFGState& state);
|
||||
ControlStatus processIfElseFalseEnd(CFGState& state);
|
||||
ControlStatus processDoWhileBodyEnd(CFGState& state);
|
||||
ControlStatus processDoWhileCondEnd(CFGState& state);
|
||||
ControlStatus processWhileCondEnd(CFGState& state);
|
||||
ControlStatus processWhileBodyEnd(CFGState& state);
|
||||
ControlStatus processForCondEnd(CFGState& state);
|
||||
ControlStatus processForBodyEnd(CFGState& state);
|
||||
ControlStatus processForUpdateEnd(CFGState& state);
|
||||
ControlStatus processNextTableSwitchCase(CFGState& state);
|
||||
ControlStatus processCondSwitchCase(CFGState& state);
|
||||
ControlStatus processCondSwitchBody(CFGState& state);
|
||||
ControlStatus processSwitchBreak(JSOp op);
|
||||
ControlStatus processSwitchEnd(DeferredEdge* breaks, jsbytecode* exitpc);
|
||||
ControlStatus processAndOrEnd(CFGState& state);
|
||||
ControlStatus processLabelEnd(CFGState& state);
|
||||
ControlStatus processTryEnd(CFGState& state);
|
||||
ControlStatus processReturn(JSOp op);
|
||||
ControlStatus processThrow();
|
||||
ControlStatus processContinue(JSOp op);
|
||||
ControlStatus processBreak(JSOp op, jssrcnote* sn);
|
||||
ControlStatus maybeLoop(JSOp op, jssrcnote* sn);
|
||||
MOZ_MUST_USE bool pushLoop(CFGState::State state, jsbytecode* stopAt, MBasicBlock* entry,
|
||||
bool osr, jsbytecode* loopHead, jsbytecode* initialPc,
|
||||
jsbytecode* bodyStart, jsbytecode* bodyEnd,
|
||||
jsbytecode* exitpc, jsbytecode* continuepc);
|
||||
MOZ_MUST_USE bool analyzeNewLoopTypes(MBasicBlock* entry, jsbytecode* start, jsbytecode* end);
|
||||
MOZ_MUST_USE bool analyzeNewLoopTypes(const CFGBlock* loopEntryBlock);
|
||||
|
||||
MBasicBlock* addBlock(MBasicBlock* block, uint32_t loopDepth);
|
||||
MBasicBlock* newBlock(MBasicBlock* predecessor, jsbytecode* pc);
|
||||
MBasicBlock* newBlock(MBasicBlock* predecessor, jsbytecode* pc, uint32_t loopDepth);
|
||||
MBasicBlock* newBlock(MBasicBlock* predecessor, jsbytecode* pc, MResumePoint* priorResumePoint);
|
||||
MBasicBlock* newBlockPopN(MBasicBlock* predecessor, jsbytecode* pc, uint32_t popped);
|
||||
MBasicBlock* newBlockAfter(MBasicBlock* at, MBasicBlock* predecessor, jsbytecode* pc);
|
||||
|
@ -289,6 +87,18 @@ class IonBuilder
|
|||
return newBlockAfter(at, nullptr, pc);
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool visitBlock(const CFGBlock* hblock, MBasicBlock* mblock);
|
||||
MOZ_MUST_USE bool visitControlInstruction(CFGControlInstruction* ins, bool* restarted);
|
||||
MOZ_MUST_USE bool visitTest(CFGTest* test);
|
||||
MOZ_MUST_USE bool visitCompare(CFGCompare* compare);
|
||||
MOZ_MUST_USE bool visitLoopEntry(CFGLoopEntry* loopEntry);
|
||||
MOZ_MUST_USE bool visitReturn(CFGControlInstruction* ins);
|
||||
MOZ_MUST_USE bool visitGoto(CFGGoto* ins);
|
||||
MOZ_MUST_USE bool visitBackEdge(CFGBackEdge* ins, bool* restarted);
|
||||
MOZ_MUST_USE bool visitTry(CFGTry* test);
|
||||
MOZ_MUST_USE bool visitThrow(CFGThrow* ins);
|
||||
MOZ_MUST_USE bool visitTableSwitch(CFGTableSwitch* ins);
|
||||
|
||||
// We want to make sure that our MTest instructions all check whether the
|
||||
// thing being tested might emulate undefined. So we funnel their creation
|
||||
// through this method, to make sure that happens. We don't want to just do
|
||||
|
@ -297,18 +107,6 @@ class IonBuilder
|
|||
// from a background thread.
|
||||
MTest* newTest(MDefinition* ins, MBasicBlock* ifTrue, MBasicBlock* ifFalse);
|
||||
|
||||
// Given a list of pending breaks, creates a new block and inserts a Goto
|
||||
// linking each break to the new block.
|
||||
MBasicBlock* createBreakCatchBlock(DeferredEdge* edge, jsbytecode* pc);
|
||||
|
||||
// Finishes loops that do not actually loop, containing only breaks and
|
||||
// returns or a do while loop with a condition that is constant false.
|
||||
ControlStatus processBrokenLoop(CFGState& state);
|
||||
|
||||
// Computes loop phis, places them in all successors of a loop, then
|
||||
// handles any pending breaks.
|
||||
ControlStatus finishLoop(CFGState& state, MBasicBlock* successor);
|
||||
|
||||
// Incorporates a type/typeSet into an OSR value for a loop, after the loop
|
||||
// body has been processed.
|
||||
MOZ_MUST_USE bool addOsrValueTypeBarrier(uint32_t slot, MInstruction** def,
|
||||
|
@ -317,15 +115,8 @@ class IonBuilder
|
|||
|
||||
// Restarts processing of a loop if the type information at its header was
|
||||
// incomplete.
|
||||
ControlStatus restartLoop(const CFGState& state);
|
||||
|
||||
void assertValidLoopHeadOp(jsbytecode* pc);
|
||||
|
||||
ControlStatus forLoop(JSOp op, jssrcnote* sn);
|
||||
ControlStatus whileOrForInLoop(jssrcnote* sn);
|
||||
ControlStatus doWhileLoop(JSOp op, jssrcnote* sn);
|
||||
ControlStatus tableSwitch(JSOp op, jssrcnote* sn);
|
||||
ControlStatus condSwitch(JSOp op, jssrcnote* sn);
|
||||
bool restartLoop(const CFGBlock* header);
|
||||
bool initLoopEntry();
|
||||
|
||||
// Please see the Big Honkin' Comment about how resume points work in
|
||||
// IonBuilder.cpp, near the definition for this function.
|
||||
|
@ -334,6 +125,8 @@ class IonBuilder
|
|||
MOZ_MUST_USE bool resumeAfter(MInstruction* ins);
|
||||
MOZ_MUST_USE bool maybeInsertResume();
|
||||
|
||||
bool blockIsOSREntry(const CFGBlock* block, const CFGBlock* predecessor);
|
||||
|
||||
void insertRecompileCheck();
|
||||
|
||||
MOZ_MUST_USE bool initParameters();
|
||||
|
@ -708,10 +501,7 @@ class IonBuilder
|
|||
MOZ_MUST_USE bool jsop_funapplyarray(uint32_t argc);
|
||||
MOZ_MUST_USE bool jsop_call(uint32_t argc, bool constructing);
|
||||
MOZ_MUST_USE bool jsop_eval(uint32_t argc);
|
||||
MOZ_MUST_USE bool jsop_ifeq(JSOp op);
|
||||
MOZ_MUST_USE bool jsop_try();
|
||||
MOZ_MUST_USE bool jsop_label();
|
||||
MOZ_MUST_USE bool jsop_condswitch();
|
||||
MOZ_MUST_USE bool jsop_andor(JSOp op);
|
||||
MOZ_MUST_USE bool jsop_dup2();
|
||||
MOZ_MUST_USE bool jsop_loophead(jsbytecode* pc);
|
||||
|
@ -1193,6 +983,9 @@ class IonBuilder
|
|||
jsbytecode* pc;
|
||||
MBasicBlock* current;
|
||||
uint32_t loopDepth_;
|
||||
Vector<MBasicBlock*, 0, JitAllocPolicy> blockWorklist;
|
||||
const CFGBlock* cfgCurrent;
|
||||
const ControlFlowGraph* cfg;
|
||||
|
||||
Vector<BytecodeSite*, 0, JitAllocPolicy> trackedOptimizationSites_;
|
||||
|
||||
|
@ -1238,12 +1031,13 @@ class IonBuilder
|
|||
{}
|
||||
};
|
||||
|
||||
Vector<CFGState, 8, JitAllocPolicy> cfgStack_;
|
||||
Vector<ControlFlowInfo, 4, JitAllocPolicy> loops_;
|
||||
Vector<ControlFlowInfo, 0, JitAllocPolicy> switches_;
|
||||
Vector<ControlFlowInfo, 2, JitAllocPolicy> labels_;
|
||||
Vector<MInstruction*, 2, JitAllocPolicy> iterators_;
|
||||
Vector<LoopHeader, 0, JitAllocPolicy> loopHeaders_;
|
||||
Vector<MBasicBlock*, 0, JitAllocPolicy> loopHeaderStack_;
|
||||
#ifdef DEBUG
|
||||
Vector<const CFGBlock*, 0, JitAllocPolicy> cfgLoopHeaderStack_;
|
||||
#endif
|
||||
|
||||
BaselineInspector* inspector;
|
||||
|
||||
size_t inliningDepth_;
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,807 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef jit_IonControlFlow_h
|
||||
#define jit_IonControlFlow_h
|
||||
|
||||
#include "mozilla/Array.h"
|
||||
|
||||
#include "jsbytecode.h"
|
||||
|
||||
#include "jit/BytecodeAnalysis.h"
|
||||
#include "jit/FixedList.h"
|
||||
#include "jit/JitAllocPolicy.h"
|
||||
#include "js/TypeDecls.h"
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
||||
class CFGControlInstruction;
|
||||
|
||||
// Adds MFoo::New functions which are mirroring the arguments of the
|
||||
// constructors. Opcodes which are using this macro can be called with a
|
||||
// TempAllocator, or the fallible version of the TempAllocator.
|
||||
#define TRIVIAL_CFG_NEW_WRAPPERS \
|
||||
template <typename... Args> \
|
||||
static CFGThisOpcode* New(TempAllocator& alloc, Args&&... args) { \
|
||||
return new(alloc) CFGThisOpcode(mozilla::Forward<Args>(args)...); \
|
||||
} \
|
||||
template <typename... Args> \
|
||||
static CFGThisOpcode* New(TempAllocator::Fallible alloc, Args&&... args) \
|
||||
{ \
|
||||
return new(alloc) CFGThisOpcode(mozilla::Forward<Args>(args)...); \
|
||||
}
|
||||
|
||||
class CFGBlock : public TempObject
|
||||
{
|
||||
size_t id_;
|
||||
jsbytecode* start;
|
||||
jsbytecode* stop;
|
||||
CFGControlInstruction* end;
|
||||
bool inWorkList;
|
||||
|
||||
public:
|
||||
explicit CFGBlock(jsbytecode* start)
|
||||
: id_(-1),
|
||||
start(start),
|
||||
stop(nullptr),
|
||||
end(nullptr),
|
||||
inWorkList(false)
|
||||
{}
|
||||
|
||||
static CFGBlock* New(TempAllocator& alloc, jsbytecode* start) {
|
||||
return new(alloc) CFGBlock(start);
|
||||
}
|
||||
|
||||
void operator=(const CFGBlock&) = delete;
|
||||
|
||||
jsbytecode* startPc() const {
|
||||
return start;
|
||||
}
|
||||
jsbytecode* stopPc() const {
|
||||
MOZ_ASSERT(stop);
|
||||
return stop;
|
||||
}
|
||||
void setStopPc(jsbytecode* stopPc) {
|
||||
stop = stopPc;
|
||||
}
|
||||
CFGControlInstruction* stopIns() const {
|
||||
MOZ_ASSERT(end);
|
||||
return end;
|
||||
}
|
||||
void setStopIns(CFGControlInstruction* stopIns) {
|
||||
end = stopIns;
|
||||
}
|
||||
bool isInWorkList() const {
|
||||
return inWorkList;
|
||||
}
|
||||
void setInWorklist() {
|
||||
MOZ_ASSERT(!inWorkList);
|
||||
inWorkList = true;
|
||||
}
|
||||
void clearInWorkList() {
|
||||
MOZ_ASSERT(inWorkList);
|
||||
inWorkList = false;
|
||||
}
|
||||
size_t id() const {
|
||||
return id_;
|
||||
}
|
||||
void setId(size_t id) {
|
||||
id_ = id;
|
||||
}
|
||||
};
|
||||
|
||||
#define CFG_CONTROL_OPCODE_LIST(_) \
|
||||
_(Test) \
|
||||
_(Compare) \
|
||||
_(Goto) \
|
||||
_(Return) \
|
||||
_(RetRVal) \
|
||||
_(LoopEntry) \
|
||||
_(BackEdge) \
|
||||
_(TableSwitch) \
|
||||
_(Try) \
|
||||
_(Throw)
|
||||
|
||||
// Forward declarations of MIR types.
|
||||
#define FORWARD_DECLARE(type) class CFG##type;
|
||||
CFG_CONTROL_OPCODE_LIST(FORWARD_DECLARE)
|
||||
#undef FORWARD_DECLARE
|
||||
|
||||
#define CFG_CONTROL_HEADER(type_name) \
|
||||
static const Type classOpcode = CFGControlInstruction::Type_##type_name; \
|
||||
using CFGThisOpcode = CFG##type_name; \
|
||||
Type type() const override { \
|
||||
return classOpcode; \
|
||||
} \
|
||||
const char* Name() const override { \
|
||||
return #type_name; \
|
||||
} \
|
||||
|
||||
|
||||
class CFGControlInstruction : public TempObject
|
||||
{
|
||||
public:
|
||||
enum Type {
|
||||
# define DEFINE_TYPES(type) Type_##type,
|
||||
CFG_CONTROL_OPCODE_LIST(DEFINE_TYPES)
|
||||
# undef DEFINE_TYPES
|
||||
};
|
||||
|
||||
virtual size_t numSuccessors() const = 0;
|
||||
virtual CFGBlock* getSuccessor(size_t i) const = 0;
|
||||
virtual void replaceSuccessor(size_t i, CFGBlock* successor) = 0;
|
||||
virtual Type type() const = 0;
|
||||
virtual const char* Name() const = 0;
|
||||
|
||||
template<typename CFGType> bool is() const {
|
||||
return type() == CFGType::classOpcode;
|
||||
}
|
||||
template<typename CFGType> CFGType* to() {
|
||||
MOZ_ASSERT(this->is<CFGType>());
|
||||
return static_cast<CFGType*>(this);
|
||||
}
|
||||
template<typename CFGType> const CFGType* to() const {
|
||||
MOZ_ASSERT(this->is<CFGType>());
|
||||
return static_cast<const CFGType*>(this);
|
||||
}
|
||||
# define TYPE_CASTS(type) \
|
||||
bool is##type() const { \
|
||||
return this->is<CFG##type>(); \
|
||||
} \
|
||||
CFG##type* to##type() { \
|
||||
return this->to<CFG##type>(); \
|
||||
} \
|
||||
const CFG##type* to##type() const { \
|
||||
return this->to<CFG##type>(); \
|
||||
}
|
||||
CFG_CONTROL_OPCODE_LIST(TYPE_CASTS)
|
||||
# undef TYPE_CASTS
|
||||
};
|
||||
|
||||
template <size_t Successors>
|
||||
class CFGAryControlInstruction : public CFGControlInstruction
|
||||
{
|
||||
mozilla::Array<CFGBlock*, Successors> successors_;
|
||||
|
||||
public:
|
||||
size_t numSuccessors() const final override {
|
||||
return Successors;
|
||||
}
|
||||
CFGBlock* getSuccessor(size_t i) const final override {
|
||||
return successors_[i];
|
||||
}
|
||||
void replaceSuccessor(size_t i, CFGBlock* succ) final override {
|
||||
successors_[i] = succ;
|
||||
}
|
||||
};
|
||||
|
||||
class CFGTry : public CFGControlInstruction
|
||||
{
|
||||
CFGBlock* tryBlock_;
|
||||
jsbytecode* catchStartPc_;
|
||||
CFGBlock* mergePoint_;
|
||||
|
||||
CFGTry(CFGBlock* successor, jsbytecode* catchStartPc, CFGBlock* mergePoint = nullptr)
|
||||
: tryBlock_(successor),
|
||||
catchStartPc_(catchStartPc),
|
||||
mergePoint_(mergePoint)
|
||||
{ }
|
||||
|
||||
public:
|
||||
CFG_CONTROL_HEADER(Try)
|
||||
TRIVIAL_CFG_NEW_WRAPPERS
|
||||
|
||||
size_t numSuccessors() const final override {
|
||||
return mergePoint_ ? 2 : 1;
|
||||
}
|
||||
CFGBlock* getSuccessor(size_t i) const final override {
|
||||
MOZ_ASSERT(i < numSuccessors());
|
||||
return (i == 0) ? tryBlock_ : mergePoint_;
|
||||
}
|
||||
void replaceSuccessor(size_t i, CFGBlock* succ) final override {
|
||||
MOZ_ASSERT(i < numSuccessors());
|
||||
if (i == 0)
|
||||
tryBlock_ = succ;
|
||||
else
|
||||
mergePoint_ = succ;
|
||||
}
|
||||
|
||||
CFGBlock* tryBlock() const {
|
||||
return getSuccessor(0);
|
||||
}
|
||||
|
||||
jsbytecode* catchStartPc() const {
|
||||
return catchStartPc_;
|
||||
}
|
||||
|
||||
CFGBlock* afterTryCatchBlock() const {
|
||||
return getSuccessor(1);
|
||||
}
|
||||
|
||||
bool codeAfterTryCatchReachable() const {
|
||||
return !!mergePoint_;
|
||||
}
|
||||
};
|
||||
|
||||
class CFGTableSwitch : public CFGControlInstruction
|
||||
{
|
||||
Vector<CFGBlock*, 4, JitAllocPolicy> successors_;
|
||||
size_t low_;
|
||||
size_t high_;
|
||||
|
||||
CFGTableSwitch(TempAllocator& alloc, size_t low, size_t high)
|
||||
: successors_(alloc),
|
||||
low_(low),
|
||||
high_(high)
|
||||
{}
|
||||
|
||||
public:
|
||||
CFG_CONTROL_HEADER(TableSwitch);
|
||||
|
||||
static CFGTableSwitch* New(TempAllocator& alloc, size_t low, size_t high) {
|
||||
return new(alloc) CFGTableSwitch(alloc, low, high);
|
||||
}
|
||||
|
||||
size_t numSuccessors() const final override {
|
||||
return successors_.length();
|
||||
}
|
||||
CFGBlock* getSuccessor(size_t i) const final override {
|
||||
MOZ_ASSERT(i < numSuccessors());
|
||||
return successors_[i];
|
||||
}
|
||||
void replaceSuccessor(size_t i, CFGBlock* succ) final override {
|
||||
MOZ_ASSERT(i < numSuccessors());
|
||||
successors_[i] = succ;
|
||||
}
|
||||
|
||||
bool addDefault(CFGBlock* defaultCase) {
|
||||
MOZ_ASSERT(successors_.length() == 0);
|
||||
return successors_.append(defaultCase);
|
||||
}
|
||||
|
||||
bool addCase(CFGBlock* caseBlock) {
|
||||
MOZ_ASSERT(successors_.length() > 0);
|
||||
return successors_.append(caseBlock);
|
||||
}
|
||||
|
||||
CFGBlock* defaultCase() const {
|
||||
return getSuccessor(0);
|
||||
}
|
||||
|
||||
CFGBlock* getCase(size_t i) const {
|
||||
return getSuccessor(i + 1);
|
||||
}
|
||||
|
||||
size_t high() const {
|
||||
return high_;
|
||||
}
|
||||
|
||||
size_t low() const {
|
||||
return low_;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* CFGCompare
|
||||
*
|
||||
* POP
|
||||
* PEEK
|
||||
* STRICTEQ JUMP succ1
|
||||
* STRICTNEQ JUMP succ2
|
||||
*/
|
||||
class CFGCompare : public CFGAryControlInstruction<2>
|
||||
{
|
||||
CFGCompare(CFGBlock* succ1, CFGBlock* succ2)
|
||||
{
|
||||
replaceSuccessor(0, succ1);
|
||||
replaceSuccessor(1, succ2);
|
||||
}
|
||||
|
||||
public:
|
||||
CFG_CONTROL_HEADER(Compare);
|
||||
TRIVIAL_CFG_NEW_WRAPPERS
|
||||
|
||||
CFGBlock* trueBranch() const {
|
||||
return getSuccessor(0);
|
||||
}
|
||||
CFGBlock* falseBranch() const {
|
||||
return getSuccessor(1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* CFGTest
|
||||
*
|
||||
* POP / PEEK (depending on keepCondition_)
|
||||
* IFEQ JUMP succ1
|
||||
* IFNEQ JUMP succ2
|
||||
*
|
||||
*/
|
||||
class CFGTest : public CFGAryControlInstruction<2>
|
||||
{
|
||||
// By default the condition gets popped. This variable
|
||||
// keeps track if we want to keep the condition.
|
||||
bool keepCondition_;
|
||||
|
||||
CFGTest(CFGBlock* succ1, CFGBlock* succ2)
|
||||
: keepCondition_(false)
|
||||
{
|
||||
replaceSuccessor(0, succ1);
|
||||
replaceSuccessor(1, succ2);
|
||||
}
|
||||
CFGTest(CFGBlock* succ1, CFGBlock* succ2, bool keepCondition)
|
||||
: keepCondition_(keepCondition)
|
||||
{
|
||||
replaceSuccessor(0, succ1);
|
||||
replaceSuccessor(1, succ2);
|
||||
}
|
||||
|
||||
public:
|
||||
CFG_CONTROL_HEADER(Test);
|
||||
TRIVIAL_CFG_NEW_WRAPPERS
|
||||
|
||||
CFGBlock* trueBranch() const {
|
||||
return getSuccessor(0);
|
||||
}
|
||||
CFGBlock* falseBranch() const {
|
||||
return getSuccessor(1);
|
||||
}
|
||||
void keepCondition() {
|
||||
keepCondition_ = true;
|
||||
}
|
||||
bool mustKeepCondition() const {
|
||||
return keepCondition_;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* CFGReturn
|
||||
*
|
||||
* POP
|
||||
* RETURN popped value
|
||||
*
|
||||
*/
|
||||
class CFGReturn : public CFGAryControlInstruction<0>
|
||||
{
|
||||
public:
|
||||
CFG_CONTROL_HEADER(Return);
|
||||
TRIVIAL_CFG_NEW_WRAPPERS
|
||||
};
|
||||
|
||||
/**
|
||||
* CFGRetRVal
|
||||
*
|
||||
* RETURN the value in the return value slot
|
||||
*
|
||||
*/
|
||||
class CFGRetRVal : public CFGAryControlInstruction<0>
|
||||
{
|
||||
public:
|
||||
CFG_CONTROL_HEADER(RetRVal);
|
||||
TRIVIAL_CFG_NEW_WRAPPERS
|
||||
};
|
||||
|
||||
/**
|
||||
* CFGThrow
|
||||
*
|
||||
* POP
|
||||
* THROW popped value
|
||||
*
|
||||
*/
|
||||
class CFGThrow : public CFGAryControlInstruction<0>
|
||||
{
|
||||
public:
|
||||
CFG_CONTROL_HEADER(Throw);
|
||||
TRIVIAL_CFG_NEW_WRAPPERS
|
||||
};
|
||||
|
||||
class CFGUnaryControlInstruction : public CFGAryControlInstruction<1>
|
||||
{
|
||||
public:
|
||||
explicit CFGUnaryControlInstruction(CFGBlock* block) {
|
||||
MOZ_ASSERT(block);
|
||||
replaceSuccessor(0, block);
|
||||
}
|
||||
|
||||
CFGBlock* successor() const {
|
||||
return getSuccessor(0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* CFGGOTO
|
||||
*
|
||||
* POP (x popAmount)
|
||||
* JMP block
|
||||
*
|
||||
*/
|
||||
class CFGGoto : public CFGUnaryControlInstruction
|
||||
{
|
||||
size_t popAmount_;
|
||||
|
||||
explicit CFGGoto(CFGBlock* block)
|
||||
: CFGUnaryControlInstruction(block),
|
||||
popAmount_(0)
|
||||
{}
|
||||
|
||||
CFGGoto(CFGBlock* block, size_t popAmount_)
|
||||
: CFGUnaryControlInstruction(block),
|
||||
popAmount_(popAmount_)
|
||||
{}
|
||||
|
||||
public:
|
||||
CFG_CONTROL_HEADER(Goto);
|
||||
TRIVIAL_CFG_NEW_WRAPPERS
|
||||
|
||||
size_t popAmount() const {
|
||||
return popAmount_;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* CFGBackEdge
|
||||
*
|
||||
* Jumps back to the start of the loop.
|
||||
*
|
||||
* JMP block
|
||||
*
|
||||
*/
|
||||
class CFGBackEdge : public CFGUnaryControlInstruction
|
||||
{
|
||||
explicit CFGBackEdge(CFGBlock* block)
|
||||
: CFGUnaryControlInstruction(block)
|
||||
{}
|
||||
|
||||
public:
|
||||
CFG_CONTROL_HEADER(BackEdge);
|
||||
TRIVIAL_CFG_NEW_WRAPPERS
|
||||
};
|
||||
|
||||
/**
|
||||
* CFGLOOPENTRY
|
||||
*
|
||||
* Indicates the jumping block is the start of a loop.
|
||||
* That block is the only block allowed to have a backedge.
|
||||
*
|
||||
* JMP block
|
||||
*
|
||||
*/
|
||||
class CFGLoopEntry : public CFGUnaryControlInstruction
|
||||
{
|
||||
bool canOsr_;
|
||||
size_t stackPhiCount_;
|
||||
jsbytecode* loopStopPc_;
|
||||
|
||||
CFGLoopEntry(CFGBlock* block, size_t stackPhiCount)
|
||||
: CFGUnaryControlInstruction(block),
|
||||
canOsr_(false),
|
||||
stackPhiCount_(stackPhiCount),
|
||||
loopStopPc_(nullptr)
|
||||
{}
|
||||
|
||||
CFGLoopEntry(CFGBlock* block, bool canOsr, size_t stackPhiCount, jsbytecode* loopStopPc)
|
||||
: CFGUnaryControlInstruction(block),
|
||||
canOsr_(canOsr),
|
||||
stackPhiCount_(stackPhiCount),
|
||||
loopStopPc_(loopStopPc)
|
||||
{}
|
||||
|
||||
public:
|
||||
CFG_CONTROL_HEADER(LoopEntry);
|
||||
TRIVIAL_CFG_NEW_WRAPPERS
|
||||
|
||||
void setCanOsr() {
|
||||
canOsr_ = true;
|
||||
}
|
||||
|
||||
bool canOsr() const {
|
||||
return canOsr_;
|
||||
}
|
||||
|
||||
size_t stackPhiCount() const {
|
||||
return stackPhiCount_;
|
||||
}
|
||||
|
||||
jsbytecode* loopStopPc() const {
|
||||
MOZ_ASSERT(loopStopPc_);
|
||||
return loopStopPc_;
|
||||
}
|
||||
|
||||
void setLoopStopPc(jsbytecode* loopStopPc) {
|
||||
loopStopPc_ = loopStopPc;
|
||||
}
|
||||
};
|
||||
|
||||
typedef Vector<CFGBlock*, 4, JitAllocPolicy> CFGBlockVector;
|
||||
|
||||
class ControlFlowGraph
|
||||
{
|
||||
// Keeps the data alive until destruction.
|
||||
LifoAlloc lifoAlloc_;
|
||||
TempAllocator alloc_;
|
||||
|
||||
// A list of blocks in RPO, containing per block a pc-range and
|
||||
// a control instruction.
|
||||
Vector<CFGBlock, 4, JitAllocPolicy> blocks_;
|
||||
|
||||
public:
|
||||
ControlFlowGraph()
|
||||
: lifoAlloc_(TempAllocator::PreferredLifoChunkSize),
|
||||
alloc_(&lifoAlloc_),
|
||||
blocks_(alloc_)
|
||||
{}
|
||||
|
||||
ControlFlowGraph(const ControlFlowGraph&) = delete;
|
||||
void operator=(const ControlFlowGraph&) = delete;
|
||||
|
||||
void dump(GenericPrinter& print, JSScript* script);
|
||||
bool init(const CFGBlockVector& blocks);
|
||||
|
||||
TempAllocator& alloc() {
|
||||
return alloc_;
|
||||
}
|
||||
|
||||
const CFGBlock* block(size_t i) const {
|
||||
return &blocks_[i];
|
||||
}
|
||||
|
||||
size_t numBlocks() const {
|
||||
return blocks_.length();
|
||||
}
|
||||
};
|
||||
|
||||
class ControlFlowGenerator
|
||||
{
|
||||
static int CmpSuccessors(const void* a, const void* b);
|
||||
|
||||
JSScript* script;
|
||||
CFGBlock* current;
|
||||
jsbytecode* pc;
|
||||
GSNCache gsn;
|
||||
TempAllocator& alloc_;
|
||||
CFGBlockVector blocks_;
|
||||
|
||||
public:
|
||||
ControlFlowGenerator(const ControlFlowGenerator&) = delete;
|
||||
void operator=(const ControlFlowGenerator&) = delete;
|
||||
|
||||
TempAllocator& alloc() {
|
||||
return alloc_;
|
||||
}
|
||||
|
||||
enum class ControlStatus {
|
||||
Error,
|
||||
Abort,
|
||||
Ended, // There is no continuation/join point.
|
||||
Joined, // Created a join node.
|
||||
Jumped, // Parsing another branch at the same level.
|
||||
None // No control flow.
|
||||
};
|
||||
|
||||
struct DeferredEdge : public TempObject
|
||||
{
|
||||
CFGBlock* block;
|
||||
DeferredEdge* next;
|
||||
|
||||
DeferredEdge(CFGBlock* block, DeferredEdge* next)
|
||||
: block(block), next(next)
|
||||
{ }
|
||||
};
|
||||
|
||||
struct ControlFlowInfo {
|
||||
// Entry in the cfgStack.
|
||||
uint32_t cfgEntry;
|
||||
|
||||
// Label that continues go to.
|
||||
jsbytecode* continuepc;
|
||||
|
||||
ControlFlowInfo(uint32_t cfgEntry, jsbytecode* continuepc)
|
||||
: cfgEntry(cfgEntry),
|
||||
continuepc(continuepc)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
// To avoid recursion, the bytecode analyzer uses a stack where each entry
|
||||
// is a small state machine. As we encounter branches or jumps in the
|
||||
// bytecode, we push information about the edges on the stack so that the
|
||||
// CFG can be built in a tree-like fashion.
|
||||
struct CFGState {
|
||||
enum State {
|
||||
IF_TRUE, // if() { }, no else.
|
||||
IF_TRUE_EMPTY_ELSE, // if() { }, empty else
|
||||
IF_ELSE_TRUE, // if() { X } else { }
|
||||
IF_ELSE_FALSE, // if() { } else { X }
|
||||
DO_WHILE_LOOP_BODY, // do { x } while ()
|
||||
DO_WHILE_LOOP_COND, // do { } while (x)
|
||||
WHILE_LOOP_COND, // while (x) { }
|
||||
WHILE_LOOP_BODY, // while () { x }
|
||||
FOR_LOOP_COND, // for (; x;) { }
|
||||
FOR_LOOP_BODY, // for (; ;) { x }
|
||||
FOR_LOOP_UPDATE, // for (; ; x) { }
|
||||
TABLE_SWITCH, // switch() { x }
|
||||
COND_SWITCH_CASE, // switch() { case X: ... }
|
||||
COND_SWITCH_BODY, // switch() { case ...: X }
|
||||
AND_OR, // && x, || x
|
||||
LABEL, // label: x
|
||||
TRY // try { x } catch(e) { }
|
||||
};
|
||||
|
||||
State state; // Current state of this control structure.
|
||||
jsbytecode* stopAt; // Bytecode at which to stop the processing loop.
|
||||
|
||||
// For if structures, this contains branch information.
|
||||
union {
|
||||
struct {
|
||||
CFGBlock* ifFalse;
|
||||
jsbytecode* falseEnd;
|
||||
CFGBlock* ifTrue; // Set when the end of the true path is reached.
|
||||
CFGTest* test;
|
||||
} branch;
|
||||
struct {
|
||||
// Common entry point.
|
||||
CFGBlock* entry;
|
||||
|
||||
// Position of where the loop body starts and ends.
|
||||
jsbytecode* bodyStart;
|
||||
jsbytecode* bodyEnd;
|
||||
|
||||
// pc immediately after the loop exits.
|
||||
jsbytecode* exitpc;
|
||||
|
||||
// Common exit point. Created lazily, so it may be nullptr.
|
||||
CFGBlock* successor;
|
||||
|
||||
// Deferred break and continue targets.
|
||||
DeferredEdge* breaks;
|
||||
DeferredEdge* continues;
|
||||
|
||||
// Initial state, in case loop processing is restarted.
|
||||
State initialState;
|
||||
jsbytecode* initialPc;
|
||||
jsbytecode* initialStopAt;
|
||||
jsbytecode* loopHead;
|
||||
|
||||
// For-loops only.
|
||||
jsbytecode* condpc;
|
||||
jsbytecode* updatepc;
|
||||
jsbytecode* updateEnd;
|
||||
} loop;
|
||||
struct {
|
||||
// Vector of body blocks to process after the cases.
|
||||
FixedList<CFGBlock*>* bodies;
|
||||
|
||||
// When processing case statements, this counter points at the
|
||||
// last uninitialized body. When processing bodies, this
|
||||
// counter targets the next body to process.
|
||||
uint32_t currentIdx;
|
||||
|
||||
// Remember the block index of the default case.
|
||||
jsbytecode* defaultTarget;
|
||||
uint32_t defaultIdx;
|
||||
|
||||
// Block immediately after the switch.
|
||||
jsbytecode* exitpc;
|
||||
DeferredEdge* breaks;
|
||||
} switch_;
|
||||
struct {
|
||||
DeferredEdge* breaks;
|
||||
} label;
|
||||
struct {
|
||||
CFGBlock* successor;
|
||||
} try_;
|
||||
};
|
||||
|
||||
inline bool isLoop() const {
|
||||
switch (state) {
|
||||
case DO_WHILE_LOOP_COND:
|
||||
case DO_WHILE_LOOP_BODY:
|
||||
case WHILE_LOOP_COND:
|
||||
case WHILE_LOOP_BODY:
|
||||
case FOR_LOOP_COND:
|
||||
case FOR_LOOP_BODY:
|
||||
case FOR_LOOP_UPDATE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static CFGState If(jsbytecode* join, CFGTest* test);
|
||||
static CFGState IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd, CFGTest* test);
|
||||
static CFGState AndOr(jsbytecode* join, CFGBlock* lhs);
|
||||
static CFGState TableSwitch(TempAllocator& alloc, jsbytecode* exitpc);
|
||||
static CFGState CondSwitch(TempAllocator& alloc, jsbytecode* exitpc,
|
||||
jsbytecode* defaultTarget);
|
||||
static CFGState Label(jsbytecode* exitpc);
|
||||
static CFGState Try(jsbytecode* exitpc, CFGBlock* successor);
|
||||
};
|
||||
|
||||
Vector<CFGState, 8, JitAllocPolicy> cfgStack_;
|
||||
Vector<ControlFlowInfo, 4, JitAllocPolicy> loops_;
|
||||
Vector<ControlFlowInfo, 0, JitAllocPolicy> switches_;
|
||||
Vector<ControlFlowInfo, 2, JitAllocPolicy> labels_;
|
||||
BytecodeAnalysis analysis_;
|
||||
bool aborted_;
|
||||
|
||||
public:
|
||||
ControlFlowGenerator(TempAllocator& alloc, JSScript* script);
|
||||
|
||||
MOZ_MUST_USE bool init();
|
||||
|
||||
MOZ_MUST_USE bool traverseBytecode();
|
||||
MOZ_MUST_USE bool addBlock(CFGBlock* block);
|
||||
ControlFlowGraph* getGraph() {
|
||||
ControlFlowGraph* cfg = js_new<ControlFlowGraph>();
|
||||
if (!cfg)
|
||||
return nullptr;
|
||||
if (!cfg->init(blocks_)) {
|
||||
js_delete(cfg);
|
||||
return nullptr;
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
||||
bool aborted() const {
|
||||
return aborted_;
|
||||
}
|
||||
|
||||
private:
|
||||
void popCfgStack();
|
||||
MOZ_MUST_USE bool processDeferredContinues(CFGState& state);
|
||||
ControlStatus processControlEnd();
|
||||
ControlStatus processCfgStack();
|
||||
ControlStatus processCfgEntry(CFGState& state);
|
||||
ControlStatus processIfStart(JSOp op);
|
||||
ControlStatus processIfEnd(CFGState& state);
|
||||
ControlStatus processIfElseTrueEnd(CFGState& state);
|
||||
ControlStatus processIfElseFalseEnd(CFGState& state);
|
||||
ControlStatus processDoWhileLoop(JSOp op, jssrcnote* sn);
|
||||
ControlStatus processDoWhileBodyEnd(CFGState& state);
|
||||
ControlStatus processDoWhileCondEnd(CFGState& state);
|
||||
ControlStatus processWhileCondEnd(CFGState& state);
|
||||
ControlStatus processWhileBodyEnd(CFGState& state);
|
||||
ControlStatus processForLoop(JSOp op, jssrcnote* sn);
|
||||
ControlStatus processForCondEnd(CFGState& state);
|
||||
ControlStatus processForBodyEnd(CFGState& state);
|
||||
ControlStatus processForUpdateEnd(CFGState& state);
|
||||
ControlStatus processWhileOrForInLoop(jssrcnote* sn);
|
||||
ControlStatus processNextTableSwitchCase(CFGState& state);
|
||||
ControlStatus processCondSwitch();
|
||||
ControlStatus processCondSwitchCase(CFGState& state);
|
||||
ControlStatus processCondSwitchDefault(CFGState& state);
|
||||
ControlStatus processCondSwitchBody(CFGState& state);
|
||||
ControlStatus processSwitchBreak(JSOp op);
|
||||
ControlStatus processSwitchEnd(DeferredEdge* breaks, jsbytecode* exitpc);
|
||||
ControlStatus processTry();
|
||||
ControlStatus processTryEnd(CFGState& state);
|
||||
ControlStatus processThrow();
|
||||
ControlStatus processTableSwitch(JSOp op, jssrcnote* sn);
|
||||
ControlStatus processContinue(JSOp op);
|
||||
ControlStatus processBreak(JSOp op, jssrcnote* sn);
|
||||
ControlStatus processReturn(JSOp op);
|
||||
ControlStatus maybeLoop(JSOp op, jssrcnote* sn);
|
||||
ControlStatus snoopControlFlow(JSOp op);
|
||||
ControlStatus processBrokenLoop(CFGState& state);
|
||||
ControlStatus finishLoop(CFGState& state, CFGBlock* successor);
|
||||
ControlStatus processAndOr(JSOp op);
|
||||
ControlStatus processAndOrEnd(CFGState& state);
|
||||
ControlStatus processLabel();
|
||||
ControlStatus processLabelEnd(CFGState& state);
|
||||
|
||||
MOZ_MUST_USE bool pushLoop(CFGState::State state, jsbytecode* stopAt, CFGBlock* entry,
|
||||
jsbytecode* loopHead, jsbytecode* initialPc,
|
||||
jsbytecode* bodyStart, jsbytecode* bodyEnd,
|
||||
jsbytecode* exitpc, jsbytecode* continuepc);
|
||||
void endCurrentBlock(CFGControlInstruction* ins);
|
||||
CFGBlock* createBreakCatchBlock(DeferredEdge* edge, jsbytecode* pc);
|
||||
};
|
||||
|
||||
} // namespace jit
|
||||
} // namespace js
|
||||
|
||||
#endif /* jit_IonControlFlow_h */
|
|
@ -441,6 +441,7 @@ jit::CheckLogging()
|
|||
" profiling Profiling-related information\n"
|
||||
" trackopts Optimization tracking information\n"
|
||||
" dump-mir-expr Dump the MIR expressions\n"
|
||||
" cfg Control flow graph generation\n"
|
||||
" all Everything\n"
|
||||
"\n"
|
||||
" bl-aborts Baseline compiler abort messages\n"
|
||||
|
@ -517,6 +518,8 @@ jit::CheckLogging()
|
|||
EnableChannel(JitSpew_OptimizationTracking);
|
||||
if (ContainsFlag(env, "dump-mir-expr"))
|
||||
EnableChannel(JitSpew_MIRExpressions);
|
||||
if (ContainsFlag(env, "cfg"))
|
||||
EnableChannel(JitSpew_CFG);
|
||||
if (ContainsFlag(env, "all"))
|
||||
LoggingBits = uint64_t(-1);
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@ namespace jit {
|
|||
_(CacheFlush) \
|
||||
/* Output a list of MIR expressions */ \
|
||||
_(MIRExpressions) \
|
||||
/* Print control flow graph */ \
|
||||
_(CFG) \
|
||||
\
|
||||
/* BASELINE COMPILER SPEW */ \
|
||||
\
|
||||
|
|
|
@ -4141,7 +4141,10 @@ MResumePoint::dump(GenericPrinter& out) const
|
|||
|
||||
switch (mode()) {
|
||||
case MResumePoint::ResumeAt:
|
||||
out.printf("At");
|
||||
if (instruction_)
|
||||
out.printf("At(%d)", instruction_->id());
|
||||
else
|
||||
out.printf("At");
|
||||
break;
|
||||
case MResumePoint::ResumeAfter:
|
||||
out.printf("After");
|
||||
|
|
|
@ -2841,13 +2841,11 @@ class MTableSwitch final
|
|||
{
|
||||
// The successors of the tableswitch
|
||||
// - First successor = the default case
|
||||
// - Successor 2 and higher = the cases sorted on case index.
|
||||
// - Successors 2 and higher = the cases
|
||||
Vector<MBasicBlock*, 0, JitAllocPolicy> successors_;
|
||||
// Index into successors_ sorted on case index
|
||||
Vector<size_t, 0, JitAllocPolicy> cases_;
|
||||
|
||||
// Contains the blocks/cases that still need to get build
|
||||
Vector<MBasicBlock*, 0, JitAllocPolicy> blocks_;
|
||||
|
||||
MUse operand_;
|
||||
int32_t low_;
|
||||
int32_t high_;
|
||||
|
@ -2861,7 +2859,6 @@ class MTableSwitch final
|
|||
int32_t low, int32_t high)
|
||||
: successors_(alloc),
|
||||
cases_(alloc),
|
||||
blocks_(alloc),
|
||||
low_(low),
|
||||
high_(high)
|
||||
{
|
||||
|
@ -2904,14 +2901,6 @@ class MTableSwitch final
|
|||
successors_[i] = successor;
|
||||
}
|
||||
|
||||
MBasicBlock** blocks() {
|
||||
return &blocks_[0];
|
||||
}
|
||||
|
||||
size_t numBlocks() const {
|
||||
return blocks_.length();
|
||||
}
|
||||
|
||||
int32_t low() const {
|
||||
return low_;
|
||||
}
|
||||
|
@ -2928,10 +2917,6 @@ class MTableSwitch final
|
|||
return getSuccessor(cases_[i]);
|
||||
}
|
||||
|
||||
size_t numCases() const {
|
||||
return high() - low() + 1;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool addDefault(MBasicBlock* block, size_t* index = nullptr) {
|
||||
MOZ_ASSERT(successors_.empty());
|
||||
if (index)
|
||||
|
@ -2943,13 +2928,8 @@ class MTableSwitch final
|
|||
return cases_.append(successorIndex);
|
||||
}
|
||||
|
||||
MBasicBlock* getBlock(size_t i) const {
|
||||
MOZ_ASSERT(i < numBlocks());
|
||||
return blocks_[i];
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool addBlock(MBasicBlock* block) {
|
||||
return blocks_.append(block);
|
||||
size_t numCases() const {
|
||||
return high() - low() + 1;
|
||||
}
|
||||
|
||||
MDefinition* getOperand(size_t index) const override {
|
||||
|
|
|
@ -141,20 +141,77 @@ MIRGraph::renumberBlocksAfter(MBasicBlock* at)
|
|||
iter->setId(++id);
|
||||
}
|
||||
|
||||
void
|
||||
MIRGraph::removeBlocksAfter(MBasicBlock* start)
|
||||
bool
|
||||
MIRGraph::removeSuccessorBlocks(MBasicBlock* start)
|
||||
{
|
||||
MBasicBlockIterator iter(begin());
|
||||
iter++;
|
||||
while (iter != end()) {
|
||||
MBasicBlock* block = *iter;
|
||||
iter++;
|
||||
if (!start->hasLastIns())
|
||||
return true;
|
||||
|
||||
if (block->id() <= start->id())
|
||||
start->mark();
|
||||
|
||||
// Mark all successors.
|
||||
Vector<MBasicBlock*, 4, SystemAllocPolicy> blocks;
|
||||
for (size_t i = 0; i < start->numSuccessors(); i++) {
|
||||
if (start->getSuccessor(i)->isMarked())
|
||||
continue;
|
||||
if (!blocks.append(start->getSuccessor(i)))
|
||||
return false;
|
||||
start->getSuccessor(i)->mark();
|
||||
}
|
||||
for (size_t i = 0; i < blocks.length(); i++) {
|
||||
MBasicBlock* block = blocks[i];
|
||||
if (!block->hasLastIns())
|
||||
continue;
|
||||
|
||||
removeBlock(block);
|
||||
for (size_t j = 0; j < block->numSuccessors(); j++) {
|
||||
if (block->getSuccessor(j)->isMarked())
|
||||
continue;
|
||||
if (!blocks.append(block->getSuccessor(j)))
|
||||
return false;
|
||||
block->getSuccessor(j)->mark();
|
||||
}
|
||||
}
|
||||
|
||||
if (osrBlock()) {
|
||||
if (osrBlock()->getSuccessor(0)->isMarked())
|
||||
osrBlock()->mark();
|
||||
}
|
||||
|
||||
// Remove blocks.
|
||||
// If they don't have any predecessor
|
||||
for (size_t i = 0; i < blocks.length(); i++) {
|
||||
MBasicBlock* block = blocks[i];
|
||||
bool allMarked = true;
|
||||
for (size_t i = 0; i < block->numPredecessors(); i++) {
|
||||
if (block->getPredecessor(i)->isMarked())
|
||||
continue;
|
||||
allMarked = false;
|
||||
break;
|
||||
}
|
||||
if (allMarked) {
|
||||
removeBlock(block);
|
||||
} else {
|
||||
MOZ_ASSERT(block != osrBlock());
|
||||
for (size_t j = 0; j < block->numPredecessors(); j++) {
|
||||
if (!block->getPredecessor(j)->isMarked())
|
||||
continue;
|
||||
block->removePredecessor(block->getPredecessor(j));
|
||||
}
|
||||
// This shouldn't have any instructions yet.
|
||||
MOZ_ASSERT(block->begin() == block->end());
|
||||
}
|
||||
}
|
||||
|
||||
if (osrBlock()) {
|
||||
if (osrBlock()->getSuccessor(0)->isDead())
|
||||
removeBlock(osrBlock());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < blocks.length(); i++)
|
||||
blocks[i]->unmark();
|
||||
start->unmark();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -175,18 +232,13 @@ MIRGraph::removeBlock(MBasicBlock* block)
|
|||
}
|
||||
}
|
||||
|
||||
block->discardAllInstructions();
|
||||
block->discardAllResumePoints();
|
||||
|
||||
// Note: phis are disconnected from the rest of the graph, but are not
|
||||
// removed entirely. If the block being removed is a loop header then
|
||||
// IonBuilder may need to access these phis to more quickly converge on the
|
||||
// possible types in the graph. See IonBuilder::analyzeNewLoopTypes.
|
||||
block->discardAllPhiOperands();
|
||||
|
||||
block->clear();
|
||||
block->markAsDead();
|
||||
blocks_.remove(block);
|
||||
numBlocks_--;
|
||||
|
||||
if (block->isInList()) {
|
||||
blocks_.remove(block);
|
||||
numBlocks_--;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -403,6 +455,7 @@ MBasicBlock::New(MIRGraph& graph, const CompileInfo& info, MBasicBlock* pred, Ki
|
|||
|
||||
MBasicBlock::MBasicBlock(MIRGraph& graph, const CompileInfo& info, BytecodeSite* site, Kind kind)
|
||||
: unreachable_(false),
|
||||
specialized_(false),
|
||||
graph_(graph),
|
||||
info_(info),
|
||||
predecessors_(graph.alloc()),
|
||||
|
@ -1005,6 +1058,19 @@ MBasicBlock::discardAllResumePoints(bool discardEntry)
|
|||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
MBasicBlock::clear()
|
||||
{
|
||||
discardAllInstructions();
|
||||
discardAllResumePoints();
|
||||
|
||||
// Note: phis are disconnected from the rest of the graph, but are not
|
||||
// removed entirely. If the block being removed is a loop header then
|
||||
// IonBuilder may need to access these phis to more quickly converge on the
|
||||
// possible types in the graph. See IonBuilder::analyzeNewLoopTypes.
|
||||
discardAllPhiOperands();
|
||||
}
|
||||
|
||||
void
|
||||
MBasicBlock::insertBefore(MInstruction* at, MInstruction* ins)
|
||||
{
|
||||
|
@ -1552,6 +1618,10 @@ MBasicBlock::inheritPhisFromBackedge(TempAllocator& alloc, MBasicBlock* backedge
|
|||
bool
|
||||
MBasicBlock::specializePhis(TempAllocator& alloc)
|
||||
{
|
||||
if (specialized_)
|
||||
return true;
|
||||
|
||||
specialized_ = true;
|
||||
for (MPhiIterator iter = phisBegin(); iter != phisEnd(); iter++) {
|
||||
MPhi* phi = *iter;
|
||||
if (!phi->specializeType(alloc))
|
||||
|
@ -1590,7 +1660,6 @@ MBasicBlock::immediateDominatorBranch(BranchDirection* pdirection)
|
|||
|
||||
MBasicBlock::BackupPoint::BackupPoint(MBasicBlock* current)
|
||||
: current_(current),
|
||||
lastBlock_(nullptr),
|
||||
lastIns_(current->hasAnyIns() ? *current->rbegin() : nullptr),
|
||||
stackPosition_(current->stackDepth()),
|
||||
slots_()
|
||||
|
@ -1605,17 +1674,6 @@ MBasicBlock::BackupPoint::BackupPoint(MBasicBlock* current)
|
|||
{
|
||||
// The block is not yet jumping into a block of an inlined function yet.
|
||||
MOZ_ASSERT(current->outerResumePoint_ == nullptr);
|
||||
|
||||
// The CFG reconstruction might add blocks and move them around.
|
||||
uint32_t lastBlockId = 0;
|
||||
PostorderIterator e = current->graph().poEnd();
|
||||
for (PostorderIterator b = current->graph().poBegin(); b != e; ++b) {
|
||||
if (lastBlockId <= b->id()) {
|
||||
lastBlock_ = *b;
|
||||
lastBlockId = b->id();
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(lastBlock_);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -1663,6 +1721,10 @@ MBasicBlock::BackupPoint::restore()
|
|||
|
||||
MOZ_ASSERT_IF(lastIns_, lastIns_->block() == current_);
|
||||
MOZ_ASSERT_IF(lastIns_, !lastIns_->isDiscarded());
|
||||
|
||||
if (!current_->graph().removeSuccessorBlocks(current_))
|
||||
return nullptr;
|
||||
|
||||
MInstructionIterator lastIns(lastIns_ ? ++(current_->begin(lastIns_)) : current_->begin());
|
||||
current_->discardAllInstructionsStartingAt(lastIns);
|
||||
current_->clearOuterResumePoint();
|
||||
|
@ -1679,8 +1741,6 @@ MBasicBlock::BackupPoint::restore()
|
|||
MOZ_ASSERT(current_->callerResumePoint() == callerResumePoint_);
|
||||
MOZ_ASSERT(current_->entryResumePoint() == entryResumePoint_);
|
||||
|
||||
current_->graph().removeBlocksAfter(lastBlock_);
|
||||
|
||||
return current_;
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,9 @@ class MBasicBlock : public TempObject, public InlineListNode<MBasicBlock>
|
|||
// This block cannot be reached by any means.
|
||||
bool unreachable_;
|
||||
|
||||
// Keeps track if the phis has been type specialized already.
|
||||
bool specialized_;
|
||||
|
||||
// Pushes a copy of a local variable or argument.
|
||||
void pushVariable(uint32_t slot);
|
||||
|
||||
|
@ -304,6 +307,7 @@ class MBasicBlock : public TempObject, public InlineListNode<MBasicBlock>
|
|||
void discardAllPhiOperands();
|
||||
void discardAllPhis();
|
||||
void discardAllResumePoints(bool discardEntry = true);
|
||||
void clear();
|
||||
|
||||
// Same as |void discard(MInstruction* ins)| but assuming that
|
||||
// all operands are already discarded.
|
||||
|
@ -662,7 +666,6 @@ class MBasicBlock : public TempObject, public InlineListNode<MBasicBlock>
|
|||
friend MBasicBlock;
|
||||
|
||||
MBasicBlock* current_;
|
||||
MBasicBlock* lastBlock_;
|
||||
MInstruction* lastIns_;
|
||||
uint32_t stackPosition_;
|
||||
FixedList<MDefinition*> slots_;
|
||||
|
@ -839,12 +842,12 @@ class MIRGraph
|
|||
ReversePostorderIterator rpoEnd() {
|
||||
return blocks_.end();
|
||||
}
|
||||
void removeBlocksAfter(MBasicBlock* block);
|
||||
MOZ_MUST_USE bool removeSuccessorBlocks(MBasicBlock* block);
|
||||
void removeBlock(MBasicBlock* block);
|
||||
void removeBlockIncludingPhis(MBasicBlock* block);
|
||||
void moveBlockToEnd(MBasicBlock* block) {
|
||||
MOZ_ASSERT(block->id());
|
||||
blocks_.remove(block);
|
||||
MOZ_ASSERT_IF(!blocks_.empty(), block->id());
|
||||
blocks_.pushBack(block);
|
||||
}
|
||||
void moveBlockBefore(MBasicBlock* at, MBasicBlock* block) {
|
||||
|
@ -852,6 +855,10 @@ class MIRGraph
|
|||
blocks_.remove(block);
|
||||
blocks_.insertBefore(at, block);
|
||||
}
|
||||
void removeBlockFromList(MBasicBlock* block) {
|
||||
blocks_.remove(block);
|
||||
numBlocks_--;
|
||||
}
|
||||
size_t numBlocks() const {
|
||||
return numBlocks_;
|
||||
}
|
||||
|
|
|
@ -236,6 +236,7 @@ UNIFIED_SOURCES += [
|
|||
'jit/IonAnalysis.cpp',
|
||||
'jit/IonBuilder.cpp',
|
||||
'jit/IonCaches.cpp',
|
||||
'jit/IonControlFlow.cpp',
|
||||
'jit/IonOptimizationLevels.cpp',
|
||||
'jit/JitcodeMap.cpp',
|
||||
'jit/JitFrames.cpp',
|
||||
|
|
Загрузка…
Ссылка в новой задаче