Merge remote-tracking branch 'origin/tracking' into custom

This commit is contained in:
roytam1 2023-03-28 09:30:21 +08:00
commit 39d69840bf
20 changed files with 3240 additions and 2241 deletions

66
js/src/ds/Nestable.h Normal file
View 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 */

View 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>());
}

View 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

View file

@ -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.

File diff suppressed because it is too large Load diff

View 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 */

View 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;
}

View 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 */

View file

@ -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));

View file

@ -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"

View file

@ -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
{

View 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, &noteIndex_))
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, &noteIndex_))
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;
}

View 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 */

View file

@ -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"

View 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, &noteIndex_))
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;
}

View 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 */

View file

@ -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',

View file

@ -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"

View file

@ -13,6 +13,7 @@
#include <algorithm>
#include <vector>
#include <limits>
#include "webrtc/base/array_view.h"
#include "webrtc/typedefs.h"