Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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/Assertions.h" // MOZ_ASSERT
#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Some
#include <algorithm> // std::max
#include <stdint.h> // uint32_t
#include "jit/CompileWrappers.h" // CompileRuntime
#include "jit/JitFrames.h" // MinJITStackSize
#include "jit/shared/Assembler-shared.h"
#include "js/TypeDecls.h" // jsbytecode
#include "vm/BindingKind.h" // BindingLocation
#include "vm/JSAtomState.h" // JSAtomState
#include "vm/JSFunction.h" // JSFunction
#include "vm/JSScript.h" // JSScript
#include "vm/Opcodes.h" // JSOp
#include "vm/Scope.h" // BindingIter
namespace js {
class ModuleObject;
namespace jit {
class InlineScriptTree;
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->needsArgsObj() ? 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);
}
inline unsigned CountArgSlots(JSScript* script, bool hasFun,
uint32_t funArgCount) {
// Same as the previous function, for use when the JSFunction is not
// available.
return StartArgSlot(script) + (hasFun ? funArgCount + 1 : 0);
}
// Contains information about the compilation source for IR being generated.
class CompileInfo {
public:
CompileInfo(CompileRuntime* runtime, JSScript* script, JSFunction* fun,
jsbytecode* osrPc, bool scriptNeedsArgsObj,
InlineScriptTree* inlineScriptTree)
: script_(script),
fun_(fun),
osrPc_(osrPc),
scriptNeedsArgsObj_(scriptNeedsArgsObj),
hadEagerTruncationBailout_(script->hadEagerTruncationBailout()),
hadSpeculativePhiBailout_(script->hadSpeculativePhiBailout()),
hadLICMInvalidation_(script->hadLICMInvalidation()),
hadReorderingBailout_(script->hadReorderingBailout()),
hadBoundsCheckBailout_(script->failedBoundsCheck()),
hadUnboxFoldingBailout_(script->hadUnboxFoldingBailout()),
mayReadFrameArgsDirectly_(script->mayReadFrameArgsDirectly()),
anyFormalIsForwarded_(script->anyFormalIsForwarded()),
isDerivedClassConstructor_(script->isDerivedClassConstructor()),
inlineScriptTree_(inlineScriptTree),
hasSeenObjectEmulateUndefinedFuseIntact_(
runtime->hasSeenObjectEmulateUndefinedFuseIntact()) {
MOZ_ASSERT_IF(osrPc, JSOp(*osrPc) == JSOp::LoopHead);
// 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_->baseScript()->function();
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_ = std::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());
for (BindingIter bi(script); bi; bi++) {
if (bi.name() != runtime->names().dot_this_) {
continue;
}
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Frame) {
thisSlotForDerivedClassConstructor_ =
mozilla::Some(localSlot(loc.slot()));
break;
}
}
}
// If the script uses an environment in body, the environment chain
// will need to be observable.
needsBodyEnvironmentObject_ = script->needsBodyEnvironment();
funNeedsSomeEnvironmentObject_ =
fun ? fun->needsSomeEnvironmentObject() : false;
}
explicit CompileInfo(unsigned nlocals)
: script_(nullptr),
fun_(nullptr),
osrPc_(nullptr),
scriptNeedsArgsObj_(false),
hadEagerTruncationBailout_(false),
hadSpeculativePhiBailout_(false),
hadLICMInvalidation_(false),
hadReorderingBailout_(false),
hadBoundsCheckBailout_(false),
hadUnboxFoldingBailout_(false),
branchHintingEnabled_(false),
mayReadFrameArgsDirectly_(false),
anyFormalIsForwarded_(false),
inlineScriptTree_(nullptr),
needsBodyEnvironmentObject_(false),
funNeedsSomeEnvironmentObject_(false),
hasSeenObjectEmulateUndefinedFuseIntact_(false) {
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; }
ModuleObject* module() const { return script_->module(); }
jsbytecode* osrPc() const { return osrPc_; }
InlineScriptTree* inlineScriptTree() const { return inlineScriptTree_; }
// It's not safe to access the JSFunction off main thread.
bool hasFunMaybeLazy() const { return fun_; }
ImmGCPtr funMaybeLazy() const { return ImmGCPtr(fun_); }
const char* filename() const { return script_->filename(); }
unsigned lineno() const { return script_->lineno(); }
// 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(needsArgsObj());
return 2;
}
uint32_t thisSlot() const {
MOZ_ASSERT(hasFunMaybeLazy());
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 totalSlots() const {
MOZ_ASSERT(script() && hasFunMaybeLazy());
return nimplicit() + nargs() + nlocals();
}
bool hasMappedArgsObj() const { return script()->hasMappedArgsObj(); }
bool needsArgsObj() const { return scriptNeedsArgsObj_; }
bool argsObjAliasesFormals() const {
return scriptNeedsArgsObj_ && script()->hasMappedArgsObj();
}
bool needsBodyEnvironmentObject() const {
return needsBodyEnvironmentObject_;
}
enum class SlotObservableKind {
// This slot must be preserved because it's observable outside SSA uses.
// It can't be recovered before or during bailout.
ObservableNotRecoverable,
// This slot must be preserved because it's observable, but it can be
// recovered.
ObservableRecoverable,
// This slot is not observable outside SSA uses.
NotObservable,
};
inline SlotObservableKind getSlotObservableKind(uint32_t slot) const {
// Locals and expression stack slots.
if (slot >= firstLocalSlot()) {
// The |this| slot for a derived class constructor is a local slot.
// It 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 SlotObservableKind::ObservableNotRecoverable;
}
return SlotObservableKind::NotObservable;
}
// Formal argument slots.
if (slot >= firstArgSlot()) {
MOZ_ASSERT(hasFunMaybeLazy());
MOZ_ASSERT(slot - firstArgSlot() < nargs());
// Preserve formal arguments if they might be read when creating a rest or
// arguments object. In non-strict scripts, Function.arguments can create
// an arguments object dynamically so we always preserve the arguments.
if (mayReadFrameArgsDirectly_ || !script()->strict()) {
return SlotObservableKind::ObservableRecoverable;
}
return SlotObservableKind::NotObservable;
}
// |this| slot is observable but it can be recovered.
if (hasFunMaybeLazy() && slot == thisSlot()) {
return SlotObservableKind::ObservableRecoverable;
}
// Environment chain slot.
if (slot == environmentChainSlot()) {
// If environments can be added in the body (after the prologue) we need
// to preserve the environment chain slot. It can't be recovered.
if (needsBodyEnvironmentObject()) {
return SlotObservableKind::ObservableNotRecoverable;
}
// If the function may need an arguments object, also preserve the
// environment chain because it may be needed to reconstruct the arguments
// object during bailout.
if (funNeedsSomeEnvironmentObject_ || needsArgsObj()) {
return SlotObservableKind::ObservableRecoverable;
}
return SlotObservableKind::NotObservable;
}
// The arguments object is observable. If it does not escape, it can
// be recovered.
if (needsArgsObj() && slot == argsObjSlot()) {
MOZ_ASSERT(hasFunMaybeLazy());
return SlotObservableKind::ObservableRecoverable;
}
MOZ_ASSERT(slot == returnValueSlot());
return SlotObservableKind::NotObservable;
}
// 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.
inline bool isObservableSlot(uint32_t slot) const {
SlotObservableKind kind = getSlotObservableKind(slot);
return (kind == SlotObservableKind::ObservableNotRecoverable ||
kind == SlotObservableKind::ObservableRecoverable);
}
// 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 {
SlotObservableKind kind = getSlotObservableKind(slot);
return (kind == SlotObservableKind::ObservableRecoverable ||
kind == SlotObservableKind::NotObservable);
}
// Check previous bailout states to prevent doing the same bailout in the
// next compilation.
bool hadEagerTruncationBailout() const { return hadEagerTruncationBailout_; }
bool hadSpeculativePhiBailout() const { return hadSpeculativePhiBailout_; }
bool hadLICMInvalidation() const { return hadLICMInvalidation_; }
bool hadReorderingBailout() const { return hadReorderingBailout_; }
bool hadBoundsCheckBailout() const { return hadBoundsCheckBailout_; }
bool hadUnboxFoldingBailout() const { return hadUnboxFoldingBailout_; }
bool branchHintingEnabled() const {
return compilingWasm() && branchHintingEnabled_;
}
void setBranchHinting(bool value) { branchHintingEnabled_ = value; }
bool mayReadFrameArgsDirectly() const { return mayReadFrameArgsDirectly_; }
bool anyFormalIsForwarded() const { return anyFormalIsForwarded_; }
bool isDerivedClassConstructor() const { return isDerivedClassConstructor_; }
bool hasSeenObjectEmulateUndefinedFuseIntact() const {
return hasSeenObjectEmulateUndefinedFuseIntact_;
}
private:
unsigned nimplicit_;
unsigned nargs_;
unsigned nlocals_;
unsigned nstack_;
unsigned nslots_;
mozilla::Maybe<unsigned> thisSlotForDerivedClassConstructor_;
JSScript* script_;
JSFunction* fun_;
jsbytecode* osrPc_;
bool scriptNeedsArgsObj_;
// Record the state of previous bailouts in order to prevent compiling the
// same function identically the next time.
bool hadEagerTruncationBailout_;
bool hadSpeculativePhiBailout_;
bool hadLICMInvalidation_;
bool hadReorderingBailout_;
bool hadBoundsCheckBailout_;
bool hadUnboxFoldingBailout_;
bool branchHintingEnabled_;
bool mayReadFrameArgsDirectly_;
bool anyFormalIsForwarded_;
bool isDerivedClassConstructor_;
InlineScriptTree* inlineScriptTree_;
// Whether a script needs environments within its body. This informs us
// that the environment chain is not easy to reconstruct.
bool needsBodyEnvironmentObject_;
bool funNeedsSomeEnvironmentObject_;
bool hasSeenObjectEmulateUndefinedFuseIntact_;
};
} // namespace jit
} // namespace js
#endif /* jit_CompileInfo_h */