Mypal/js/src/jit/CompileInfo.h
2021-02-04 16:48:36 +02:00

560 lines
17 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* 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_CompileInfo_h
#define jit_CompileInfo_h
#include "mozilla/Maybe.h"
#include "jsfun.h"
#include "jit/JitAllocPolicy.h"
#include "jit/JitFrames.h"
#include "jit/Registers.h"
#include "vm/EnvironmentObject.h"
namespace js {
namespace jit {
class TrackedOptimizations;
inline unsigned
StartArgSlot(JSScript* script)
{
// Reserved slots:
// Slot 0: Environment chain.
// Slot 1: Return value.
// When needed:
// Slot 2: Argumentsobject.
// Note: when updating this, please also update the assert in SnapshotWriter::startFrame
return 2 + (script->argumentsHasVarBinding() ? 1 : 0);
}
inline unsigned
CountArgSlots(JSScript* script, JSFunction* fun)
{
// Slot x + 0: This value.
// Slot x + 1: Argument 1.
// ...
// Slot x + n: Argument n.
// Note: when updating this, please also update the assert in SnapshotWriter::startFrame
return StartArgSlot(script) + (fun ? fun->nargs() + 1 : 0);
}
// The compiler at various points needs to be able to store references to the
// current inline path (the sequence of scripts and call-pcs that lead to the
// current function being inlined).
//
// To support this, the top-level IonBuilder keeps a tree that records the
// inlinings done during compilation.
class InlineScriptTree {
// InlineScriptTree for the caller
InlineScriptTree* caller_;
// PC in the caller corresponding to this script.
jsbytecode* callerPc_;
// Script for this entry.
JSScript* script_;
// Child entries (linked together by nextCallee pointer)
InlineScriptTree* children_;
InlineScriptTree* nextCallee_;
public:
InlineScriptTree(InlineScriptTree* caller, jsbytecode* callerPc, JSScript* script)
: caller_(caller), callerPc_(callerPc), script_(script),
children_(nullptr), nextCallee_(nullptr)
{}
static InlineScriptTree* New(TempAllocator* allocator, InlineScriptTree* caller,
jsbytecode* callerPc, JSScript* script);
InlineScriptTree* addCallee(TempAllocator* allocator, jsbytecode* callerPc,
JSScript* calleeScript);
InlineScriptTree* caller() const {
return caller_;
}
bool isOutermostCaller() const {
return caller_ == nullptr;
}
bool hasCaller() const {
return caller_ != nullptr;
}
InlineScriptTree* outermostCaller() {
if (isOutermostCaller())
return this;
return caller_->outermostCaller();
}
jsbytecode* callerPc() const {
return callerPc_;
}
JSScript* script() const {
return script_;
}
bool hasChildren() const {
return children_ != nullptr;
}
InlineScriptTree* firstChild() const {
MOZ_ASSERT(hasChildren());
return children_;
}
bool hasNextCallee() const {
return nextCallee_ != nullptr;
}
InlineScriptTree* nextCallee() const {
MOZ_ASSERT(hasNextCallee());
return nextCallee_;
}
unsigned depth() const {
if (isOutermostCaller())
return 1;
return 1 + caller_->depth();
}
};
class BytecodeSite : public TempObject
{
// InlineScriptTree identifying innermost active function at site.
InlineScriptTree* tree_;
// Bytecode address within innermost active function.
jsbytecode* pc_;
// Optimization information at the pc.
TrackedOptimizations* optimizations_;
public:
BytecodeSite()
: tree_(nullptr), pc_(nullptr), optimizations_(nullptr)
{}
BytecodeSite(InlineScriptTree* tree, jsbytecode* pc)
: tree_(tree), pc_(pc), optimizations_(nullptr)
{
MOZ_ASSERT(tree_ != nullptr);
MOZ_ASSERT(pc_ != nullptr);
}
InlineScriptTree* tree() const {
return tree_;
}
jsbytecode* pc() const {
return pc_;
}
JSScript* script() const {
return tree_ ? tree_->script() : nullptr;
}
bool hasOptimizations() const {
return !!optimizations_;
}
TrackedOptimizations* optimizations() const {
MOZ_ASSERT(hasOptimizations());
return optimizations_;
}
void setOptimizations(TrackedOptimizations* optimizations) {
optimizations_ = optimizations;
}
};
enum AnalysisMode {
/* JavaScript execution, not analysis. */
Analysis_None,
/*
* MIR analysis performed when invoking 'new' on a script, to determine
* definite properties. Used by the optimizing JIT.
*/
Analysis_DefiniteProperties,
/*
* MIR analysis performed when executing a script which uses its arguments,
* when it is not known whether a lazy arguments value can be used.
*/
Analysis_ArgumentsUsage
};
// Contains information about the compilation source for IR being generated.
class CompileInfo
{
public:
CompileInfo(JSScript* script, JSFunction* fun, jsbytecode* osrPc,
AnalysisMode analysisMode, bool scriptNeedsArgsObj,
InlineScriptTree* inlineScriptTree)
: script_(script), fun_(fun), osrPc_(osrPc),
analysisMode_(analysisMode), scriptNeedsArgsObj_(scriptNeedsArgsObj),
hadOverflowBailout_(script->hadOverflowBailout()),
mayReadFrameArgsDirectly_(script->mayReadFrameArgsDirectly()),
inlineScriptTree_(inlineScriptTree)
{
MOZ_ASSERT_IF(osrPc, JSOp(*osrPc) == JSOP_LOOPENTRY);
// The function here can flow in from anywhere so look up the canonical
// function to ensure that we do not try to embed a nursery pointer in
// jit-code. Precisely because it can flow in from anywhere, it's not
// guaranteed to be non-lazy. Hence, don't access its script!
if (fun_) {
fun_ = fun_->nonLazyScript()->functionNonDelazifying();
MOZ_ASSERT(fun_->isTenured());
}
nimplicit_ = StartArgSlot(script) /* env chain and argument obj */
+ (fun ? 1 : 0); /* this */
nargs_ = fun ? fun->nargs() : 0;
nlocals_ = script->nfixed();
// An extra slot is needed for global scopes because INITGLEXICAL (stack
// depth 1) is compiled as a SETPROP (stack depth 2) on the global lexical
// scope.
uint32_t extra = script->isGlobalCode() ? 1 : 0;
nstack_ = Max<unsigned>(script->nslots() - script->nfixed(), MinJITStackSize) + extra;
nslots_ = nimplicit_ + nargs_ + nlocals_ + nstack_;
// For derived class constructors, find and cache the frame slot for
// the .this binding. This slot is assumed to be always
// observable. See isObservableFrameSlot.
if (script->isDerivedClassConstructor()) {
MOZ_ASSERT(script->functionHasThisBinding());
CompileRuntime* runtime = GetJitContext()->runtime;
for (BindingIter bi(script); bi; bi++) {
if (bi.name() != runtime->names().dotThis)
continue;
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Frame) {
thisSlotForDerivedClassConstructor_ = mozilla::Some(localSlot(loc.slot()));
break;
}
}
}
}
explicit CompileInfo(unsigned nlocals)
: script_(nullptr), fun_(nullptr), osrPc_(nullptr),
analysisMode_(Analysis_None), scriptNeedsArgsObj_(false),
mayReadFrameArgsDirectly_(false), inlineScriptTree_(nullptr)
{
nimplicit_ = 0;
nargs_ = 0;
nlocals_ = nlocals;
nstack_ = 1; /* For FunctionCompiler::pushPhiInput/popPhiOutput */
nslots_ = nlocals_ + nstack_;
}
JSScript* script() const {
return script_;
}
bool compilingWasm() const {
return script() == nullptr;
}
JSFunction* funMaybeLazy() const {
return fun_;
}
ModuleObject* module() const {
return script_->module();
}
jsbytecode* osrPc() const {
return osrPc_;
}
InlineScriptTree* inlineScriptTree() const {
return inlineScriptTree_;
}
bool hasOsrAt(jsbytecode* pc) const {
MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY);
return pc == osrPc();
}
jsbytecode* startPC() const {
return script_->code();
}
jsbytecode* limitPC() const {
return script_->codeEnd();
}
const char* filename() const {
return script_->filename();
}
unsigned lineno() const {
return script_->lineno();
}
unsigned lineno(jsbytecode* pc) const {
return PCToLineNumber(script_, pc);
}
// Script accessors based on PC.
JSAtom* getAtom(jsbytecode* pc) const {
return script_->getAtom(GET_UINT32_INDEX(pc));
}
PropertyName* getName(jsbytecode* pc) const {
return script_->getName(GET_UINT32_INDEX(pc));
}
inline RegExpObject* getRegExp(jsbytecode* pc) const;
JSObject* getObject(jsbytecode* pc) const {
return script_->getObject(GET_UINT32_INDEX(pc));
}
inline JSFunction* getFunction(jsbytecode* pc) const;
const Value& getConst(jsbytecode* pc) const {
return script_->getConst(GET_UINT32_INDEX(pc));
}
jssrcnote* getNote(GSNCache& gsn, jsbytecode* pc) const {
return GetSrcNote(gsn, script(), pc);
}
// Total number of slots: args, locals, and stack.
unsigned nslots() const {
return nslots_;
}
// Number of slots needed for env chain, return value,
// maybe argumentsobject and this value.
unsigned nimplicit() const {
return nimplicit_;
}
// Number of arguments (without counting this value).
unsigned nargs() const {
return nargs_;
}
// Number of slots needed for all local variables. This includes "fixed
// vars" (see above) and also block-scoped locals.
unsigned nlocals() const {
return nlocals_;
}
unsigned ninvoke() const {
return nslots_ - nstack_;
}
uint32_t environmentChainSlot() const {
MOZ_ASSERT(script());
return 0;
}
uint32_t returnValueSlot() const {
MOZ_ASSERT(script());
return 1;
}
uint32_t argsObjSlot() const {
MOZ_ASSERT(hasArguments());
return 2;
}
uint32_t thisSlot() const {
MOZ_ASSERT(funMaybeLazy());
MOZ_ASSERT(nimplicit_ > 0);
return nimplicit_ - 1;
}
uint32_t firstArgSlot() const {
return nimplicit_;
}
uint32_t argSlotUnchecked(uint32_t i) const {
// During initialization, some routines need to get at arg
// slots regardless of how regular argument access is done.
MOZ_ASSERT(i < nargs_);
return nimplicit_ + i;
}
uint32_t argSlot(uint32_t i) const {
// This should only be accessed when compiling functions for
// which argument accesses don't need to go through the
// argument object.
MOZ_ASSERT(!argsObjAliasesFormals());
return argSlotUnchecked(i);
}
uint32_t firstLocalSlot() const {
return nimplicit_ + nargs_;
}
uint32_t localSlot(uint32_t i) const {
return firstLocalSlot() + i;
}
uint32_t firstStackSlot() const {
return firstLocalSlot() + nlocals();
}
uint32_t stackSlot(uint32_t i) const {
return firstStackSlot() + i;
}
uint32_t startArgSlot() const {
MOZ_ASSERT(script());
return StartArgSlot(script());
}
uint32_t endArgSlot() const {
MOZ_ASSERT(script());
return CountArgSlots(script(), funMaybeLazy());
}
uint32_t totalSlots() const {
MOZ_ASSERT(script() && funMaybeLazy());
return nimplicit() + nargs() + nlocals();
}
bool isSlotAliased(uint32_t index) const {
MOZ_ASSERT(index >= startArgSlot());
uint32_t arg = index - firstArgSlot();
if (arg < nargs())
return script()->formalIsAliased(arg);
return false;
}
bool hasArguments() const {
return script()->argumentsHasVarBinding();
}
bool argumentsAliasesFormals() const {
return script()->argumentsAliasesFormals();
}
bool needsArgsObj() const {
return scriptNeedsArgsObj_;
}
bool argsObjAliasesFormals() const {
return scriptNeedsArgsObj_ && script()->hasMappedArgsObj();
}
AnalysisMode analysisMode() const {
return analysisMode_;
}
bool isAnalysis() const {
return analysisMode_ != Analysis_None;
}
// Returns true if a slot can be observed out-side the current frame while
// the frame is active on the stack. This implies that these definitions
// would have to be executed and that they cannot be removed even if they
// are unused.
bool isObservableSlot(uint32_t slot) const {
if (isObservableFrameSlot(slot))
return true;
if (isObservableArgumentSlot(slot))
return true;
return false;
}
bool isObservableFrameSlot(uint32_t slot) const {
if (!funMaybeLazy())
return false;
// The |this| value must always be observable.
if (slot == thisSlot())
return true;
// The |this| frame slot in derived class constructors should never be
// optimized out, as a Debugger might need to perform TDZ checks on it
// via, e.g., an exceptionUnwind handler. The TDZ check is required
// for correctness if the handler decides to continue execution.
if (thisSlotForDerivedClassConstructor_ && *thisSlotForDerivedClassConstructor_ == slot)
return true;
if (funMaybeLazy()->needsSomeEnvironmentObject() && slot == environmentChainSlot())
return true;
// If the function may need an arguments object, then make sure to
// preserve the env chain, because it may be needed to construct the
// arguments object during bailout. If we've already created an
// arguments object (or got one via OSR), preserve that as well.
if (hasArguments() && (slot == environmentChainSlot() || slot == argsObjSlot()))
return true;
return false;
}
bool isObservableArgumentSlot(uint32_t slot) const {
if (!funMaybeLazy())
return false;
// Function.arguments can be used to access all arguments in non-strict
// scripts, so we can't optimize out any arguments.
if ((mayReadFrameArgsDirectly_ || !script()->strict()) &&
firstArgSlot() <= slot && slot - firstArgSlot() < nargs())
{
return true;
}
return false;
}
// Returns true if a slot can be recovered before or during a bailout. A
// definition which can be observed and recovered, implies that this
// definition can be optimized away as long as we can compute its values.
bool isRecoverableOperand(uint32_t slot) const {
// If this script is not a function, then none of the slots are
// observable. If it this |slot| is not observable, thus we can always
// recover it.
if (!funMaybeLazy())
return true;
// The |this| and the |envChain| values can be recovered.
if (slot == thisSlot() || slot == environmentChainSlot())
return true;
if (isObservableFrameSlot(slot))
return false;
if (needsArgsObj() && isObservableArgumentSlot(slot))
return false;
return true;
}
// Check previous bailout states to prevent doing the same bailout in the
// next compilation.
bool hadOverflowBailout() const {
return hadOverflowBailout_;
}
bool mayReadFrameArgsDirectly() const {
return mayReadFrameArgsDirectly_;
}
private:
unsigned nimplicit_;
unsigned nargs_;
unsigned nlocals_;
unsigned nstack_;
unsigned nslots_;
mozilla::Maybe<unsigned> thisSlotForDerivedClassConstructor_;
JSScript* script_;
JSFunction* fun_;
jsbytecode* osrPc_;
AnalysisMode analysisMode_;
// Whether a script needs an arguments object is unstable over compilation
// since the arguments optimization could be marked as failed on the main
// thread, so cache a value here and use it throughout for consistency.
bool scriptNeedsArgsObj_;
// Record the state of previous bailouts in order to prevent compiling the
// same function identically the next time.
bool hadOverflowBailout_;
bool mayReadFrameArgsDirectly_;
InlineScriptTree* inlineScriptTree_;
};
} // namespace jit
} // namespace js
#endif /* jit_CompileInfo_h */