mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 07:18:39 +09:00
Merge remote-tracking branch 'origin/tracking' into custom
This commit is contained in:
commit
39d69840bf
20 changed files with 3240 additions and 2241 deletions
66
js/src/ds/Nestable.h
Normal file
66
js/src/ds/Nestable.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/* -*- 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 ds_Nestable_h
|
||||
#define ds_Nestable_h
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
// A base class for nestable structures.
|
||||
template <typename Concrete>
|
||||
class MOZ_STACK_CLASS Nestable
|
||||
{
|
||||
Concrete** stack_;
|
||||
Concrete* enclosing_;
|
||||
|
||||
protected:
|
||||
explicit Nestable(Concrete** stack)
|
||||
: stack_(stack),
|
||||
enclosing_(*stack)
|
||||
{
|
||||
*stack_ = static_cast<Concrete*>(this);
|
||||
}
|
||||
|
||||
// These method are protected. Some derived classes, such as ParseContext,
|
||||
// do not expose the ability to walk the stack.
|
||||
Concrete* enclosing() const {
|
||||
return enclosing_;
|
||||
}
|
||||
|
||||
template <typename Predicate /* (Concrete*) -> bool */>
|
||||
static Concrete* findNearest(Concrete* it, Predicate predicate) {
|
||||
while (it && !predicate(it))
|
||||
it = it->enclosing();
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T* findNearest(Concrete* it) {
|
||||
while (it && !it->template is<T>())
|
||||
it = it->enclosing();
|
||||
return it ? &it->template as<T>() : nullptr;
|
||||
}
|
||||
|
||||
template <typename T, typename Predicate /* (T*) -> bool */>
|
||||
static T* findNearest(Concrete* it, Predicate predicate) {
|
||||
while (it && (!it->template is<T>() || !predicate(&it->template as<T>())))
|
||||
it = it->enclosing();
|
||||
return it ? &it->template as<T>() : nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
~Nestable() {
|
||||
MOZ_ASSERT(*stack_ == static_cast<Concrete*>(this));
|
||||
*stack_ = enclosing_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* ds_Nestable_h */
|
||||
84
js/src/frontend/BytecodeControlStructures.cpp
Normal file
84
js/src/frontend/BytecodeControlStructures.cpp
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/* -*- 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 "frontend/BytecodeControlStructures.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "frontend/EmitterScope.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
NestableControl::NestableControl(BytecodeEmitter* bce, StatementKind kind)
|
||||
: Nestable<NestableControl>(&bce->innermostNestableControl),
|
||||
kind_(kind),
|
||||
emitterScope_(bce->innermostEmitterScopeNoCheck())
|
||||
{}
|
||||
|
||||
BreakableControl::BreakableControl(BytecodeEmitter* bce, StatementKind kind)
|
||||
: NestableControl(bce, kind)
|
||||
{
|
||||
MOZ_ASSERT(is<BreakableControl>());
|
||||
}
|
||||
|
||||
bool
|
||||
BreakableControl::patchBreaks(BytecodeEmitter* bce)
|
||||
{
|
||||
return bce->emitJumpTargetAndPatch(breaks);
|
||||
}
|
||||
|
||||
LabelControl::LabelControl(BytecodeEmitter* bce, JSAtom* label, ptrdiff_t startOffset)
|
||||
: BreakableControl(bce, StatementKind::Label),
|
||||
label_(bce->cx, label),
|
||||
startOffset_(startOffset)
|
||||
{}
|
||||
|
||||
LoopControl::LoopControl(BytecodeEmitter* bce, StatementKind loopKind)
|
||||
: BreakableControl(bce, loopKind),
|
||||
tdzCache_(bce),
|
||||
continueTarget({ -1 })
|
||||
{
|
||||
MOZ_ASSERT(is<LoopControl>());
|
||||
|
||||
LoopControl* enclosingLoop = findNearest<LoopControl>(enclosing());
|
||||
|
||||
stackDepth_ = bce->stackDepth;
|
||||
loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1;
|
||||
|
||||
int loopSlots;
|
||||
if (loopKind == StatementKind::Spread || loopKind == StatementKind::ForOfLoop)
|
||||
loopSlots = 3;
|
||||
else if (loopKind == StatementKind::ForInLoop)
|
||||
loopSlots = 2;
|
||||
else
|
||||
loopSlots = 0;
|
||||
|
||||
MOZ_ASSERT(loopSlots <= stackDepth_);
|
||||
|
||||
if (enclosingLoop) {
|
||||
canIonOsr_ = (enclosingLoop->canIonOsr_ &&
|
||||
stackDepth_ == enclosingLoop->stackDepth_ + loopSlots);
|
||||
} else {
|
||||
canIonOsr_ = stackDepth_ == loopSlots;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
LoopControl::patchBreaksAndContinues(BytecodeEmitter* bce)
|
||||
{
|
||||
MOZ_ASSERT(continueTarget.offset != -1);
|
||||
if (!patchBreaks(bce))
|
||||
return false;
|
||||
bce->patchJumpsToTarget(continues, continueTarget);
|
||||
return true;
|
||||
}
|
||||
|
||||
TryFinallyControl::TryFinallyControl(BytecodeEmitter* bce, StatementKind kind)
|
||||
: NestableControl(bce, kind),
|
||||
emittingSubroutine_(false)
|
||||
{
|
||||
MOZ_ASSERT(is<TryFinallyControl>());
|
||||
}
|
||||
175
js/src/frontend/BytecodeControlStructures.h
Normal file
175
js/src/frontend/BytecodeControlStructures.h
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
/* -*- 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 frontend_BytecodeControlStructures_h
|
||||
#define frontend_BytecodeControlStructures_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ds/Nestable.h"
|
||||
#include "frontend/JumpList.h"
|
||||
#include "frontend/SharedContext.h"
|
||||
#include "frontend/TDZCheckCache.h"
|
||||
#include "gc/Rooting.h"
|
||||
#include "vm/String.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
class EmitterScope;
|
||||
|
||||
class NestableControl : public Nestable<NestableControl>
|
||||
{
|
||||
StatementKind kind_;
|
||||
|
||||
// The innermost scope when this was pushed.
|
||||
EmitterScope* emitterScope_;
|
||||
|
||||
protected:
|
||||
NestableControl(BytecodeEmitter* bce, StatementKind kind);
|
||||
|
||||
public:
|
||||
using Nestable<NestableControl>::enclosing;
|
||||
using Nestable<NestableControl>::findNearest;
|
||||
|
||||
StatementKind kind() const {
|
||||
return kind_;
|
||||
}
|
||||
|
||||
EmitterScope* emitterScope() const {
|
||||
return emitterScope_;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool is() const;
|
||||
|
||||
template <typename T>
|
||||
T& as() {
|
||||
MOZ_ASSERT(this->is<T>());
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class BreakableControl : public NestableControl
|
||||
{
|
||||
public:
|
||||
// Offset of the last break.
|
||||
JumpList breaks;
|
||||
|
||||
BreakableControl(BytecodeEmitter* bce, StatementKind kind);
|
||||
|
||||
MOZ_MUST_USE bool patchBreaks(BytecodeEmitter* bce);
|
||||
};
|
||||
template <>
|
||||
inline bool
|
||||
NestableControl::is<BreakableControl>() const
|
||||
{
|
||||
return StatementKindIsUnlabeledBreakTarget(kind_) || kind_ == StatementKind::Label;
|
||||
}
|
||||
|
||||
class LabelControl : public BreakableControl
|
||||
{
|
||||
RootedAtom label_;
|
||||
|
||||
// The code offset when this was pushed. Used for effectfulness checking.
|
||||
ptrdiff_t startOffset_;
|
||||
|
||||
public:
|
||||
LabelControl(BytecodeEmitter* bce, JSAtom* label, ptrdiff_t startOffset);
|
||||
|
||||
HandleAtom label() const {
|
||||
return label_;
|
||||
}
|
||||
|
||||
ptrdiff_t startOffset() const {
|
||||
return startOffset_;
|
||||
}
|
||||
};
|
||||
template <>
|
||||
inline bool
|
||||
NestableControl::is<LabelControl>() const
|
||||
{
|
||||
return kind_ == StatementKind::Label;
|
||||
}
|
||||
|
||||
class LoopControl : public BreakableControl
|
||||
{
|
||||
// Loops' children are emitted in dominance order, so they can always
|
||||
// have a TDZCheckCache.
|
||||
TDZCheckCache tdzCache_;
|
||||
|
||||
// Stack depth when this loop was pushed on the control stack.
|
||||
int32_t stackDepth_;
|
||||
|
||||
// The loop nesting depth. Used as a hint to Ion.
|
||||
uint32_t loopDepth_;
|
||||
|
||||
// Can we OSR into Ion from here? True unless there is non-loop state on the stack.
|
||||
bool canIonOsr_;
|
||||
|
||||
public:
|
||||
// The target of continue statement jumps, e.g., the update portion of a
|
||||
// for(;;) loop.
|
||||
JumpTarget continueTarget;
|
||||
|
||||
// Offset of the last continue in the loop.
|
||||
JumpList continues;
|
||||
|
||||
LoopControl(BytecodeEmitter* bce, StatementKind loopKind);
|
||||
|
||||
uint32_t loopDepth() const {
|
||||
return loopDepth_;
|
||||
}
|
||||
|
||||
bool canIonOsr() const {
|
||||
return canIonOsr_;
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool patchBreaksAndContinues(BytecodeEmitter* bce);
|
||||
};
|
||||
template <>
|
||||
inline bool
|
||||
NestableControl::is<LoopControl>() const
|
||||
{
|
||||
return StatementKindIsLoop(kind_);
|
||||
}
|
||||
|
||||
class TryFinallyControl : public NestableControl
|
||||
{
|
||||
bool emittingSubroutine_;
|
||||
|
||||
public:
|
||||
// The subroutine when emitting a finally block.
|
||||
JumpList gosubs;
|
||||
|
||||
// Offset of the last catch guard, if any.
|
||||
JumpList guardJump;
|
||||
|
||||
TryFinallyControl(BytecodeEmitter* bce, StatementKind kind);
|
||||
|
||||
void setEmittingSubroutine() {
|
||||
emittingSubroutine_ = true;
|
||||
}
|
||||
|
||||
bool emittingSubroutine() const {
|
||||
return emittingSubroutine_;
|
||||
}
|
||||
};
|
||||
template <>
|
||||
inline bool
|
||||
NestableControl::is<TryFinallyControl>() const
|
||||
{
|
||||
return kind_ == StatementKind::Try || kind_ == StatementKind::Finally;
|
||||
}
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_BytecodeControlStructures_h */
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -122,14 +122,13 @@ typedef Vector<jssrcnote, 64> SrcNotesVector;
|
|||
|
||||
class CallOrNewEmitter;
|
||||
class ElemOpEmitter;
|
||||
class EmitterScope;
|
||||
class NestableControl;
|
||||
class PropOpEmitter;
|
||||
class TDZCheckCache;
|
||||
|
||||
struct MOZ_STACK_CLASS BytecodeEmitter
|
||||
{
|
||||
class NestableControl;
|
||||
class EmitterScope;
|
||||
|
||||
SharedContext* const sc; /* context shared between parsing and bytecode generation */
|
||||
|
||||
ExclusiveContext* const cx;
|
||||
|
|
@ -257,9 +256,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
|||
|
||||
MOZ_MUST_USE bool init();
|
||||
|
||||
template <typename Predicate /* (NestableControl*) -> bool */>
|
||||
NestableControl* findInnermostNestableControl(Predicate predicate) const;
|
||||
|
||||
template <typename T>
|
||||
T* findInnermostNestableControl() const;
|
||||
|
||||
|
|
@ -357,7 +353,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
|||
}
|
||||
|
||||
bool reportError(ParseNode* pn, unsigned errorNumber, ...);
|
||||
bool reportError(const mozilla::Maybe<uint32_t>& maybeOffset, unsigned errorNumber, ...);
|
||||
bool reportExtraWarning(ParseNode* pn, unsigned errorNumber, ...);
|
||||
bool reportExtraWarning(const mozilla::Maybe<uint32_t>& maybeOffset, unsigned errorNumber, ...);
|
||||
bool reportStrictModeError(ParseNode* pn, unsigned errorNumber, ...);
|
||||
|
||||
// If pn contains a useful expression, return true with *answer set to true.
|
||||
|
|
|
|||
1092
js/src/frontend/EmitterScope.cpp
Normal file
1092
js/src/frontend/EmitterScope.cpp
Normal file
File diff suppressed because it is too large
Load diff
155
js/src/frontend/EmitterScope.h
Normal file
155
js/src/frontend/EmitterScope.h
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/* -*- 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 frontend_EmitterScope_h
|
||||
#define frontend_EmitterScope_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ds/Nestable.h"
|
||||
#include "frontend/NameAnalysisTypes.h"
|
||||
#include "frontend/NameCollections.h"
|
||||
#include "frontend/SharedContext.h"
|
||||
#include "jstypes.h"
|
||||
#include "vm/Scope.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
// A scope that introduces bindings.
|
||||
class EmitterScope : public Nestable<EmitterScope>
|
||||
{
|
||||
// The cache of bound names that may be looked up in the
|
||||
// scope. Initially populated as the set of names this scope binds. As
|
||||
// names are looked up in enclosing scopes, they are cached on the
|
||||
// current scope.
|
||||
PooledMapPtr<NameLocationMap> nameCache_;
|
||||
|
||||
// If this scope's cache does not include free names, such as the
|
||||
// global scope, the NameLocation to return.
|
||||
mozilla::Maybe<NameLocation> fallbackFreeNameLocation_;
|
||||
|
||||
// True if there is a corresponding EnvironmentObject on the environment
|
||||
// chain, false if all bindings are stored in frame slots on the stack.
|
||||
bool hasEnvironment_;
|
||||
|
||||
// The number of enclosing environments. Used for error checking.
|
||||
uint8_t environmentChainLength_;
|
||||
|
||||
// The next usable slot on the frame for not-closed over bindings.
|
||||
//
|
||||
// The initial frame slot when assigning slots to bindings is the
|
||||
// enclosing scope's nextFrameSlot. For the first scope in a frame,
|
||||
// the initial frame slot is 0.
|
||||
uint32_t nextFrameSlot_;
|
||||
|
||||
// The index in the BytecodeEmitter's interned scope vector, otherwise
|
||||
// ScopeNote::NoScopeIndex.
|
||||
uint32_t scopeIndex_;
|
||||
|
||||
// If kind is Lexical, Catch, or With, the index in the BytecodeEmitter's
|
||||
// block scope note list. Otherwise ScopeNote::NoScopeNote.
|
||||
uint32_t noteIndex_;
|
||||
|
||||
MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce);
|
||||
|
||||
template <typename BindingIter>
|
||||
MOZ_MUST_USE bool checkSlotLimits(BytecodeEmitter* bce, const BindingIter& bi);
|
||||
|
||||
MOZ_MUST_USE bool checkEnvironmentChainLength(BytecodeEmitter* bce);
|
||||
|
||||
void updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi);
|
||||
|
||||
MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, JSAtom* name, NameLocation loc);
|
||||
|
||||
mozilla::Maybe<NameLocation> lookupInCache(BytecodeEmitter* bce, JSAtom* name);
|
||||
|
||||
EmitterScope* enclosing(BytecodeEmitter** bce) const;
|
||||
|
||||
Scope* enclosingScope(BytecodeEmitter* bce) const;
|
||||
|
||||
static bool nameCanBeFree(BytecodeEmitter* bce, JSAtom* name);
|
||||
|
||||
static NameLocation searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops);
|
||||
NameLocation searchAndCache(BytecodeEmitter* bce, JSAtom* name);
|
||||
|
||||
template <typename ScopeCreator>
|
||||
MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope);
|
||||
template <typename ScopeCreator>
|
||||
MOZ_MUST_USE bool internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope);
|
||||
MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce);
|
||||
|
||||
MOZ_MUST_USE bool deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart,
|
||||
uint32_t slotEnd);
|
||||
|
||||
public:
|
||||
explicit EmitterScope(BytecodeEmitter* bce);
|
||||
|
||||
void dump(BytecodeEmitter* bce);
|
||||
|
||||
MOZ_MUST_USE bool enterLexical(BytecodeEmitter* bce, ScopeKind kind,
|
||||
Handle<LexicalScope::Data*> bindings);
|
||||
MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox);
|
||||
MOZ_MUST_USE bool enterComprehensionFor(BytecodeEmitter* bce,
|
||||
Handle<LexicalScope::Data*> bindings);
|
||||
MOZ_MUST_USE bool enterFunction(BytecodeEmitter* bce, FunctionBox* funbox);
|
||||
MOZ_MUST_USE bool enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox);
|
||||
MOZ_MUST_USE bool enterParameterExpressionVar(BytecodeEmitter* bce);
|
||||
MOZ_MUST_USE bool enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc);
|
||||
MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc);
|
||||
MOZ_MUST_USE bool enterModule(BytecodeEmitter* module, ModuleSharedContext* modulesc);
|
||||
MOZ_MUST_USE bool enterWith(BytecodeEmitter* bce);
|
||||
MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce);
|
||||
|
||||
MOZ_MUST_USE bool leave(BytecodeEmitter* bce, bool nonLocal = false);
|
||||
|
||||
uint32_t index() const {
|
||||
MOZ_ASSERT(scopeIndex_ != ScopeNote::NoScopeIndex, "Did you forget to intern a Scope?");
|
||||
return scopeIndex_;
|
||||
}
|
||||
|
||||
uint32_t noteIndex() const {
|
||||
return noteIndex_;
|
||||
}
|
||||
|
||||
Scope* scope(const BytecodeEmitter* bce) const;
|
||||
|
||||
bool hasEnvironment() const {
|
||||
return hasEnvironment_;
|
||||
}
|
||||
|
||||
// The first frame slot used.
|
||||
uint32_t frameSlotStart() const {
|
||||
if (EmitterScope* inFrame = enclosingInFrame())
|
||||
return inFrame->nextFrameSlot_;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The last frame slot used + 1.
|
||||
uint32_t frameSlotEnd() const {
|
||||
return nextFrameSlot_;
|
||||
}
|
||||
|
||||
uint32_t numFrameSlots() const {
|
||||
return frameSlotEnd() - frameSlotStart();
|
||||
}
|
||||
|
||||
EmitterScope* enclosingInFrame() const {
|
||||
return Nestable<EmitterScope>::enclosing();
|
||||
}
|
||||
|
||||
NameLocation lookup(BytecodeEmitter* bce, JSAtom* name);
|
||||
|
||||
mozilla::Maybe<NameLocation> locationBoundInScope(JSAtom* name, EmitterScope* target);
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_EmitterScope_h */
|
||||
167
js/src/frontend/ForOfLoopControl.cpp
Normal file
167
js/src/frontend/ForOfLoopControl.cpp
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
/* -*- 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 "frontend/ForOfLoopControl.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "frontend/EmitterScope.h"
|
||||
#include "frontend/IfEmitter.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
ForOfLoopControl::ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted,
|
||||
IteratorKind iterKind)
|
||||
: LoopControl(bce, StatementKind::ForOfLoop),
|
||||
iterDepth_(iterDepth),
|
||||
numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX),
|
||||
allowSelfHosted_(allowSelfHosted),
|
||||
iterKind_(iterKind)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
ForOfLoopControl::emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce)
|
||||
{
|
||||
tryCatch_.emplace(bce, TryEmitter::TryCatch, TryEmitter::DontUseRetVal,
|
||||
TryEmitter::DontUseControl);
|
||||
|
||||
if (!tryCatch_->emitTry())
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX);
|
||||
numYieldsAtBeginCodeNeedingIterClose_ = bce->yieldAndAwaitOffsetList.numYields;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce)
|
||||
{
|
||||
if (!tryCatch_->emitCatch()) // ITER ...
|
||||
return false;
|
||||
|
||||
if (!bce->emit1(JSOP_EXCEPTION)) // ITER ... EXCEPTION
|
||||
return false;
|
||||
unsigned slotFromTop = bce->stackDepth - iterDepth_;
|
||||
if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER
|
||||
return false;
|
||||
|
||||
// If ITER is undefined, it means the exception is thrown by
|
||||
// IteratorClose for non-local jump, and we should't perform
|
||||
// IteratorClose again here.
|
||||
if (!bce->emit1(JSOP_UNDEFINED)) // ITER ... EXCEPTION ITER UNDEF
|
||||
return false;
|
||||
if (!bce->emit1(JSOP_STRICTNE)) // ITER ... EXCEPTION NE
|
||||
return false;
|
||||
|
||||
InternalIfEmitter ifIteratorIsNotClosed(bce);
|
||||
if (!ifIteratorIsNotClosed.emitThen()) // ITER ... EXCEPTION
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(slotFromTop == unsigned(bce->stackDepth - iterDepth_));
|
||||
if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER
|
||||
return false;
|
||||
if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Throw))
|
||||
return false; // ITER ... EXCEPTION
|
||||
|
||||
if (!ifIteratorIsNotClosed.emitEnd()) // ITER ... EXCEPTION
|
||||
return false;
|
||||
|
||||
if (!bce->emit1(JSOP_THROW)) // ITER ...
|
||||
return false;
|
||||
|
||||
// If any yields were emitted, then this for-of loop is inside a star
|
||||
// generator and must handle the case of Generator.return. Like in
|
||||
// yield*, it is handled with a finally block.
|
||||
uint32_t numYieldsEmitted = bce->yieldAndAwaitOffsetList.numYields;
|
||||
if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) {
|
||||
if (!tryCatch_->emitFinally())
|
||||
return false;
|
||||
|
||||
InternalIfEmitter ifGeneratorClosing(bce);
|
||||
if (!bce->emit1(JSOP_ISGENCLOSING)) // ITER ... FTYPE FVALUE CLOSING
|
||||
return false;
|
||||
if (!ifGeneratorClosing.emitThen()) // ITER ... FTYPE FVALUE
|
||||
return false;
|
||||
if (!bce->emitDupAt(slotFromTop + 1)) // ITER ... FTYPE FVALUE ITER
|
||||
return false;
|
||||
if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Normal))
|
||||
return false; // ITER ... FTYPE FVALUE
|
||||
if (!ifGeneratorClosing.emitEnd()) // ITER ... FTYPE FVALUE
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tryCatch_->emitEnd())
|
||||
return false;
|
||||
|
||||
tryCatch_.reset();
|
||||
numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ForOfLoopControl::emitIteratorCloseInInnermostScope(BytecodeEmitter* bce,
|
||||
CompletionKind completionKind /* = CompletionKind::Normal */)
|
||||
{
|
||||
return emitIteratorCloseInScope(bce, *bce->innermostEmitterScope(), completionKind);
|
||||
}
|
||||
|
||||
bool
|
||||
ForOfLoopControl::emitIteratorCloseInScope(BytecodeEmitter* bce,
|
||||
EmitterScope& currentScope,
|
||||
CompletionKind completionKind /* = CompletionKind::Normal */)
|
||||
{
|
||||
ptrdiff_t start = bce->offset();
|
||||
if (!bce->emitIteratorCloseInScope(currentScope, iterKind_, completionKind,
|
||||
allowSelfHosted_))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ptrdiff_t end = bce->offset();
|
||||
return bce->tryNoteList.append(JSTRY_FOR_OF_ITERCLOSE, 0, start, end);
|
||||
}
|
||||
|
||||
bool
|
||||
ForOfLoopControl::emitPrepareForNonLocalJumpFromScope(BytecodeEmitter* bce,
|
||||
EmitterScope& currentScope,
|
||||
bool isTarget)
|
||||
{
|
||||
// Pop unnecessary values from the stack. Effectively this means
|
||||
// leaving try-catch block. However, the performing IteratorClose can
|
||||
// reach the depth for try-catch, and effectively re-enter the
|
||||
// try-catch block.
|
||||
if (!bce->emit1(JSOP_POP)) // ITER RESULT
|
||||
return false;
|
||||
if (!bce->emit1(JSOP_POP)) // ITER
|
||||
return false;
|
||||
|
||||
// Clear ITER slot on the stack to tell catch block to avoid performing
|
||||
// IteratorClose again.
|
||||
if (!bce->emit1(JSOP_UNDEFINED)) // ITER UNDEF
|
||||
return false;
|
||||
if (!bce->emit1(JSOP_SWAP)) // UNDEF ITER
|
||||
return false;
|
||||
|
||||
if (!emitIteratorCloseInScope(bce, currentScope, CompletionKind::Normal)) // UNDEF
|
||||
return false;
|
||||
|
||||
if (isTarget) {
|
||||
// At the level of the target block, there's bytecode after the
|
||||
// loop that will pop the iterator and the value, so push
|
||||
// undefineds to balance the stack.
|
||||
if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF
|
||||
return false;
|
||||
if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF UNDEF
|
||||
return false;
|
||||
} else {
|
||||
if (!bce->emit1(JSOP_POP)) //
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
99
js/src/frontend/ForOfLoopControl.h
Normal file
99
js/src/frontend/ForOfLoopControl.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/* -*- 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 frontend_ForOfLoopControl_h
|
||||
#define frontend_ForOfLoopControl_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "jsapi.h"
|
||||
|
||||
#include "frontend/BytecodeControlStructures.h"
|
||||
#include "frontend/TryEmitter.h"
|
||||
#include "jsiter.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
class EmitterScope;
|
||||
|
||||
class ForOfLoopControl : public LoopControl
|
||||
{
|
||||
// The stack depth of the iterator.
|
||||
int32_t iterDepth_;
|
||||
|
||||
// for-of loops, when throwing from non-iterator code (i.e. from the body
|
||||
// or from evaluating the LHS of the loop condition), need to call
|
||||
// IteratorClose. This is done by enclosing non-iterator code with
|
||||
// try-catch and call IteratorClose in `catch` block.
|
||||
// If IteratorClose itself throws, we must not re-call IteratorClose. Since
|
||||
// non-local jumps like break and return call IteratorClose, whenever a
|
||||
// non-local jump is emitted, we must tell catch block not to perform
|
||||
// IteratorClose.
|
||||
//
|
||||
// for (x of y) {
|
||||
// // Operations for iterator (IteratorNext etc) are outside of
|
||||
// // try-block.
|
||||
// try {
|
||||
// ...
|
||||
// if (...) {
|
||||
// // Before non-local jump, clear iterator on the stack to tell
|
||||
// // catch block not to perform IteratorClose.
|
||||
// tmpIterator = iterator;
|
||||
// iterator = undefined;
|
||||
// IteratorClose(tmpIterator, { break });
|
||||
// break;
|
||||
// }
|
||||
// ...
|
||||
// } catch (e) {
|
||||
// // Just throw again when iterator is cleared by non-local jump.
|
||||
// if (iterator === undefined)
|
||||
// throw e;
|
||||
// IteratorClose(iterator, { throw, e });
|
||||
// }
|
||||
// }
|
||||
mozilla::Maybe<TryEmitter> tryCatch_;
|
||||
|
||||
// Used to track if any yields were emitted between calls to to
|
||||
// emitBeginCodeNeedingIteratorClose and emitEndCodeNeedingIteratorClose.
|
||||
uint32_t numYieldsAtBeginCodeNeedingIterClose_;
|
||||
|
||||
bool allowSelfHosted_;
|
||||
|
||||
IteratorKind iterKind_;
|
||||
|
||||
public:
|
||||
ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted,
|
||||
IteratorKind iterKind);
|
||||
|
||||
MOZ_MUST_USE bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce);
|
||||
MOZ_MUST_USE bool emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce);
|
||||
|
||||
MOZ_MUST_USE bool emitIteratorCloseInInnermostScope(BytecodeEmitter* bce,
|
||||
CompletionKind completionKind = CompletionKind::Normal);
|
||||
MOZ_MUST_USE bool emitIteratorCloseInScope(BytecodeEmitter* bce,
|
||||
EmitterScope& currentScope,
|
||||
CompletionKind completionKind = CompletionKind::Normal);
|
||||
|
||||
MOZ_MUST_USE bool emitPrepareForNonLocalJumpFromScope(BytecodeEmitter* bce,
|
||||
EmitterScope& currentScope,
|
||||
bool isTarget);
|
||||
};
|
||||
template <>
|
||||
inline bool
|
||||
NestableControl::is<ForOfLoopControl>() const
|
||||
{
|
||||
return kind_ == StatementKind::ForOfLoop;
|
||||
}
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_ForOfLoopControl_h */
|
||||
|
|
@ -254,16 +254,12 @@ IsTypeofKind(ParseNodeKind kind)
|
|||
* or (if the push was optimized away) empty
|
||||
* PNK_STATEMENTLIST.
|
||||
* PNK_SWITCH binary pn_left: discriminant
|
||||
* pn_right: list of PNK_CASE nodes, with at most one
|
||||
* default node, or if there are let bindings
|
||||
* in the top level of the switch body's cases, a
|
||||
* PNK_LEXICALSCOPE node that contains the list of
|
||||
* PNK_CASE nodes.
|
||||
* pn_right: PNK_LEXICALSCOPE node that contains the list
|
||||
* of PNK_CASE nodes, with at most one default node.
|
||||
* PNK_CASE binary pn_left: case-expression if CaseClause, or
|
||||
* null if DefaultClause
|
||||
* pn_right: PNK_STATEMENTLIST node for this case's
|
||||
* statements
|
||||
* pn_u.binary.offset: scratch space for the emitter
|
||||
* PNK_WHILE binary pn_left: cond, pn_right: body
|
||||
* PNK_DOWHILE binary pn_left: body, pn_right: cond
|
||||
* PNK_FOR binary pn_left: either PNK_FORIN (for-in statement),
|
||||
|
|
@ -587,7 +583,6 @@ class ParseNode
|
|||
union {
|
||||
unsigned iflags; /* JSITER_* flags for PNK_{COMPREHENSION,}FOR node */
|
||||
bool isStatic; /* only for PNK_CLASSMETHOD */
|
||||
uint32_t offset; /* for the emitter's use on PNK_CASE nodes */
|
||||
};
|
||||
} binary;
|
||||
struct { /* one kid if unary */
|
||||
|
|
@ -1069,10 +1064,6 @@ class CaseClause : public BinaryNode
|
|||
// The next CaseClause in the same switch statement.
|
||||
CaseClause* next() const { return pn_next ? &pn_next->as<CaseClause>() : nullptr; }
|
||||
|
||||
// Scratch space used by the emitter.
|
||||
uint32_t offset() const { return pn_u.binary.offset; }
|
||||
void setOffset(uint32_t u) { pn_u.binary.offset = u; }
|
||||
|
||||
static bool test(const ParseNode& node) {
|
||||
bool match = node.isKind(PNK_CASE);
|
||||
MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "jsiter.h"
|
||||
#include "jspubtd.h"
|
||||
|
||||
#include "ds/Nestable.h"
|
||||
#include "frontend/BytecodeCompiler.h"
|
||||
#include "frontend/FullParseHandler.h"
|
||||
#include "frontend/NameAnalysisTypes.h"
|
||||
|
|
|
|||
|
|
@ -60,56 +60,6 @@ StatementKindIsUnlabeledBreakTarget(StatementKind kind)
|
|||
return StatementKindIsLoop(kind) || kind == StatementKind::Switch;
|
||||
}
|
||||
|
||||
// A base class for nestable structures in the frontend, such as statements
|
||||
// and scopes.
|
||||
template <typename Concrete>
|
||||
class MOZ_STACK_CLASS Nestable
|
||||
{
|
||||
Concrete** stack_;
|
||||
Concrete* enclosing_;
|
||||
|
||||
protected:
|
||||
explicit Nestable(Concrete** stack)
|
||||
: stack_(stack),
|
||||
enclosing_(*stack)
|
||||
{
|
||||
*stack_ = static_cast<Concrete*>(this);
|
||||
}
|
||||
|
||||
// These method are protected. Some derived classes, such as ParseContext,
|
||||
// do not expose the ability to walk the stack.
|
||||
Concrete* enclosing() const {
|
||||
return enclosing_;
|
||||
}
|
||||
|
||||
template <typename Predicate /* (Concrete*) -> bool */>
|
||||
static Concrete* findNearest(Concrete* it, Predicate predicate) {
|
||||
while (it && !predicate(it))
|
||||
it = it->enclosing();
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T* findNearest(Concrete* it) {
|
||||
while (it && !it->template is<T>())
|
||||
it = it->enclosing();
|
||||
return it ? &it->template as<T>() : nullptr;
|
||||
}
|
||||
|
||||
template <typename T, typename Predicate /* (T*) -> bool */>
|
||||
static T* findNearest(Concrete* it, Predicate predicate) {
|
||||
while (it && (!it->template is<T>() || !predicate(&it->template as<T>())))
|
||||
it = it->enclosing();
|
||||
return it ? &it->template as<T>() : nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
~Nestable() {
|
||||
MOZ_ASSERT(*stack_ == static_cast<Concrete*>(this));
|
||||
*stack_ = enclosing_;
|
||||
}
|
||||
};
|
||||
|
||||
// These flags apply to both global and function contexts.
|
||||
class AnyContextFlags
|
||||
{
|
||||
|
|
|
|||
424
js/src/frontend/SwitchEmitter.cpp
Normal file
424
js/src/frontend/SwitchEmitter.cpp
Normal file
|
|
@ -0,0 +1,424 @@
|
|||
/* -*- 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 "frontend/SwitchEmitter.h"
|
||||
|
||||
#include "jsutil.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "frontend/SharedContext.h"
|
||||
#include "frontend/SourceNotes.h"
|
||||
#include "vm/Opcodes.h"
|
||||
#include "vm/Runtime.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
using mozilla::Maybe;
|
||||
|
||||
bool
|
||||
SwitchEmitter::TableGenerator::addNumber(int32_t caseValue)
|
||||
{
|
||||
if (isInvalid())
|
||||
return true;
|
||||
|
||||
if (unsigned(caseValue + int(JS_BIT(15))) >= unsigned(JS_BIT(16))) {
|
||||
setInvalid();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (intmap_.isNothing())
|
||||
intmap_.emplace();
|
||||
|
||||
low_ = std::min(low_, caseValue);
|
||||
high_ = std::max(high_, caseValue);
|
||||
|
||||
// Check for duplicates, which require a JSOP_CONDSWITCH.
|
||||
// We bias caseValue by 65536 if it's negative, and hope that's a rare case
|
||||
// (because it requires a malloc'd bitmap).
|
||||
if (caseValue < 0)
|
||||
caseValue += JS_BIT(16);
|
||||
if (caseValue >= intmapBitLength_) {
|
||||
size_t newLength = NumWordsForBitArrayOfLength(caseValue + 1);
|
||||
if (!intmap_->resize(newLength)) {
|
||||
ReportOutOfMemory(bce_->cx);
|
||||
return false;
|
||||
}
|
||||
intmapBitLength_ = newLength * BitArrayElementBits;
|
||||
}
|
||||
if (IsBitArrayElementSet(intmap_->begin(), intmap_->length(), caseValue)) {
|
||||
// Duplicate entry is not supported in table switch.
|
||||
setInvalid();
|
||||
return true;
|
||||
}
|
||||
SetBitArrayElement(intmap_->begin(), intmap_->length(), caseValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
SwitchEmitter::TableGenerator::finish(uint32_t caseCount)
|
||||
{
|
||||
intmap_.reset();
|
||||
|
||||
#ifdef DEBUG
|
||||
finished_ = true;
|
||||
#endif
|
||||
|
||||
if (isInvalid())
|
||||
return;
|
||||
|
||||
if (caseCount == 0) {
|
||||
low_ = 0;
|
||||
high_ = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute table length and select condswitch instead if overlarge
|
||||
// or more than half-sparse.
|
||||
tableLength_ = uint32_t(high_ - low_ + 1);
|
||||
if (tableLength_ >= JS_BIT(16) || tableLength_ > 2 * caseCount)
|
||||
setInvalid();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
SwitchEmitter::TableGenerator::toCaseIndex(int32_t caseValue) const
|
||||
{
|
||||
MOZ_ASSERT(finished_);
|
||||
MOZ_ASSERT(isValid());
|
||||
uint32_t caseIndex = uint32_t(caseValue - low_);
|
||||
MOZ_ASSERT(caseIndex < tableLength_);
|
||||
return caseIndex;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
SwitchEmitter::TableGenerator::tableLength() const
|
||||
{
|
||||
MOZ_ASSERT(finished_);
|
||||
MOZ_ASSERT(isValid());
|
||||
return tableLength_;
|
||||
}
|
||||
|
||||
SwitchEmitter::SwitchEmitter(BytecodeEmitter* bce)
|
||||
: bce_(bce)
|
||||
{}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitDiscriminant(const Maybe<uint32_t>& switchPos)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Start);
|
||||
switchPos_ = switchPos;
|
||||
|
||||
if (switchPos_) {
|
||||
// Ensure that the column of the switch statement is set properly.
|
||||
if (!bce_->updateSourceCoordNotes(*switchPos_))
|
||||
return false;
|
||||
}
|
||||
|
||||
state_ = State::Discriminant;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitLexical(Handle<LexicalScope::Data*> bindings)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Discriminant);
|
||||
MOZ_ASSERT(bindings);
|
||||
|
||||
tdzCacheLexical_.emplace(bce_);
|
||||
emitterScope_.emplace(bce_);
|
||||
if (!emitterScope_->enterLexical(bce_, ScopeKind::Lexical, bindings))
|
||||
return false;
|
||||
|
||||
state_ = State::Lexical;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::validateCaseCount(uint32_t caseCount)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Discriminant || state_ == State::Lexical);
|
||||
if (caseCount > JS_BIT(16)) {
|
||||
bce_->reportError(switchPos_, JSMSG_TOO_MANY_CASES);
|
||||
return false;
|
||||
}
|
||||
caseCount_ = caseCount;
|
||||
|
||||
state_ = State::CaseCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCond()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::CaseCount);
|
||||
|
||||
kind_ = Kind::Cond;
|
||||
|
||||
// After entering the scope if necessary, push the switch control.
|
||||
controlInfo_.emplace(bce_, StatementKind::Switch);
|
||||
top_ = bce_->offset();
|
||||
|
||||
if (!caseOffsets_.resize(caseCount_)) {
|
||||
ReportOutOfMemory(bce_->cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The note has two offsets: first tells total switch code length;
|
||||
// second tells offset to first JSOP_CASE.
|
||||
if (!bce_->newSrcNote3(SRC_CONDSWITCH, 0, 0, ¬eIndex_))
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(top_ == bce_->offset());
|
||||
if (!bce_->emitN(JSOP_CONDSWITCH, 0))
|
||||
return false;
|
||||
|
||||
tdzCacheCaseAndBody_.emplace(bce_);
|
||||
|
||||
state_ = State::Cond;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitTable(const TableGenerator& tableGen)
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::CaseCount);
|
||||
kind_ = Kind::Table;
|
||||
|
||||
// After entering the scope if necessary, push the switch control.
|
||||
controlInfo_.emplace(bce_, StatementKind::Switch);
|
||||
top_ = bce_->offset();
|
||||
|
||||
// The note has one offset that tells total switch code length.
|
||||
|
||||
// 3 offsets (len, low, high) before the table, 1 per entry.
|
||||
size_t switchSize = size_t(JUMP_OFFSET_LEN * (3 + tableGen.tableLength()));
|
||||
if (!bce_->newSrcNote2(SRC_TABLESWITCH, 0, ¬eIndex_))
|
||||
return false;
|
||||
|
||||
if (!caseOffsets_.resize(tableGen.tableLength())) {
|
||||
ReportOutOfMemory(bce_->cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(top_ == bce_->offset());
|
||||
if (!bce_->emitN(JSOP_TABLESWITCH, switchSize))
|
||||
return false;
|
||||
|
||||
// Skip default offset.
|
||||
jsbytecode* pc = bce_->code(top_ + JUMP_OFFSET_LEN);
|
||||
|
||||
// Fill in switch bounds, which we know fit in 16-bit offsets.
|
||||
SET_JUMP_OFFSET(pc, tableGen.low());
|
||||
SET_JUMP_OFFSET(pc + JUMP_OFFSET_LEN, tableGen.high());
|
||||
|
||||
state_ = State::Table;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCaseOrDefaultJump(uint32_t caseIndex, bool isDefault)
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Cond);
|
||||
|
||||
if (state_ == State::Case) {
|
||||
// Link the last JSOP_CASE's SRC_NEXTCASE to current JSOP_CASE or
|
||||
// JSOP_DEFAULT for the benefit of IonBuilder.
|
||||
if (!bce_->setSrcNoteOffset(caseNoteIndex_, 0, bce_->offset() - lastCaseOffset_))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isDefault) {
|
||||
if (!bce_->emitJump(JSOP_DEFAULT, &condSwitchDefaultOffset_))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!bce_->newSrcNote2(SRC_NEXTCASE, 0, &caseNoteIndex_))
|
||||
return false;
|
||||
|
||||
JumpList caseJump;
|
||||
if (!bce_->emitJump(JSOP_CASE, &caseJump))
|
||||
return false;
|
||||
caseOffsets_[caseIndex] = caseJump.offset;
|
||||
lastCaseOffset_ = caseJump.offset;
|
||||
|
||||
if (state_ == State::Cond) {
|
||||
// Switch note's second offset is to first JSOP_CASE.
|
||||
unsigned noteCount = bce_->notes().length();
|
||||
if (!bce_->setSrcNoteOffset(noteIndex_, 1, lastCaseOffset_ - top_))
|
||||
return false;
|
||||
unsigned noteCountDelta = bce_->notes().length() - noteCount;
|
||||
if (noteCountDelta != 0)
|
||||
caseNoteIndex_ += noteCountDelta;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCaseJump()
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Cond);
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Case);
|
||||
if (!emitCaseOrDefaultJump(caseIndex_, false))
|
||||
return false;
|
||||
caseIndex_++;
|
||||
|
||||
state_ = State::Case;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitImplicitDefault()
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Cond);
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Case);
|
||||
if (!emitCaseOrDefaultJump(0, true))
|
||||
return false;
|
||||
|
||||
caseIndex_ = 0;
|
||||
|
||||
// No internal state after emitting default jump.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCaseBody()
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Cond);
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Case ||
|
||||
state_ == State::CaseBody || state_ == State::DefaultBody);
|
||||
|
||||
tdzCacheCaseAndBody_.reset();
|
||||
|
||||
if (state_ == State::Cond || state_ == State::Case) {
|
||||
// For cond switch, JSOP_DEFAULT is always emitted.
|
||||
if (!emitImplicitDefault())
|
||||
return false;
|
||||
}
|
||||
|
||||
JumpList caseJump;
|
||||
caseJump.offset = caseOffsets_[caseIndex_];
|
||||
if (!bce_->emitJumpTargetAndPatch(caseJump))
|
||||
return false;
|
||||
|
||||
JumpTarget here;
|
||||
if (!bce_->emitJumpTarget(&here))
|
||||
return false;
|
||||
caseIndex_++;
|
||||
|
||||
tdzCacheCaseAndBody_.emplace(bce_);
|
||||
|
||||
state_ = State::CaseBody;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitCaseBody(int32_t caseValue, const TableGenerator& tableGen)
|
||||
{
|
||||
MOZ_ASSERT(kind_ == Kind::Table);
|
||||
MOZ_ASSERT(state_ == State::Table ||
|
||||
state_ == State::CaseBody || state_ == State::DefaultBody);
|
||||
|
||||
tdzCacheCaseAndBody_.reset();
|
||||
|
||||
JumpTarget here;
|
||||
if (!bce_->emitJumpTarget(&here))
|
||||
return false;
|
||||
caseOffsets_[tableGen.toCaseIndex(caseValue)] = here.offset;
|
||||
|
||||
tdzCacheCaseAndBody_.emplace(bce_);
|
||||
|
||||
state_ = State::CaseBody;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitDefaultBody()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Table ||
|
||||
state_ == State::Case ||
|
||||
state_ == State::CaseBody);
|
||||
MOZ_ASSERT(!hasDefault_);
|
||||
|
||||
tdzCacheCaseAndBody_.reset();
|
||||
|
||||
if (state_ == State::Cond || state_ == State::Case) {
|
||||
// For cond switch, JSOP_DEFAULT is always emitted.
|
||||
if (!emitImplicitDefault())
|
||||
return false;
|
||||
}
|
||||
JumpTarget here;
|
||||
if (!bce_->emitJumpTarget(&here))
|
||||
return false;
|
||||
defaultJumpTargetOffset_ = here;
|
||||
|
||||
tdzCacheCaseAndBody_.emplace(bce_);
|
||||
|
||||
hasDefault_ = true;
|
||||
state_ = State::DefaultBody;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SwitchEmitter::emitEnd()
|
||||
{
|
||||
MOZ_ASSERT(state_ == State::Cond || state_ == State::Table ||
|
||||
state_ == State::CaseBody || state_ == State::DefaultBody);
|
||||
|
||||
tdzCacheCaseAndBody_.reset();
|
||||
|
||||
if (!hasDefault_) {
|
||||
// If no default case, offset for default is to end of switch.
|
||||
if (!bce_->emitJumpTarget(&defaultJumpTargetOffset_))
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(defaultJumpTargetOffset_.offset != -1);
|
||||
|
||||
// Set the default offset (to end of switch if no default).
|
||||
jsbytecode* pc;
|
||||
if (kind_ == Kind::Cond) {
|
||||
pc = nullptr;
|
||||
bce_->patchJumpsToTarget(condSwitchDefaultOffset_, defaultJumpTargetOffset_);
|
||||
} else {
|
||||
// Fill in the default jump target.
|
||||
pc = bce_->code(top_);
|
||||
SET_JUMP_OFFSET(pc, defaultJumpTargetOffset_.offset - top_);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
}
|
||||
|
||||
// Set the SRC_SWITCH note's offset operand to tell end of switch.
|
||||
if (!bce_->setSrcNoteOffset(noteIndex_, 0, bce_->lastNonJumpTargetOffset() - top_))
|
||||
return false;
|
||||
|
||||
if (kind_ == Kind::Table) {
|
||||
// Skip over the already-initialized switch bounds.
|
||||
pc += 2 * JUMP_OFFSET_LEN;
|
||||
|
||||
// Fill in the jump table, if there is one.
|
||||
for (uint32_t i = 0, length = caseOffsets_.length(); i < length; i++) {
|
||||
ptrdiff_t off = caseOffsets_[i];
|
||||
SET_JUMP_OFFSET(pc, off == 0 ? 0 : off - top_);
|
||||
pc += JUMP_OFFSET_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
// Patch breaks before leaving the scope, as all breaks are under the
|
||||
// lexical scope if it exists.
|
||||
if (!controlInfo_->patchBreaks(bce_))
|
||||
return false;
|
||||
|
||||
if (emitterScope_ && !emitterScope_->leave(bce_))
|
||||
return false;
|
||||
|
||||
emitterScope_.reset();
|
||||
tdzCacheLexical_.reset();
|
||||
|
||||
controlInfo_.reset();
|
||||
|
||||
state_ = State::End;
|
||||
return true;
|
||||
}
|
||||
470
js/src/frontend/SwitchEmitter.h
Normal file
470
js/src/frontend/SwitchEmitter.h
Normal file
|
|
@ -0,0 +1,470 @@
|
|||
/* -*- 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 frontend_SwitchEmitter_h
|
||||
#define frontend_SwitchEmitter_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "jsalloc.h"
|
||||
|
||||
#include "frontend/BytecodeControlStructures.h"
|
||||
#include "frontend/EmitterScope.h"
|
||||
#include "frontend/JumpList.h"
|
||||
#include "frontend/TDZCheckCache.h"
|
||||
#include "gc/Rooting.h"
|
||||
#include "js/Value.h"
|
||||
#include "js/Vector.h"
|
||||
#include "vm/Scope.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
|
||||
// Class for emitting bytecode for switch-case-default block.
|
||||
//
|
||||
// Usage: (check for the return value is omitted for simplicity)
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; }`
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
//
|
||||
// se.validateCaseCount(1);
|
||||
// se.emitCond();
|
||||
//
|
||||
// emit(c1_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body;
|
||||
// default: def_body; }`
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
//
|
||||
// se.validateCaseCount(2);
|
||||
// se.emitCond();
|
||||
//
|
||||
// emit(c1_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// emit(c2_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c2_body);
|
||||
//
|
||||
// se.emitDefaultBody();
|
||||
// emit(def_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body; }`
|
||||
// with Table Switch
|
||||
// SwitchEmitter::TableGenerator tableGen(this);
|
||||
// tableGen.addNumber(c1_expr_value);
|
||||
// tableGen.addNumber(c2_expr_value);
|
||||
// tableGen.finish(2);
|
||||
//
|
||||
// // If `!tableGen.isValid()` here, `emitCond` should be used instead.
|
||||
//
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
// se.validateCaseCount(2);
|
||||
// se.emitTable(tableGen);
|
||||
//
|
||||
// se.emitCaseBody(c1_expr_value, tableGen);
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitCaseBody(c2_expr_value, tableGen);
|
||||
// emit(c2_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body;
|
||||
// default: def_body; }`
|
||||
// with Table Switch
|
||||
// SwitchEmitter::TableGenerator tableGen(bce);
|
||||
// tableGen.addNumber(c1_expr_value);
|
||||
// tableGen.addNumber(c2_expr_value);
|
||||
// tableGen.finish(2);
|
||||
//
|
||||
// // If `!tableGen.isValid()` here, `emitCond` should be used instead.
|
||||
//
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
// se.validateCaseCount(2);
|
||||
// se.emitTable(tableGen);
|
||||
//
|
||||
// se.emitCaseBody(c1_expr_value, tableGen);
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitCaseBody(c2_expr_value, tableGen);
|
||||
// emit(c2_body);
|
||||
//
|
||||
// se.emitDefaultBody();
|
||||
// emit(def_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; }`
|
||||
// in case c1_body contains lexical bindings
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
//
|
||||
// se.validateCaseCount(1);
|
||||
//
|
||||
// se.emitLexical(bindings);
|
||||
//
|
||||
// se.emitCond();
|
||||
//
|
||||
// emit(c1_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
// `switch (discriminant) { case c1_expr: c1_body; }`
|
||||
// in case c1_body contains hosted functions
|
||||
// SwitchEmitter se(this);
|
||||
// se.emitDiscriminant(Some(offset_of_switch));
|
||||
// emit(discriminant);
|
||||
//
|
||||
// se.validateCaseCount(1);
|
||||
//
|
||||
// se.emitLexical(bindings);
|
||||
// emit(hosted functions);
|
||||
//
|
||||
// se.emitCond();
|
||||
//
|
||||
// emit(c1_expr);
|
||||
// se.emitCaseJump();
|
||||
//
|
||||
// se.emitCaseBody();
|
||||
// emit(c1_body);
|
||||
//
|
||||
// se.emitEnd();
|
||||
//
|
||||
class MOZ_STACK_CLASS SwitchEmitter
|
||||
{
|
||||
// Bytecode for each case.
|
||||
//
|
||||
// Cond Switch
|
||||
// {discriminant}
|
||||
// JSOP_CONDSWITCH
|
||||
//
|
||||
// {c1_expr}
|
||||
// JSOP_CASE c1
|
||||
//
|
||||
// JSOP_JUMPTARGET
|
||||
// {c2_expr}
|
||||
// JSOP_CASE c2
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// JSOP_JUMPTARGET
|
||||
// JSOP_DEFAULT default
|
||||
//
|
||||
// c1:
|
||||
// JSOP_JUMPTARGET
|
||||
// {c1_body}
|
||||
// JSOP_GOTO end
|
||||
//
|
||||
// c2:
|
||||
// JSOP_JUMPTARGET
|
||||
// {c2_body}
|
||||
// JSOP_GOTO end
|
||||
//
|
||||
// default:
|
||||
// end:
|
||||
// JSOP_JUMPTARGET
|
||||
//
|
||||
// Table Switch
|
||||
// {discriminant}
|
||||
// JSOP_TABLESWITCH c1, c2, ...
|
||||
//
|
||||
// c1:
|
||||
// JSOP_JUMPTARGET
|
||||
// {c1_body}
|
||||
// JSOP_GOTO end
|
||||
//
|
||||
// c2:
|
||||
// JSOP_JUMPTARGET
|
||||
// {c2_body}
|
||||
// JSOP_GOTO end
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// end:
|
||||
// JSOP_JUMPTARGET
|
||||
|
||||
public:
|
||||
enum class Kind {
|
||||
Table,
|
||||
Cond
|
||||
};
|
||||
|
||||
// Class for generating optimized table switch data.
|
||||
class MOZ_STACK_CLASS TableGenerator
|
||||
{
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
// Bit array for given numbers.
|
||||
mozilla::Maybe<js::Vector<size_t, 128, SystemAllocPolicy>> intmap_;
|
||||
|
||||
// The length of the intmap_.
|
||||
int32_t intmapBitLength_ = 0;
|
||||
|
||||
// The length of the table.
|
||||
uint32_t tableLength_ = 0;
|
||||
|
||||
// The lower and higher bounds of the table.
|
||||
int32_t low_ = JSVAL_INT_MAX, high_ = JSVAL_INT_MIN;
|
||||
|
||||
// Whether the table is still valid.
|
||||
bool valid_= true;
|
||||
|
||||
#ifdef DEBUG
|
||||
bool finished_ = false;
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit TableGenerator(BytecodeEmitter* bce)
|
||||
: bce_(bce)
|
||||
{}
|
||||
|
||||
void setInvalid() {
|
||||
valid_ = false;
|
||||
}
|
||||
MOZ_MUST_USE bool isValid() const {
|
||||
return valid_;
|
||||
}
|
||||
MOZ_MUST_USE bool isInvalid() const {
|
||||
return !valid_;
|
||||
}
|
||||
|
||||
// Add the given number to the table. The number is the value of
|
||||
// `expr` for `case expr:` syntax.
|
||||
MOZ_MUST_USE bool addNumber(int32_t caseValue);
|
||||
|
||||
// Finish generating the table.
|
||||
// `caseCount` should be the number of cases in the switch statement,
|
||||
// excluding the default case.
|
||||
void finish(uint32_t caseCount);
|
||||
|
||||
private:
|
||||
friend SwitchEmitter;
|
||||
|
||||
// The following methods can be used only after calling `finish`.
|
||||
|
||||
// Returns the lower bound of the added numbers.
|
||||
int32_t low() const {
|
||||
MOZ_ASSERT(finished_);
|
||||
return low_;
|
||||
}
|
||||
|
||||
// Returns the higher bound of the numbers.
|
||||
int32_t high() const {
|
||||
MOZ_ASSERT(finished_);
|
||||
return high_;
|
||||
}
|
||||
|
||||
// Returns the index in SwitchEmitter.caseOffsets_ for table switch.
|
||||
uint32_t toCaseIndex(int32_t caseValue) const;
|
||||
|
||||
// Returns the length of the table.
|
||||
// This method can be called only if `isValid()` is true.
|
||||
uint32_t tableLength() const;
|
||||
};
|
||||
|
||||
private:
|
||||
BytecodeEmitter* bce_;
|
||||
|
||||
// `kind_` should be set to the correct value in emitCond/emitTable.
|
||||
Kind kind_ = Kind::Cond;
|
||||
|
||||
// True if there's explicit default case.
|
||||
bool hasDefault_ = false;
|
||||
|
||||
// The source note index for SRC_CONDSWITCH.
|
||||
unsigned noteIndex_ = 0;
|
||||
|
||||
// Source note index of the previous SRC_NEXTCASE.
|
||||
unsigned caseNoteIndex_ = 0;
|
||||
|
||||
// The number of cases in the switch statement, excluding the default case.
|
||||
uint32_t caseCount_ = 0;
|
||||
|
||||
// Internal index for case jump and case body, used by cond switch.
|
||||
uint32_t caseIndex_ = 0;
|
||||
|
||||
// Bytecode offset after emitting `discriminant`.
|
||||
ptrdiff_t top_ = 0;
|
||||
|
||||
// Bytecode offset of the previous JSOP_CASE.
|
||||
ptrdiff_t lastCaseOffset_ = 0;
|
||||
|
||||
// Bytecode offset of the JSOP_JUMPTARGET for default body.
|
||||
JumpTarget defaultJumpTargetOffset_ = { -1 };
|
||||
|
||||
// Bytecode offset of the JSOP_DEFAULT.
|
||||
JumpList condSwitchDefaultOffset_;
|
||||
|
||||
// Instantiated when there's lexical scope for entire switch.
|
||||
mozilla::Maybe<TDZCheckCache> tdzCacheLexical_;
|
||||
mozilla::Maybe<EmitterScope> emitterScope_;
|
||||
|
||||
// Instantiated while emitting case expression and case/default body.
|
||||
mozilla::Maybe<TDZCheckCache> tdzCacheCaseAndBody_;
|
||||
|
||||
// Control for switch.
|
||||
mozilla::Maybe<BreakableControl> controlInfo_;
|
||||
|
||||
mozilla::Maybe<uint32_t> switchPos_;
|
||||
|
||||
// Cond Switch:
|
||||
// Offset of each JSOP_CASE.
|
||||
// Table Switch:
|
||||
// Offset of each JSOP_JUMPTARGET for case.
|
||||
js::Vector<ptrdiff_t, 32, SystemAllocPolicy> caseOffsets_;
|
||||
|
||||
// The state of this emitter.
|
||||
//
|
||||
// +-------+ emitDiscriminant +--------------+
|
||||
// | Start |----------------->| Discriminant |-+
|
||||
// +-------+ +--------------+ |
|
||||
// |
|
||||
// +-------------------------------------------+
|
||||
// |
|
||||
// | validateCaseCount +-----------+
|
||||
// +->+------------------------>+------------------>| CaseCount |-+
|
||||
// | ^ +-----------+ |
|
||||
// | emitLexical +---------+ | |
|
||||
// +------------>| Lexical |-+ |
|
||||
// +---------+ |
|
||||
// |
|
||||
// +--------------------------------------------------------------+
|
||||
// |
|
||||
// | emitTable +-------+
|
||||
// +---------->| Table |---------------------------->+-+
|
||||
// | +-------+ ^ |
|
||||
// | | |
|
||||
// | emitCond +------+ | |
|
||||
// +---------->| Cond |-+------------------------>+->+ |
|
||||
// +------+ | ^ |
|
||||
// | | |
|
||||
// | emitCase +------+ | |
|
||||
// +->+--------->| Case |->+-+ |
|
||||
// ^ +------+ | |
|
||||
// | | |
|
||||
// +--------------------+ |
|
||||
// |
|
||||
// +---------------------------------------------------+
|
||||
// |
|
||||
// | emitEnd +-----+
|
||||
// +-+----------------------------------------->+-------->| End |
|
||||
// | ^ +-----+
|
||||
// | emitCaseBody +----------+ |
|
||||
// +->+-+---------------->| CaseBody |--->+-+-+
|
||||
// ^ | +----------+ ^ |
|
||||
// | | | |
|
||||
// | | emitDefaultBody +-------------+ | |
|
||||
// | +---------------->| DefaultBody |-+ |
|
||||
// | +-------------+ |
|
||||
// | |
|
||||
// +-------------------------------------+
|
||||
//
|
||||
enum class State {
|
||||
// The initial state.
|
||||
Start,
|
||||
|
||||
// After calling emitDiscriminant.
|
||||
Discriminant,
|
||||
|
||||
// After calling validateCaseCount.
|
||||
CaseCount,
|
||||
|
||||
// After calling emitLexical.
|
||||
Lexical,
|
||||
|
||||
// After calling emitCond.
|
||||
Cond,
|
||||
|
||||
// After calling emitTable.
|
||||
Table,
|
||||
|
||||
// After calling emitCase.
|
||||
Case,
|
||||
|
||||
// After calling emitCaseBody.
|
||||
CaseBody,
|
||||
|
||||
// After calling emitDefaultBody.
|
||||
DefaultBody,
|
||||
|
||||
// After calling emitEnd.
|
||||
End
|
||||
};
|
||||
State state_ = State::Start;
|
||||
|
||||
public:
|
||||
explicit SwitchEmitter(BytecodeEmitter* bce);
|
||||
|
||||
// `switchPos` is the offset in the source code for the character below:
|
||||
//
|
||||
// switch ( cond ) { ... }
|
||||
// ^
|
||||
// |
|
||||
// switchPos
|
||||
//
|
||||
// Can be Nothing() if not available.
|
||||
MOZ_MUST_USE bool emitDiscriminant(const mozilla::Maybe<uint32_t>& switchPos);
|
||||
|
||||
// `caseCount` should be the number of cases in the switch statement,
|
||||
// excluding the default case.
|
||||
MOZ_MUST_USE bool validateCaseCount(uint32_t caseCount);
|
||||
|
||||
// `bindings` is a lexical scope for the entire switch, in case there's
|
||||
// let/const effectively directly under case or default blocks.
|
||||
MOZ_MUST_USE bool emitLexical(Handle<LexicalScope::Data*> bindings);
|
||||
|
||||
MOZ_MUST_USE bool emitCond();
|
||||
MOZ_MUST_USE bool emitTable(const TableGenerator& tableGen);
|
||||
|
||||
MOZ_MUST_USE bool emitCaseJump();
|
||||
|
||||
MOZ_MUST_USE bool emitCaseBody();
|
||||
MOZ_MUST_USE bool emitCaseBody(int32_t caseValue, const TableGenerator& tableGen);
|
||||
MOZ_MUST_USE bool emitDefaultBody();
|
||||
MOZ_MUST_USE bool emitEnd();
|
||||
|
||||
private:
|
||||
MOZ_MUST_USE bool emitCaseOrDefaultJump(uint32_t caseIndex, bool isDefault);
|
||||
MOZ_MUST_USE bool emitImplicitDefault();
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_SwitchEmitter_h */
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include "frontend/SharedContext.h" // for Nestable
|
||||
#include "ds/Nestable.h"
|
||||
#include "frontend/NameCollections.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "vm/Stack.h"
|
||||
|
|
|
|||
286
js/src/frontend/TryEmitter.cpp
Normal file
286
js/src/frontend/TryEmitter.cpp
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
/* -*- 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 "frontend/TryEmitter.h"
|
||||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "frontend/SourceNotes.h"
|
||||
#include "vm/Opcodes.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
||||
using mozilla::Maybe;
|
||||
|
||||
TryEmitter::TryEmitter(BytecodeEmitter* bce, Kind kind,
|
||||
ShouldUseRetVal retValKind,
|
||||
ShouldUseControl controlKind)
|
||||
: bce_(bce),
|
||||
kind_(kind),
|
||||
retValKind_(retValKind),
|
||||
depth_(0),
|
||||
noteIndex_(0),
|
||||
tryStart_(0),
|
||||
state_(Start)
|
||||
{
|
||||
if (controlKind == UseControl)
|
||||
controlInfo_.emplace(bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try);
|
||||
finallyStart_.offset = 0;
|
||||
}
|
||||
|
||||
// Emits JSOP_GOTO to the end of try-catch-finally.
|
||||
// Used in `yield*`.
|
||||
bool
|
||||
TryEmitter::emitJumpOverCatchAndFinally()
|
||||
{
|
||||
if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TryEmitter::emitTry()
|
||||
{
|
||||
MOZ_ASSERT(state_ == Start);
|
||||
|
||||
// Since an exception can be thrown at any place inside the try block,
|
||||
// we need to restore the stack and the scope chain before we transfer
|
||||
// the control to the exception handler.
|
||||
//
|
||||
// For that we store in a try note associated with the catch or
|
||||
// finally block the stack depth upon the try entry. The interpreter
|
||||
// uses this depth to properly unwind the stack and the scope chain.
|
||||
depth_ = bce_->stackDepth;
|
||||
|
||||
// Record the try location, then emit the try block.
|
||||
if (!bce_->newSrcNote(SRC_TRY, ¬eIndex_))
|
||||
return false;
|
||||
if (!bce_->emit1(JSOP_TRY))
|
||||
return false;
|
||||
tryStart_ = bce_->offset();
|
||||
|
||||
state_ = Try;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TryEmitter::emitTryEnd()
|
||||
{
|
||||
MOZ_ASSERT(state_ == Try);
|
||||
MOZ_ASSERT(depth_ == bce_->stackDepth);
|
||||
|
||||
// GOSUB to finally, if present.
|
||||
if (hasFinally() && controlInfo_) {
|
||||
if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Source note points to the jump at the end of the try block.
|
||||
if (!bce_->setSrcNoteOffset(noteIndex_, 0, bce_->offset() - tryStart_ + JSOP_TRY_LENGTH))
|
||||
return false;
|
||||
|
||||
// Emit jump over catch and/or finally.
|
||||
if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_))
|
||||
return false;
|
||||
|
||||
if (!bce_->emitJumpTarget(&tryEnd_))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TryEmitter::emitCatch()
|
||||
{
|
||||
if (state_ == Try) {
|
||||
if (!emitTryEnd())
|
||||
return false;
|
||||
} else {
|
||||
MOZ_ASSERT(state_ == Catch);
|
||||
if (!emitCatchEnd(true))
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(bce_->stackDepth == depth_);
|
||||
|
||||
if (retValKind_ == UseRetVal) {
|
||||
// Clear the frame's return value that might have been set by the
|
||||
// try block:
|
||||
//
|
||||
// eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1
|
||||
if (!bce_->emit1(JSOP_UNDEFINED))
|
||||
return false;
|
||||
if (!bce_->emit1(JSOP_SETRVAL))
|
||||
return false;
|
||||
}
|
||||
|
||||
state_ = Catch;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
TryEmitter::emitCatchEnd(bool hasNext)
|
||||
{
|
||||
MOZ_ASSERT(state_ == Catch);
|
||||
|
||||
if (!controlInfo_)
|
||||
return true;
|
||||
|
||||
// gosub <finally>, if required.
|
||||
if (hasFinally()) {
|
||||
if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs))
|
||||
return false;
|
||||
MOZ_ASSERT(bce_->stackDepth == depth_);
|
||||
}
|
||||
|
||||
// Jump over the remaining catch blocks. This will get fixed
|
||||
// up to jump to after catch/finally.
|
||||
if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_))
|
||||
return false;
|
||||
|
||||
// If this catch block had a guard clause, patch the guard jump to
|
||||
// come here.
|
||||
if (controlInfo_->guardJump.offset != -1) {
|
||||
if (!bce_->emitJumpTargetAndPatch(controlInfo_->guardJump))
|
||||
return false;
|
||||
controlInfo_->guardJump.offset = -1;
|
||||
|
||||
// If this catch block is the last one, rethrow, delegating
|
||||
// execution of any finally block to the exception handler.
|
||||
if (!hasNext) {
|
||||
if (!bce_->emit1(JSOP_EXCEPTION))
|
||||
return false;
|
||||
if (!bce_->emit1(JSOP_THROW))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TryEmitter::emitFinally(const Maybe<uint32_t>& finallyPos /* = Nothing() */)
|
||||
{
|
||||
// If we are using controlInfo_ (i.e., emitting a syntactic try
|
||||
// blocks), we must have specified up front if there will be a finally
|
||||
// close. For internal try blocks, like those emitted for yield* and
|
||||
// IteratorClose inside for-of loops, we can emitFinally even without
|
||||
// specifying up front, since the internal try blocks emit no GOSUBs.
|
||||
if (!controlInfo_) {
|
||||
if (kind_ == TryCatch)
|
||||
kind_ = TryCatchFinally;
|
||||
} else {
|
||||
MOZ_ASSERT(hasFinally());
|
||||
}
|
||||
|
||||
if (state_ == Try) {
|
||||
if (!emitTryEnd())
|
||||
return false;
|
||||
} else {
|
||||
MOZ_ASSERT(state_ == Catch);
|
||||
if (!emitCatchEnd(false))
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(bce_->stackDepth == depth_);
|
||||
|
||||
if (!bce_->emitJumpTarget(&finallyStart_))
|
||||
return false;
|
||||
|
||||
if (controlInfo_) {
|
||||
// Fix up the gosubs that might have been emitted before non-local
|
||||
// jumps to the finally code.
|
||||
bce_->patchJumpsToTarget(controlInfo_->gosubs, finallyStart_);
|
||||
|
||||
// Indicate that we're emitting a subroutine body.
|
||||
controlInfo_->setEmittingSubroutine();
|
||||
}
|
||||
if (finallyPos) {
|
||||
if (!bce_->updateSourceCoordNotes(finallyPos.value()))
|
||||
return false;
|
||||
}
|
||||
if (!bce_->emit1(JSOP_FINALLY))
|
||||
return false;
|
||||
|
||||
if (retValKind_ == UseRetVal) {
|
||||
if (!bce_->emit1(JSOP_GETRVAL))
|
||||
return false;
|
||||
|
||||
// Clear the frame's return value to make break/continue return
|
||||
// correct value even if there's no other statement before them:
|
||||
//
|
||||
// eval("x: try { 1 } finally { break x; }"); // undefined, not 1
|
||||
if (!bce_->emit1(JSOP_UNDEFINED))
|
||||
return false;
|
||||
if (!bce_->emit1(JSOP_SETRVAL))
|
||||
return false;
|
||||
}
|
||||
|
||||
state_ = Finally;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TryEmitter::emitFinallyEnd()
|
||||
{
|
||||
MOZ_ASSERT(state_ == Finally);
|
||||
|
||||
if (retValKind_ == UseRetVal) {
|
||||
if (!bce_->emit1(JSOP_SETRVAL))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bce_->emit1(JSOP_RETSUB))
|
||||
return false;
|
||||
|
||||
bce_->hasTryFinally = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TryEmitter::emitEnd()
|
||||
{
|
||||
if (state_ == Catch) {
|
||||
MOZ_ASSERT(!hasFinally());
|
||||
if (!emitCatchEnd(false))
|
||||
return false;
|
||||
} else {
|
||||
MOZ_ASSERT(state_ == Finally);
|
||||
MOZ_ASSERT(hasFinally());
|
||||
if (!emitFinallyEnd())
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(bce_->stackDepth == depth_);
|
||||
|
||||
// ReconstructPCStack needs a NOP here to mark the end of the last
|
||||
// catch block.
|
||||
if (!bce_->emit1(JSOP_NOP))
|
||||
return false;
|
||||
|
||||
// Fix up the end-of-try/catch jumps to come here.
|
||||
if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_))
|
||||
return false;
|
||||
|
||||
// Add the try note last, to let post-order give us the right ordering
|
||||
// (first to last for a given nesting level, inner to outer by level).
|
||||
if (hasCatch()) {
|
||||
if (!bce_->tryNoteList.append(JSTRY_CATCH, depth_, tryStart_, tryEnd_.offset))
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we've got a finally, mark try+catch region with additional
|
||||
// trynote to catch exceptions (re)thrown from a catch block or
|
||||
// for the try{}finally{} case.
|
||||
if (hasFinally()) {
|
||||
if (!bce_->tryNoteList.append(JSTRY_FINALLY, depth_, tryStart_, finallyStart_.offset))
|
||||
return false;
|
||||
}
|
||||
|
||||
state_ = End;
|
||||
return true;
|
||||
}
|
||||
117
js/src/frontend/TryEmitter.h
Normal file
117
js/src/frontend/TryEmitter.h
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/* -*- 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 frontend_TryEmitter_h
|
||||
#define frontend_TryEmitter_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "frontend/BytecodeControlStructures.h"
|
||||
#include "frontend/JumpList.h"
|
||||
#include "frontend/TDZCheckCache.h"
|
||||
|
||||
namespace js {
|
||||
namespace frontend {
|
||||
|
||||
struct BytecodeEmitter;
|
||||
|
||||
class MOZ_STACK_CLASS TryEmitter
|
||||
{
|
||||
public:
|
||||
enum Kind {
|
||||
TryCatch,
|
||||
TryCatchFinally,
|
||||
TryFinally
|
||||
};
|
||||
enum ShouldUseRetVal {
|
||||
UseRetVal,
|
||||
DontUseRetVal
|
||||
};
|
||||
enum ShouldUseControl {
|
||||
UseControl,
|
||||
DontUseControl,
|
||||
};
|
||||
|
||||
private:
|
||||
BytecodeEmitter* bce_;
|
||||
Kind kind_;
|
||||
ShouldUseRetVal retValKind_;
|
||||
|
||||
// Track jumps-over-catches and gosubs-to-finally for later fixup.
|
||||
//
|
||||
// When a finally block is active, non-local jumps (including
|
||||
// jumps-over-catches) result in a GOSUB being written into the bytecode
|
||||
// stream and fixed-up later.
|
||||
//
|
||||
// If ShouldUseControl is DontUseControl, all that handling is skipped.
|
||||
// DontUseControl is used by yield* and the internal try-catch around
|
||||
// IteratorClose. These internal uses must:
|
||||
// * have only one catch block
|
||||
// * have no catch guard
|
||||
// * have JSOP_GOTO at the end of catch-block
|
||||
// * have no non-local-jump
|
||||
// * don't use finally block for normal completion of try-block and
|
||||
// catch-block
|
||||
//
|
||||
// Additionally, a finally block may be emitted when ShouldUseControl is
|
||||
// DontUseControl, even if the kind is not TryCatchFinally or TryFinally,
|
||||
// because GOSUBs are not emitted. This internal use shares the
|
||||
// requirements as above.
|
||||
Maybe<TryFinallyControl> controlInfo_;
|
||||
|
||||
int depth_;
|
||||
unsigned noteIndex_;
|
||||
ptrdiff_t tryStart_;
|
||||
JumpList catchAndFinallyJump_;
|
||||
JumpTarget tryEnd_;
|
||||
JumpTarget finallyStart_;
|
||||
|
||||
enum State {
|
||||
Start,
|
||||
Try,
|
||||
TryEnd,
|
||||
Catch,
|
||||
CatchEnd,
|
||||
Finally,
|
||||
FinallyEnd,
|
||||
End
|
||||
};
|
||||
State state_;
|
||||
|
||||
bool hasCatch() const {
|
||||
return kind_ == TryCatch || kind_ == TryCatchFinally;
|
||||
}
|
||||
bool hasFinally() const {
|
||||
return kind_ == TryCatchFinally || kind_ == TryFinally;
|
||||
}
|
||||
|
||||
public:
|
||||
TryEmitter(BytecodeEmitter* bce, Kind kind,
|
||||
ShouldUseRetVal retValKind = UseRetVal, ShouldUseControl controlKind = UseControl);
|
||||
|
||||
MOZ_MUST_USE bool emitJumpOverCatchAndFinally();
|
||||
|
||||
MOZ_MUST_USE bool emitTry();
|
||||
MOZ_MUST_USE bool emitCatch();
|
||||
|
||||
MOZ_MUST_USE bool emitFinally(const mozilla::Maybe<uint32_t>& finallyPos = mozilla::Nothing());
|
||||
|
||||
MOZ_MUST_USE bool emitEnd();
|
||||
|
||||
private:
|
||||
MOZ_MUST_USE bool emitTryEnd();
|
||||
MOZ_MUST_USE bool emitCatchEnd(bool hasNext);
|
||||
MOZ_MUST_USE bool emitFinallyEnd();
|
||||
};
|
||||
|
||||
} /* namespace frontend */
|
||||
} /* namespace js */
|
||||
|
||||
#endif /* frontend_TryEmitter_h */
|
||||
|
|
@ -139,18 +139,23 @@ UNIFIED_SOURCES += [
|
|||
'ds/LifoAlloc.cpp',
|
||||
'ds/MemoryProtectionExceptionHandler.cpp',
|
||||
'frontend/BytecodeCompiler.cpp',
|
||||
'frontend/BytecodeControlStructures.cpp',
|
||||
'frontend/BytecodeEmitter.cpp',
|
||||
'frontend/CallOrNewEmitter.cpp',
|
||||
'frontend/ElemOpEmitter.cpp',
|
||||
'frontend/EmitterScope.cpp',
|
||||
'frontend/FoldConstants.cpp',
|
||||
'frontend/ForOfLoopControl.cpp',
|
||||
'frontend/IfEmitter.cpp',
|
||||
'frontend/JumpList.cpp',
|
||||
'frontend/NameFunctions.cpp',
|
||||
'frontend/NameOpEmitter.cpp',
|
||||
'frontend/ParseNode.cpp',
|
||||
'frontend/PropOpEmitter.cpp',
|
||||
'frontend/SwitchEmitter.cpp',
|
||||
'frontend/TDZCheckCache.cpp',
|
||||
'frontend/TokenStream.cpp',
|
||||
'frontend/TryEmitter.cpp',
|
||||
'gc/Allocator.cpp',
|
||||
'gc/Barrier.cpp',
|
||||
'gc/GCTrace.cpp',
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include "transportflow.h"
|
||||
#include "AudioPacketizer.h"
|
||||
#include "StreamTracks.h"
|
||||
#include "webrtc/base/basictypes.h"
|
||||
|
||||
#include "webrtc/modules/rtp_rtcp/include/rtp_header_parser.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
|
||||
#include "webrtc/base/array_view.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue